diff --git a/.gitignore b/.gitignore index 74e4fdc1f4a0..91bb184ce7b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1173,3 +1173,5 @@ patches_after_merge.ply CMakeUserPresets.json /.cache compile_commands.json +/HDVF/test/HDVF/tmp +/HDVF/examples/HDVF/tmp diff --git a/Algebraic_foundations/doc/Algebraic_foundations/CGAL/number_utils.h b/Algebraic_foundations/doc/Algebraic_foundations/CGAL/number_utils.h index 74d7c452e996..cf0b3c55bec9 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/CGAL/number_utils.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/CGAL/number_utils.h @@ -22,7 +22,7 @@ namespace CGAL { \ingroup PkgAlgebraicFoundationsRef The template function `compare()` compares the first argument with respect to -the second, i.e.\ it returns `CGAL::LARGER` if \f$ x\f$ is larger than \f$ y\f$. +the second. In case the argument types `NT1` and `NT2` differ, `compare` is performed with the semantic of the type determined via diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/AlgebraicStructureTraits--IsInvertible.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/AlgebraicStructureTraits--IsInvertible.h new file mode 100644 index 000000000000..75bd2c43d45a --- /dev/null +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/AlgebraicStructureTraits--IsInvertible.h @@ -0,0 +1,48 @@ + +namespace AlgebraicStructureTraits_{ + +/*! +\ingroup PkgAlgebraicFoundationsAlgebraicStructuresConcepts +\cgalConcept + +`AdaptableUnaryFunction`, +returns `true` in case the argument is invertible in the ring. + +\cgalRefines{AdaptableUnaryFunction} + +\sa `AlgebraicStructureTraits` + +*/ + +class IsInvertible { +public: + +/// \name Types +/// @{ + +/*! +Is `AlgebraicStructureTraits::Boolean`. +*/ +typedef unspecified_type result_type; + +/*! +Is `AlgebraicStructureTraits::Type`. +*/ +typedef unspecified_type argument_type; + +/// @} + +/// \name Operations +/// @{ + +/*! + +returns true in case there exists an element \f$ y\f$ in the ring such that \f$ x * y \f$ is the one of the ring. +*/ +result_type operator()(argument_type x); + +/// @} + +}; /* end IsInvetible */ + +} /* end of namespace AlgebraicStructureTraits_ */ diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/Field.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/Field.h index cf0c4615928a..40b2b51ead69 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/Field.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/Field.h @@ -22,7 +22,6 @@ Moreover, `CGAL::Algebraic_structure_traits< Field >` is a model of \sa `IntegralDomain` \sa `UniqueFactorizationDomain` \sa `EuclideanRing` -\sa `Field` \sa `FieldWithSqrt` \sa `FieldWithKthRoot` \sa `FieldWithRootOf` diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithKthRoot.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithKthRoot.h index d786a07704df..8086aaddc9cd 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithKthRoot.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithKthRoot.h @@ -18,7 +18,6 @@ Moreover, `CGAL::Algebraic_structure_traits< FieldWithKthRoot >` is a model of ` \sa `EuclideanRing` \sa `Field` \sa `FieldWithSqrt` -\sa `FieldWithKthRoot` \sa `FieldWithRootOf` \sa `AlgebraicStructureTraits` diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithRootOf.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithRootOf.h index fbedea0a5cc8..a8392a445a7f 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithRootOf.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithRootOf.h @@ -20,7 +20,6 @@ Moreover, `CGAL::Algebraic_structure_traits< FieldWithRootOf >` is a model of `A \sa `Field` \sa `FieldWithSqrt` \sa `FieldWithKthRoot` -\sa `FieldWithRootOf` \sa `AlgebraicStructureTraits` */ diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithSqrt.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithSqrt.h index 6f4de356deb4..1522b5a0fb70 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithSqrt.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/FieldWithSqrt.h @@ -17,7 +17,6 @@ Moreover, `CGAL::Algebraic_structure_traits< FieldWithSqrt >` is a model of `Alg \sa `UniqueFactorizationDomain` \sa `EuclideanRing` \sa `Field` -\sa `FieldWithSqrt` \sa `FieldWithKthRoot` \sa `FieldWithRootOf` \sa `AlgebraicStructureTraits` diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomain.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomain.h index ebdcbfe2189c..5b375fb000b9 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomain.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomain.h @@ -19,7 +19,6 @@ Moreover, `CGAL::Algebraic_structure_traits< IntegralDomain >` is a model of \cgalRefines{IntegralDomainWithoutDivision} \sa `IntegralDomainWithoutDivision` -\sa `IntegralDomain` \sa `UniqueFactorizationDomain` \sa `EuclideanRing` \sa `Field` diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomainWithoutDivision.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomainWithoutDivision.h index 2636122f3792..aaea70c170be 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomainWithoutDivision.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/IntegralDomainWithoutDivision.h @@ -25,13 +25,13 @@ Moreover, `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >` is - \link AlgebraicStructureTraits::Algebraic_category `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Algebraic_category` \endlink derived from `CGAL::Integral_domain_without_division_tag` - \link AlgebraicStructureTraits::Is_zero `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Is_zero` \endlink which is a model of `AlgebraicStructureTraits_::IsZero` - \link AlgebraicStructureTraits::Is_one `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Is_one` \endlink which is a model of `AlgebraicStructureTraits_::IsOne` +- \link AlgebraicStructureTraits::Square `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Is_invertible` \endlink which is a model of `AlgebraicStructureTraits_::IsInvertible` - \link AlgebraicStructureTraits::Square `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Square` \endlink which is a model of `AlgebraicStructureTraits_::Square` - \link AlgebraicStructureTraits::Simplify `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Simplify` \endlink which is a model of `AlgebraicStructureTraits_::Simplify` - \link AlgebraicStructureTraits::Unit_part `CGAL::Algebraic_structure_traits< IntegralDomainWithoutDivision >::Unit_part` \endlink which is a model of `AlgebraicStructureTraits_::UnitPart` \cgalRefines{Assignable,CopyConstructible,DefaultConstructible,EqualityComparable,FromIntConstructible} -\sa `IntegralDomainWithoutDivision` \sa `IntegralDomain` \sa `UniqueFactorizationDomain` \sa `EuclideanRing` diff --git a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/UniqueFactorizationDomain.h b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/UniqueFactorizationDomain.h index 5087c6b2f5c8..0f57b4f9ba25 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/Concepts/UniqueFactorizationDomain.h +++ b/Algebraic_foundations/doc/Algebraic_foundations/Concepts/UniqueFactorizationDomain.h @@ -27,7 +27,6 @@ derived from `CGAL::Unique_factorization_domain_tag` \sa `IntegralDomainWithoutDivision` \sa `IntegralDomain` -\sa `UniqueFactorizationDomain` \sa `EuclideanRing` \sa `Field` \sa `FieldWithSqrt` diff --git a/Algebraic_foundations/doc/Algebraic_foundations/PackageDescription.txt b/Algebraic_foundations/doc/Algebraic_foundations/PackageDescription.txt index 7121abc1afd5..570b9d5b9846 100644 --- a/Algebraic_foundations/doc/Algebraic_foundations/PackageDescription.txt +++ b/Algebraic_foundations/doc/Algebraic_foundations/PackageDescription.txt @@ -38,6 +38,7 @@ - `AlgebraicStructureTraits` - `AlgebraicStructureTraits_::IsZero` - `AlgebraicStructureTraits_::IsOne` +- `AlgebraicStructureTraits_::IsInvertible` - `AlgebraicStructureTraits_::Square` - `AlgebraicStructureTraits_::Simplify` - `AlgebraicStructureTraits_::UnitPart` diff --git a/Algebraic_foundations/include/CGAL/Algebraic_structure_traits.h b/Algebraic_foundations/include/CGAL/Algebraic_structure_traits.h index e0d5eee6706c..b8c8a135e590 100644 --- a/Algebraic_foundations/include/CGAL/Algebraic_structure_traits.h +++ b/Algebraic_foundations/include/CGAL/Algebraic_structure_traits.h @@ -77,6 +77,7 @@ class Algebraic_structure_traits { typedef Null_functor Square; typedef Null_functor Is_zero; typedef Null_functor Is_one; + typedef Null_functor Is_invertible; typedef Null_functor Sqrt; typedef Null_functor Kth_root; typedef Null_functor Root_of; @@ -463,6 +464,16 @@ class Algebraic_structure_traits_base< Type_, Field_tag > CGAL_IMPLICIT_INTEROPERABLE_BINARY_OPERATOR( Type ) }; + class Is_invertible + : public CGAL::cpp98::unary_function< Type, bool > { + public: + bool operator()( const Type& t) const { + typedef Algebraic_structure_traits AST; + typename AST::Is_zero is_zero; + return ! is_zero(t); + } + }; + // maps to \c 1/x by default. class Inverse : public CGAL::cpp98::unary_function< Type, Type > { diff --git a/Algebraic_foundations/include/CGAL/number_utils.h b/Algebraic_foundations/include/CGAL/number_utils.h index 48d8fc487254..6f00d6fa78e8 100644 --- a/Algebraic_foundations/include/CGAL/number_utils.h +++ b/Algebraic_foundations/include/CGAL/number_utils.h @@ -89,6 +89,14 @@ is_one( const AS& x ) { return is_one( x ); } +template< class AS > +inline +typename Algebraic_structure_traits::Is_invertible::result_type +is_invertible( const AS& x ) { + typename Algebraic_structure_traits< AS >::Is_invertible is_invertible; + return is_invertible( x ); +} + template< class AS > inline typename Algebraic_structure_traits< AS >::Sqrt::result_type diff --git a/Algebraic_foundations/test/Algebraic_foundations/Algebraic_structure_traits.cpp b/Algebraic_foundations/test/Algebraic_foundations/Algebraic_structure_traits.cpp index efd31636c51d..b74ad4d332ef 100644 --- a/Algebraic_foundations/test/Algebraic_foundations/Algebraic_structure_traits.cpp +++ b/Algebraic_foundations/test/Algebraic_foundations/Algebraic_structure_traits.cpp @@ -42,6 +42,7 @@ int main(){ CGAL_IS_AST_NULL_FUNCTOR ( Square); CGAL_IS_AST_NULL_FUNCTOR ( Is_zero); CGAL_IS_AST_NULL_FUNCTOR ( Is_one); + CGAL_IS_AST_NULL_FUNCTOR ( Is_invertible); CGAL_IS_AST_NULL_FUNCTOR ( Sqrt); CGAL_IS_AST_NULL_FUNCTOR ( Kth_root); CGAL_IS_AST_NULL_FUNCTOR ( Root_of); diff --git a/Documentation/doc/Documentation/packages.txt b/Documentation/doc/Documentation/packages.txt index d93b8980be2e..4a68ef1867a4 100644 --- a/Documentation/doc/Documentation/packages.txt +++ b/Documentation/doc/Documentation/packages.txt @@ -128,7 +128,6 @@ \package_listing{Surface_mesh_skeletonization} \package_listing{Surface_mesh_approximation} \package_listing{Heat_method_3} -\package_listing{Surface_mesh_topology} \cgalPackageSection{PartPointSetProcessing,Point Set Processing} @@ -141,6 +140,11 @@ \package_listing{Stream_lines_2} \package_listing{Classification} +\cgalPackageSection{PartTopology,Topology} + +\package_listing{Surface_mesh_topology} +\package_listing{HDVF} + \cgalPackageSection{PartSearchStructures,Spatial Searching and Sorting} \package_listing{Point_set_2} diff --git a/HDVF/doc/HDVF/Concepts/AbstractChainComplex.h b/HDVF/doc/HDVF/Concepts/AbstractChainComplex.h new file mode 100644 index 000000000000..5749b2aeeb33 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/AbstractChainComplex.h @@ -0,0 +1,179 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `AbstractChainComplex` describes the requirements for (topological) chain complexes associated to abstract complexes used in the concept `HDVF`. + + It provides methods to: + + - get the dimension of the complex, the number of cells in each dimension + - get the boundary and co-boundary of cell(s) + - get the vertices of a given cell + - output the complex in text format + + Cells are indexed along each dimension and thus identified by their index together with their dimension. + + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Abstract_simplicial_chain_complex`} + \cgalHasModelsEnd + + */ + +class AbstractChainComplex +{ +public: + /// \name Types + /// @{ + + /*! + Type of coefficients ring used to compute homology, model of `IntegralDomainWithoutDivision` + */ + typedef unspecified_type Coefficient_ring; + + /** + Type of structure of sparse matrix used. + + A model of `SparseMatrixStructure` providing a model of `SparseChain` and an associated model of `SparseMatrix`. + + \warning Check if the concept `SparseMatrixStructure`and documentation are correct... + */ + typedef unspecified_type Sparse_matrix_structure; + + /*! + Type of column-major chains (returned by the boundary operator) + */ + typedef Sparse_matrix_structure:: template Sparse_chain_type Column_chain; + + /*! + Type of row-major chains (returned by the co-boundary operator) + */ + typedef Sparse_matrix_structure:: template Sparse_chain_type Row_chain ; + + /*! + Type of column-major sparse matrices (used to store the boundary operator) + */ + typedef Sparse_matrix_structure:: template Sparse_matrix_type Column_matrix; + + /// @} + + /// \name Verification operations + /// @{ + + /** \brief Checks if `q` belongs to the range of dimensions of cells in the complex. */ + bool is_valid_cell_dimension(int q); + /** \brief Checks if the cell of index `i` and dimension `q` belongs to the range of dimensions of cells in the complex. */ + bool is_valid_cell(size_t i, int q); + + /// @} + + + /// \name Access functions + /// @{ + + /*! + \brief Returns the dimension of the complex. + * + * This dimension is the largest dimension of cells. + * + * \return The dimension of the complex. + */ + int dimension(); + + /** \brief Get (unique) object Id. + * + * For comparison of constant references to the complex. + */ + size_t id () const; + + /*! + \brief Returns the number of cells in a given dimension. + * + * \param q Dimension along which the number of cells is returned. + * + * \return Number of cells in dimension `q`. If `q` is not in the interval `[0,d]` with `d` the dimension of the complex, the function returns an empty column chain. + */ + size_t number_of_cells(int q); + + /*! + \brief Returns a constant reference to the vector of boundary matrices (along each dimension). + * + * The q-th element of this vector is a column-major sparse matrix containing the boundaries of q-cells (i.e. rows encode q-1 cells and columns q cells). + * + * \return A constant reference to the vector of column-major boundary matrices along each dimension. + */ + const vector & boundary_matrices() const; + + /*! + \brief Returns a constant reference over the boundary matrix of dimension `q` (i.e.\ column-major matrix of \f$\partial_q\f$). + * + * It is a column-major sparse matrix containing the boundaries of q-cells (i.e. rows encode q-1 cells and columns q cells). + * + * \param q Dimension of the boundary matrix (i.e. columns will contain the boundary of dimension q cells). + * + * \return A constant reference over the column-major sparse matrix containing the matrix of the boundary operator of dimension q. + */ + const Column_matrix & boundary_matrix(int q) const; + + /*! + * \brief Returns the boundary of the cell `id_cell` in dimension `q`. + * + * \param id_cell %Index of the cell. + * \param q Dimension of the cell. + * + * \return A copy of the column-major chain containing the boundary of the cell id_cell in dimension `q`. If `q` is not in the interval `[0,d]` with `d` the dimension of the complex, the function returns an empty column chain. + */ + Column_chain d(size_t id_cell, int q); + + + /*! + \brief Returns the co-boundary of the cell `id_cell` in dimension `q`. + * + * This chain is actually a row of the boundary matrix in dimension `q`. + * + * \warning As the boundary matrix is stored column-major, this entails crossing the full matrix to extract the row coefficients (O(number of non empty columns)) + * + * \param id_cell %Index of the cell. + * \param q Dimension of the cell. + * + * \return The row-major chain containing the co-boundary of the cell id_cell in dimension `q`. If `q` is not in the interval `[0,d]` with `d` the dimension of the complex, the function returns an empty row chain. + */ + Row_chain cod(size_t id_cell, int q); + + /*! + \brief Returns the vertices of a given cell (that is, the indices of its faces of dimension 0). + + */ + std::vector bottom_faces(size_t id_cell, int q) const; + + /*! + * \brief Returns the cofaces of a given chain in dimension `q`. + * + * The resulting chain, whatever the storage format of the input, is column-major, lies in dimension `q`+1 and is null if this dimension exceeds the dimension of the complex. + * + * \tparam CoefficientR `CoefficientRing` of the chain. + * \tparam StorageF `StorageFormat` of the chain. + */ + template + Column_chain cofaces_chain (SparseChain chain, int q) const; + + /// @} + + /// \name Input, output + /// @{ + /// + /*! + \brief Inserts the chain complex in text mode in the stream. + + Displays the number of cells in each dimension and the boundary matrix in each dimension. + + \param out The output stream. + \param complex Constant reference over the complex considered. + */ + friend std::ostream& operator<<(std::ostream& out, const AbstractChainComplex& complex) const; + /// @} +}; + + + + diff --git a/HDVF/doc/HDVF/Concepts/Filtration.h b/HDVF/doc/HDVF/Concepts/Filtration.h new file mode 100644 index 000000000000..f8531470c540 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/Filtration.h @@ -0,0 +1,199 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `Filtration` describes the requirements for persistent filtrations associated to persistent homology computation. + + By definition, a filtration over a chain complex `K` is a sequence of sub complexes \f$\{K_t\,;\, t\in[d_1, \ldots, d_N]\}\f$, where \f$d_i\f$ are scalars, such that: + + - \f$\forall d\leqslant d'\f$, \f$K_d\subseteq K_{d'}\f$. + - \f$|K_{d_{i+1}}| = |K_{d_{i}}|+1\f$. + - As a consequence, any cell \f$\sigma\in K\f$, is "added" at a given *time* \f$i\f$ and the corresponding \f$d_i\f$ is called its *degree* (denoted by \f$\mathrm{deg}(\sigma)\f$). + - \f$K_{d_1}\f$ is an empty complex. + - \f$K_{d_N} = K\f$. + - For any cell \f$\sigma\in K\f$, if \f$\tau\f$ is a proper face of \f$\sigma\f$, then \f$\mathrm{deg}(\tau)\leqslant \mathrm{deg}(\sigma)\f$. + + Hence a filtration is associated to an `AbstractChainComplex`. Each cell is equipped with a scalar value (called its degree) and the filtration is an enumeration of cells in any dimension by increasing degrees. + + In consequence, a filtration associated to a complex `K`: + - maps each cell to a scalar called its degree + - orders the cells of `K` (each cell has a unique index along the filtration called *time* along the filtration) such that: + - the index of a cell is larger that the indices of its faces, + - the degree map is increasing along the filtration. + + Let us remind that in all `AbstractChainComplex`, cells are indexed along each dimension and thus identified by their index together with their dimension. + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Filtration_core`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Filtration_lower_star`} + \cgalHasModelsEnd + + */ + +class Filtration +{ +public: + + /// \name Types + /// @{ + + /*! \brief Type of the underlying complex (a model of `AbstractChainComplex`). */ + typedef unspecified_type Chain_complex; + + /*! \brief (Scalar) type of degrees (model of `RealEmbeddable`). + */ + typedef unspecified_type Degree ; + + /*! \brief Type of coefficients used to compute homology . */ + typedef Complex::Coefficient_ring Coefficient_ring; + + + /*! \brief Type for indexing uniquely a cell. + * + * As stated in `AbstractChainComplex`, cells are identified by their dimension, together with their index along this dimension. The type `Cell` stores this pair: + * + * - First element of the pair: index of the cell. + * - Second element of the pair: dimension of the cell. + */ + typedef std::pair Cell ; + + /*! \brief Value type of the filtration iterator. + * Contains a cell (identified by its index and dimension in a `Cell`) and its associated degree. + */ + typedef struct { + Cell cell_dim ; + Degree degree ; + } Filtration_iter_value ; + + /// @} + +public: + + /// \name Iterator + /// @{ + /** + * \brief The iterator over filtration. + * + * Iterate the filtration by increasing degrees. + */ + struct iterator + { + // Iterator tags + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Filtration_iter_value; + + /*! \brief Iterator constructor + * + * \param f Constant reference on the `Filtration_core` iterated. + * \param i The initial index. + */ + iterator(const Filtration& f, size_t i=0); + + /*! \brief Iterator dereference + * + * \returns A `Filtration_iter_value` structure containing the information of the current cell and its degree. + */ + value_type operator*() const; + + /*! + * \brief Prefix incrementation. + * + * Moves to next cell in the filtration. + * + * \returns The reference to the current iterator. + */ + iterator& operator++(); + + /*! \brief Postfix incrementation. Moves to next cell in the filtration. + * \returns The pre-incremented iterator. + */ + iterator operator++(int); + + /*! + * \brief Equality check. + * \returns `true` if the indices are equal. + */ + friend bool operator== (const iterator& a, const iterator& b); + + /*! + * \brief Inequality check. + * \returns `true` if the indices are different. + */ + friend bool operator!= (const iterator& a, const iterator& b); + }; + + /*! + * \brief Iterator to the beginning of the filtration. + * + * \return The iterator to the beginning of the filtration. + */ + iterator begin(); + + /** + * \brief Iterator past-the-end of the filtration. + * + * \return The iterator past-the-end of the filtration. + */ + iterator end(); + + /// @} + + /// \name Getters and tests + /// @{ + + /*! \brief Gets the filtration size. + */ + size_t size () const; + + /*! \brief Gets the cell (that is cell index and dimension) at the index `i` of the filtration. + */ + Cell cell (size_t i) const; + + /*! \brief Gets the degree of the `i`th element of the filtration. + */ + Degree degree (size_t i) const; + + + /*! \brief Checks that a filtration is valid. + * Checks that cells are ordered in increasing degrees and all cells have indices larger than their faces. + */ + bool is_valid() const; + + /** \brief Comparison operator. */ + bool operator==(const Filtration_core& f) const; + + + /// @} + + // Input/output filtration + + /// \name Input, output + /// @{ + + /** + * \brief Exports the filtration time indices. + * + * The method exports the time index of every cells in each dimension. + * + * \returns A vector containing, for each dimension, the vector of labels by cell index. + */ + vector > export_filtration () const; + + /*! + \brief Extracts a filtration from a stream. + + The filtration is encoded using the simple %FILTR file format (see secHDVFfiltr_file_format "user manual" for details). + */ + friend std::istream & operator>>(std::istream & in, Filtration &f); + + /*! + \brief Inserts a filtration into a stream. + + The filtration is encoded using the simple %FILTR file format (see secHDVFfiltr_file_format "user manual" for details). + */ + friend std::ostream & operator<<(std::ostream & out, const Filtration &f); + /// @} +}; + + diff --git a/HDVF/doc/HDVF/Concepts/GeometricChainComplex.h b/HDVF/doc/HDVF/Concepts/GeometricChainComplex.h new file mode 100644 index 000000000000..0c362db8060f --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/GeometricChainComplex.h @@ -0,0 +1,86 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `GeometricChainComplex` refines the concept `AbstractChainComplex` and describes the requirements for (topological) chain complexes associated to geometric complexes used in the concept `HDVF`. + It adds to `AbstractChainComplex` methods to get vertex coordinates. + + \cgalRefines{AbstractChainComplex} + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Simplicial_chain_complex`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Cubical_chain_complex`} + \cgalHasModelsEnd + + */ + +class GeometricChainComplex +{ +public: + /// \name Types + /// @{ + + /*! + Type of points. + */ + typedef unspecified_type Point ; + /// @} + + + /// \name Access functions + /// @{ + + /*! + \brief Returns the vector of vertex coordinates. + */ + const std::vector& points() const; + + /*! + \brief Returns the coordinates of the cell of index `i` and dimension 0. + */ + Point point(size_t i) const; + + /// @} + + /// \name VTK export + /// @{ + + /** + * \brief Exports a column chain `chain` over a `GeometricChainComplex` to a %VTK file. + * + * Exports cells of `chain` with a non null coefficient to a %VTK file, with a distinguished cell (for instance the underlying critical cell to a generator). Two properties are exported: + * - the `Label` property (2 for the distinguished cell, 0 for other cells) + * - the `CellId` property containing the "basis" index of the cell. + * + * \param K Underlying `GeometricChainComplex`. + * \param filename Output file name. + * \param chain Column chain exported to %VTK. + * \param q Dimension of the chain. + * \param cellId Index of the distinguished cell. + * + * \exception File_not_found If the file `filename` cannot be created and opened, throw a `%std::runtime_error` exception. + */ + friend void write_VTK(const GeometricChainComplex &K, const std::string &filename, const Column_chain& chain, int q, size_t cellId); + + /** + * \brief Exports a `GeometricChainComplex` (plus, optionally, labels) to a %VTK file. + * + * The method generates legacy text %VTK files. Labels are exported as such in a %VTK property, together with CellID property, containing the index of each cell. + * + * \tparam CoefficientRing Coefficient ring of the underlying complex `K`. + * \tparam Traits Traits class of the underlying complex `K`. + * \tparam LabelType Type of labels provided (default: int). + * + * \param K `GeometricChainComplex` exported. + * \param filename Output file root (output filenames will be built from this root). + * \param labels Pointer to a vector of labels in each dimension. `labels->at(q)` is the set of integer labels of cells of dimension q. If labels is NULL, only CellID property is exported. + * \param label_type_name Typename used in %VTK export (e.g. "int" or "unsigned_long", see VTK manual ). + * + * \exception File_not_found If the file `filename` cannot be created and opened, throw a `%std::runtime_error` exception. + */ + template + void write_VTK(const GeometricChainComplex &K, const std::string &filename, const std::vector > *labels=NULL, std::string label_type_name = "int"); + + /// @} + +}; diff --git a/HDVF/doc/HDVF/Concepts/HDVF.h b/HDVF/doc/HDVF/Concepts/HDVF.h new file mode 100644 index 000000000000..098b129b28f9 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/HDVF.h @@ -0,0 +1,529 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `HDVF` describes the requirements for Homological Discrete Vector Fields (%HDVF for short) , a theory of computational homology unifying discrete Morse theory and effective homology. HDVFs were introduced by Aldo Gonzalez-Lorenzo in his PhD (see [AGL,2017], [AGL,2016]). + + A HDVF is a combinatorial tool computing the homology of a chain complex (described by the `AbstractChainComplex` concept) including "geometric" information such as homology and cohomology generators. + + In order to build a perfect HDVF, a pairing operation called `A()` associates (under conditions) two critical cells (becoming PRIMARY and SECONDARY) and updates the reduction in time \f$\mathscr O(n^2)\f$. Intuitively, this operation bears some similitudes with discrete Morse theory's DGVF arrows (see \ref secHDVFdiscrete_Morse "introduction to discrete Morse theory" in user manual). Actually, an HDVF can always be represented by a vector field - possibly with cycles. + The *validity condition for A* is tested by various `find_pair_A()` and `find_pairs_A()` methods, which search for such valid pairs. + + + Starting from this operation, `HDVF` provides two methods to generate perfect HDVFs (and hence compute homology): + - `compute_perfect_hdvf()` which computes a perfect HDVF by chosing at each step the first A-pairings available (depending on cells ordering in the chain complex) + - `compute_rand_perfect_hdvf()` which computes a perfect random HDVF by chosing at each step a rand A-pairing among all possible ones (this option is thus slower) + + For efficiency, the HDVF reduction can be computed partially and we define constants to define the HDVF computation option required: + - compute only the reduced boundary (option `OPT_BND`): to get Betti numbers + - compute \f$\partial'\f$ together with \f$h\f$ and \f$g\f$ (option `OPT_G`): to get homology generators + - compute \f$\partial'\f$ together with \f$h\f$ and \f$f\f$ (option `OPT_F`): to get co-homology generators + - compute the full reduction (option `OPT_FULL`): to get the full homological / cohomological information + + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf_core`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf_persistence`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf_duality`} + \cgalHasModelsEnd + + \sa `AbstractChainComplex` + \sa `IntegralDomainWithoutDivision` + \sa `SparseChain` + \sa `SparseMatrix` + + \warning How to describe constants declared in the namespace Homological_discrete_vector_field and used everywhere? PSC_flag, options, exporttype* + + \warning How mention more clearly the export functions from Geometric_chain_complex_tools which provide write_VTK functions ? + + [AGL, 2017] Aldo Gonzalez-Lorenzo, Alexandra Bac, Jean-Luc Mari, Pedro Real. Allowing cycles in discrete Morse theory, Topology and its Applications, Volume 228, 2017, Pages 1-35. + + [AGL, 2016] Aldo Gonzalez-Lorenzo. Computational Homology Applied to Discrete Objects. Discrete Mathematics [cs.DM]. Aix-Marseille Université; Universidad de Sevilla, 2016. + + */ + +class HDVF +{ +public: + /// \name Constants + /// @{ + + /** \brief HDVF computation option: compute only the reduced boundary. */ + const int OPT_BND = 0b0001; + /** \brief HDVF computation option: compute only the reduced boundary and \f$f\f$. */ + const int OPT_F = 0b0010; + /** \brief HDVF computation option: compute only the reduced boundary and \f$g\f$. */ + const int OPT_G = 0b0100; + /** \brief HDVF computation option: compute the full reduction. */ + const int OPT_FULL = 0b1000; + + /** \brief HDVF Enum for the label of cells. + * + * Values can be `PRIMARY`, `SECONDARY`, `CRITICAL` or `NONE` (for discarded cells in persistent homology or duality). + */ + enum PSC_flag { + PRIMARY, + SECONDARY, + CRITICAL, + NONE + }; + + /// @} + + /// \name Types + /// @{ + + /*! + * \brief Type of underlying chain complex (a model of `AbstractChainComplex`). + */ + typedef unspecified_type Complex; + + /*! \brief Type of coefficients used to compute homology (a model of `IntegralDomainWithoutDivision`). */ + typedef Complex::Coefficient_ring Coefficient_ring; + + /*! + * \brief Type of sparse chains (a model of `SparseChain`). + */ + typedef unspecified_type Sparse_chain; + + /*! + * \brief Type of sparse matrices (a model of `SparseMatrix`). + */ + typedef unspecified_type Sparse_matrix; + + + /*! \brief Structure to represent data for HDVF operations (between pairs of cells). + * + * Cells are always sorted so that the dimension of `sigma` is lesser than the dimension of `tau`. + */ + struct Cell_pair { + /// Index of the first cell + size_t sigma; + /// Index of the second cell + size_t tau; + /// Lower dimension of the cells + int dim; + /// Comparison operator + inline bool operator==(const Cell_pair& pair2); + /// Output operator + friend std::ostream& operator<<(std::ostream &out, const std::vector& pairs); + }; + + /*! + Type of column-major chains + */ + typedef Sparse_chain Column_chain; + + /*! + Type of row-major chains + */ + typedef Sparse_chain Row_chain ; + + /*! + Type of column-major sparse matrices + */ + typedef Sparse_matrix Column_matrix; + + /*! + Type of row-major sparse matrices + */ + typedef Sparse_matrix Row_matrix; + + /// @} + + /// \name Functions to find valid pairs for A() operations + /// @{ + + /*! + * \brief Finds a valid Cell_pair of dimension q / q+1 for A. + * + * The function searches a pair of critical cells \f$(\gamma_1, \gamma2)\f$ of dimension q / q+1, valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false`. + * + * \param q Lower dimension of the pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual Cell_pair find_pair_A(int q, bool &found) const; + + /*! + * \brief Finds a valid Cell_pair for A containing `gamma` (a cell of dimension `q`) + * + * The function searches a cell \f$\gamma'\f$ such that one of the following conditions holds: + * - \f$\gamma'\f$ has dimension q+1 and \f$(\gamma, \gamma')\f$ is valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma'), \gamma \rangle\f$ invertible), + * - \f$\gamma'\f$ has dimension q-1 and \f$(\gamma', \gamma)\f$ is valid for A (ie.\ such that \f$\langle \partial_{q}(\gamma), \gamma' \rangle\f$ invertible). + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false`. If the cell index is incorrect (larger than the number of cells in the dimension), `found` is set to `false`. + * + * \param q Dimension of the cell `gamma`. + * \param gamma Index of a cell to pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual Cell_pair find_pair_A(int q, size_t gamma, bool &found) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q / q+1 for A. + * + * The function searches all pairs of critical cells \f$(\gamma_1, \gamma2)\f$ of dimension q / q+1, valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false` and the function returns an empty vector. + * + * \param q Lower dimension of the pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual std::vector find_pairs_A(int q, bool &found) const; + + /** + * \brief Finds *all* valid Cell_pair for A containing `gamma` (a cell of dimension `q`) + * + * The function searches all `CRITICAL` cells \f$\gamma'\f$ such that one of the following conditions holds: + * - \f$\gamma'\f$ has dimension q+1 and \f$(\gamma, \gamma')\f$ is valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma'), \gamma \rangle\f$ invertible), + * - \f$\gamma'\f$ has dimension q-1 and \f$(\gamma', \gamma)\f$ is valid for A (ie.\ such that \f$\langle \partial_{q}(\gamma), \gamma' \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false`. If the cell index is incorrect (larger than the number of cells in the dimension), `found` is set to `false`. In both cases, the function returns an empty vector. + * + * \param q Dimension of the cell `gamma`. + * \param gamma Index of a cell to pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual std::vector find_pairs_A(int q, size_t gamma, bool &found) const; + + /** \brief Checks if the pair of cells \f$(\gamma, \gamma')\f$, of dimensions q / q+1, is valid for A. + * + * The pair is valid if \f$\langle \mathrm d(\gamma),\gamma' \rangle\f$ is invertible. + * + * \param gamma Index of the first cell (dimension `q`). + * \param gamma_prime Index of the second cell (dimension `q+1`). + * \param q Lower dimension of cells. + */ + + bool is_valid_pair_for_A(size_t gamma, size_t gamma_prime, int q); + + + /// @} + + + /// \name HDVF functions and operations + /// @{ + + /*! + * \brief A operation: pairs critical cells. + * + * A pair of critical cells \f$(\gamma_1, \gamma_2)\f$ of respective dimension q and q+1 is valid for A if \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ is invertible. After the `A()` operation, \f$\gamma_1\f$ becomes `PRIMARY`, \f$\gamma_2\f$ becomes `SECONDARY`. The A method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param gamma1 First cell of the pair (dimension `q`) + * \param gamma2 Second cell of the pair (dimension `q+1`) + * \param q Dimension of the pair + */ + void A(size_t gamma1, size_t gamma2, int q); + + /** + * \brief A operation (pairs critical cells). + * + * A pair of critical cells \f$(\gamma_1, \gamma_2)\f$ of respective dimension q and q+1 is valid for A if \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ is invertible. After the `A()` operation, \f$\gamma_1\f$ becomes `PRIMARY`, \f$\gamma_2\f$ becomes `SECONDARY`. The A method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param p Argument `Cell_pair` containing \f$\gamma_1\f$, \f$\gamma_2\f$ and the dimension of the pair. + */ + void A(const Cell_pair& p); + + /*! + * \brief Computes a perfect HDVF. + * + * As long as valid pairs for A exist, the function selects the first available pair (returned by `find_pair_A`()) and applies the corresponding `A()` operation. + * If the `IntegralDomainWithoutDivision` of coefficients is a field, this operation always produces a perfect HDVF (ie.\ the reduced boundary is null and the reduction provides homology and cohomology information). + * Otherwise the operation produces a maximal HDVF with a residual boundary matrix over critical cells. + * + * If the HDVF is initially not trivial (some cells have already been paired), the function completes it into a perfect HDVF. + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \return The vector of all `Cell_pair` paired with A. + * + */ + std::vector compute_perfect_hdvf(bool verbose = false); + + /*! + * \brief Computes a random perfect HDVF. + * + * As long as valid pairs for A exist, the function selects a random pair (among pairs returned by `find_pairs_A()`) and applies the corresponding `A()` operation. + * If the `IntegralDomainWithoutDivision` of coefficients is a field, this operation always produces a perfect HDVF (that is the reduced boundary is null and the reduction provides homology and cohomology information). + * + * If the HDVF is initially not trivial (some cells have already been paired), the function randomly completes it into a perfect HDVF. + * + * \warning This method is slower that `compute_perfect_hdvf()` (finding out all possible valid pairs requires additional time). + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \return The vector of all pairs of cells used for apply A. + */ + std::vector compute_rand_perfect_hdvf(bool verbose = false); + + /*! + * \brief Tests if a HDVF is perfect. + * + * The function returns `true` if the reduced boundary matrix is null and `false` otherwise. + */ + bool is_perfect_hdvf(); + + /** \brief Comparison operator (with a HDVF over the same underlying complex). + * + * Compares the HDVF with `other` HDVF (fast comparison, testing only HDVF flags). + * + * \param other Other HDVF to compare. + */ + bool operator==(const Hdvf_core& other); + + /** \brief Compares the HDVF with another HDVF over the same underlying complex. + * + * \param other Other HDVF to compare. + * \param full_compare Turns on "in depth" HDVF comparison (reduction matrices). + */ + + bool compare(const Hdvf_core& other, bool full_compare = false) const; + + /// @} + + /// \name Getters + /// @{ + + /*! + * \brief Gets cells with a given `PSC_flag` in any dimension. + * + * The function returns a vector containing, for each dimension, the vector of cells with a given `PSC_flag`. Indices are sorted by increasing value. + * + * \param flag PSC_flag to select. + */ + std::vector > psc_flag (PSC_flag flag) const; + + /*! + * \brief Gets cells with a given `PSC_flag` in dimension `q`. + * + * The function returns the vector of cells of dimension `q` with a given `PSC_flag`. Indices are sorted by increasing value. + * + * \param flag PSC_flag to select. + * \param q Dimension visited. + */ + std::vector psc_flag (PSC_flag flag, int q) const; + + /*! + * \brief Gets the PSC_flag of the cell `tau` in dimension `q`. + * + * \param tau Index of the cell. + * \param q Dimension of the cell. + */ + PSC_flag psc_flag (size_t tau, int q) const; + + /** + * \brief Gets the`PSC_flag` of all cells in dimension `q`. + * + * The function returns the vector of `PSC_flag` of cells of dimension `q`. + * + * \param q Dimension visited. + */ + virtual const std::vector& psc_flags (int q) const; + + /** + * \brief Gets the`PSC_flag` of all cells. + * + * The function returns a vector which `q`th element contains the vector of `PSC_flag` of cells of dimension `q`. + */ + virtual const std::vector >& psc_flags () const; + + /** \brief Returns the number of cells with a given flag in dimension `q`. + * + * \param flag `PSC_flag` tested. + * \param q Dimension along which the number of cells is returned. + */ + size_t number_of_cells_by_flag(PSC_flag flag, int q); + + /*! + * \brief Gets HDVF option. + */ + int hdvf_opts (); + + /*! + * \brief Gets the row-major matrix of \f$f\f$ (from the reduction associated to the HDVF). + */ + const Row_matrix& matrix_f (int q) const; + + /*! + * \brief Gets the column-major matrix of \f$g\f$ (from the reduction associated to the HDVF). + */ + const Column_matrix& matrix_g (int q) const; + + /*! + * \brief Gets the column-major matrix of \f$h\f$ (from the reduction associated to the HDVF). + */ + const Column_matrix& matrix_h (int q) const; + + /*! + * \brief Gets the column-major matrix of \f$\partial'\f$, reduced boundary operator (from the reduction associated to the HDVF). + */ + const Column_matrix& matrix_dd (int q) const; + + /** + \brief Gets a constant reference over the underlying chain complex. + */ + const Chain_complex& complex () const; + + /*! + * \brief Returns a chain containing the homology generator associated to `cell_index` (critical cell) of dimension `q`. + * + * The method exports the chain \f$g(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`. + * + * The complexity is in the number of (non zero) coefficients of the generator. + * + * \return A column-major chain. + */ + Column_chain homology_chain (size_t cell_index, int q) const; + + /*! + * \brief Returns a chain containing the cohomology generators associated to `cell_index` (critical cell) of dimension `q`. + * + * The method exports: + * - the chain \f$f^\star(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`, + * - or the co-faces of this chain if `co_faces` is true (sometimes easier to visualize cohomology generators) + * + * The complexity is in the number of (non zero) coefficients of the generator. + * + * Below, a sample mesh with, (left) homology generators, (right) two examples of cohomology generators (corresponding generators/co-generators bear similar colours): + * + * + * + * + * + * The same generators displayed through their co-faces: + * + * + * + * + * All homology / cohomology generators: + * + * + * + * \return A column-major chain. + */ + virtual Column_chain cohomology_chain (size_t cell_index, int dim, bool co_faces = false) const; + + /// @} + + /// \name Dimension restriction functions + /// @{ + + /** \brief Returns the dimension of the Hdvf computation. + * + * If HDVF computation is full (computation of homology in any dimension), returns -1; otherwise, returns the dimension along which the HDVF is computed. + */ + int dimension_restriction() const; + + /** \brief Changes the dimension of Hdvf computation. + * + * \param dimension Dimension along which the computation is restricted. Set -1 for full computation (computation of homology in any dimension), and a positive value lower than the complex dimension for a computation restricted to dimension `dimension`. + */ + inline void dimension_restriction(int dimension); + + /// @} + + /// \name Input, output + /// @{ + + /*! + * \brief Writes the matrices of the reduction. + * + * Writes the matrices of the reduction (that is \f$f\f$, \f$g\f$, \f$h\f$, \f$\partial'\f$ the reduced boundary). + * + * If `dimension_restriction` is -1, writes matrices in any dimensions. If `dimension_restriction` is positive, restricts output to `dimension_restriction`. If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \param out Output stream (by default, writes the complex to `std::cout`). + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs matrices in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + std::ostream& write_matrices(std::ostream &out = std::cout, int dimension_restriction = -2) const; + + /*! \brief Writes the homology and cohomology reduction information. + * + * Writes \f$f^*\f$, \f$g\f$ \f$\partial'\f$ the reduced boundary over each critical cell. + * + * If `dimension_restriction` is -1, writes the reduction in any dimensions. If `dimension_restriction` is positive, restricts output to `dimension_restriction`. If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \param out Output stream (by default, writes the complex to `std::cout`). + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs reductions in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + std::ostream& write_reduction(std::ostream &out = std::cout, int dimension_restriction = -2) const; + + /** + * \brief Writes the PSC flags of cells along computed dimensions. + * + * If `dimension_restriction` is -1, writes flags in any dimensions. If `dimension_restriction` is positive, restricts output to `dimension_restriction`. If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \param out Output stream (by default, writes the complex to `std::cout`). + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs reductions in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + std::ostream& write_flags(std::ostream &out = std::cout, int dimension_restriction = -2) const; + + /*! \brief Writes a HDVF together with the associated reduction (f, g, h, d matrices) + * + * Exports a HDVF to a stream in %HDVF file format (a simple text file format, see %HDVF_file_format "user manual" for a specification). + * + * \param out Output stream. + */ + std::ostream& write_hdvf_reduction(std::ostream& out) const ; + + /** \brief Writes a HDVF together with the associated reduction to a file (f, g, h, d matrices). + * + * Exports a HDVF to a file in `hdvf` file format (a simple text file format, see %HDVF_file_format "user manual" for a specification). + * + * \param filename Output file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void write_hdvf_reduction(std::string filename) const; + + /*! + * \brief Reads a HDVF together with the associated reduction (f, g, h, d matrices) + * + * Loads a HDVF and its reduction from a stream in `hdvf` file format, a simple text file format (see %HDVF_file_format "user manual" for a specification). + * \warning The underlying complex is not stored in the file! + * + * \param in_stream Input stream. + * + * \exception Incompatible_prefix If the prefix of the stored HDVF is not 'HDVF_core' raises a `%std::runtime_error`. + * + * \exception Incompatible_type If the type of the stored HDVF is not 0 (HDVF and reduction storage) raises a `%std::runtime_error`. + * + * \exception Incompatible_dimension If the dimension of the HDVF stored does not match the dimension of the underlying complex, raises a `%std::runtime_error`. + * + * \exception Incoherent_number_of_cells If the number of cells loaded in a given dimension does not match that of the underlying complex, raises a `%std::runtime_error`. + */ + std::istream& read_hdvf_reduction(std::istream& in_stream) ; + + /** + * \brief Reads a HDVF together with the associated reduction from a file (f, g, h, d matrices) + * + * Loads a HDVF and its reduction from a file in `hdvf` file format, a simple text file format (see %HDVF_file_format "user manual" for a specification). + * \warning The underlying complex is not stored in the file! + * + * \param filename Input file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void read_hdvf_reduction(std::string filename); + + /*! + * \brief Exports an integer code for cells labels (in particular for vtk export). + * + * The method exports integers encoding cells labels for every cell in each dimension. Labels are encoded as follows: + * - `PRIMARY` is encoded as -1 + * - `SECONDARY` is encoded as 1 + * - `CRITICAL` is encoded as 0 + * - `NONE` is encoded as -2 + * + * \return A vector containing, for each dimension, the vector of encoded labels by cell index. + */ + std::vector > psc_labels () const; + + /// @} + +}; diff --git a/HDVF/doc/HDVF/Concepts/HDVFTraits.h b/HDVF/doc/HDVF/Concepts/HDVFTraits.h new file mode 100644 index 000000000000..3bcf66e9ddf8 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/HDVFTraits.h @@ -0,0 +1,29 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `HDVFTraits` describes the requirements for geometric traits classes used in this package. + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf_traits_2`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf_traits_3`} + \cgalHasModelsBare{`CGAL::Homological_discrete_vector_field::Hdvf_traits_d`} + \cgalHasModelsEnd + + @todo add requirements on Point, Vector, FT, Bbox + */ +class HDVFTraits +{ +public: + /* \brief Type tag encoding the dimension of the data. Must be a `CGAL::Dimension_tag<>` */ + typedef unspecified_type Dimension ; + + /* \brief Geometric kernel type. Must be a model of the `Kernel` concept. */ + typedef unspecified_type Kernel; + typedef unspecified_type Point; + typedef unspecified_type Vector; + typedef typename Kernel::FT FT; + + /* \brief Type of bounding boxes in the data dimension. */ + typedef unspecified_type Bbox ; +}; diff --git a/HDVF/doc/HDVF/Concepts/SparseChain.h b/HDVF/doc/HDVF/Concepts/SparseChain.h new file mode 100644 index 000000000000..982c9bd9acb6 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/SparseChain.h @@ -0,0 +1,473 @@ +/*! +\ingroup PkgHDVFConcepts +\cgalConcept + +The concept `SparseChain` describes the requirements for sparse vectors (called *sparse chains* in homology) optimized for topological computations. +More precisely, `SparseChain` provides all the operations on chains required by the `SparseMatrix` concept. + + `SparseChain` encodes non zero coefficients of a sparse vector. + + `SparseChain` can be either row or column vectors. The following integer constants, called `Storage_format`, encode the direction of sparse chains (and sparse matrices). + - `CGAL::OSM::COLUMN` for column-major chains and matrices (which is the default), + - `CGAL::OSM::ROW` for row-major chains and matrices. + + +\cgalHasModelsBegin +\cgalHasModelsBare{`CGAL::OSM::Sparse_chain`} +\cgalHasModelsEnd + + \sa `IntegralDomainWithoutDivision` + \sa `SparseMatrix` +*/ + +class SparseChain { + +public: + /// \name Member data + /// @{ + + /*! + * \brief Chain storage format (either `CGAL::OSM::ROW` or `CGAL::OSM::COLUMN`). + */ + int Storage_format; + + /// \name Types + /// @{ + + /*! + * \brief Type of coefficients stored in the matrix (a model of `IntegralDomainWithoutDivision`). + */ + typedef unspecified_type Coefficient_ring; + + /** \brief Type of derived sparse matrices. */ + template + using Sparse_matrix_derived = OSM::Sparse_matrix_structure::Sparse_matrix_type; + + /*! + * \brief Sparse chain iterator type. + */ + typedef unspecified_type iterator; + + /*! + * \brief Sparse chain constant iterator type. + */ + typedef unspecified_type const_iterator; + + /// @} + + /// \name Creation, filling + /// @{ + + /*! + * \brief Creates new empty sparse chain. + * + * Creates a sparse chain encoding an empty linear combination of cells. + */ + SparseChain(); + + /*! + * \brief Creates new empty sparse chain (ie.\ zero-chain) of given size. + * + * Constructor with size, initializes an empty sparse chain encoding a linear combination of cells with all coefficients null. + */ + SparseChain(size_t chainSize); + + /** + * \brief Assignment. + * + *The assignment operator converts the storage format if needed (complexity \f$\mathscr O(k)\f$ with \f$k\f$ the number of non zero coefficients of `other`). + * + * \pre Chains must have the same `Coefficient_ring`. + * + * \param other Chain to copy into `this`. + */ + Sparse_chain& operator=(const Sparse_chain &other); + + /// @} + + /// \name Matrix informations and iterators + /// @{ + + /*! + * \brief Size of the chain + * + * \return Dimension of the basis (that is, the size of the chain). + */ + size_t dimension() const; + + /*! + * \brief Iterator to the beginning of the chain. + * + * \warning The chain is stored in an unordered map for speed reason. + * + * \return The function returns an iterator to the first non zero index. + */ + iterator begin() noexcept; + + /*! + * \brief Constant iterator to the beginning of the chain. + * + * \warning The chain is stored unordered for speed reason. + * + * \return The function returns a constant iterator to the first non zero index. + */ + const_iterator begin() const noexcept; + + /*! + * \brief Constant iterator to the beginning of the chain. + * + * \warning The chain is stored unordered for speed reason. + * + * \return The function returns a constant iterator to the first non zero index. + */ + const_iterator cbegin() const noexcept; + + /*! + * \brief Iterator to the end of the chain. + * + * \warning The chain is stored unordered for speed reason. + * + * \return The function returns an iterator past-the-end of the chain. + */ + iterator end() noexcept; + + /*! + * \brief Constant iterator to the end of the chain. + * + * \warning The chain is stored unordered for speed reason. + * + * \return The function returns a constant iterator past-the end of the chain. + */ + const_iterator end() const noexcept; + + /*! + * \brief Constant iterator to the end of the chain. + * + * \warning The chain is stored unordered for speed reason. + * + * \return The function returns a constant iterator to the ending of the chain. + */ + const_iterator cend() const noexcept; + + /// @} + + /// \name Output + /// @{ + + /*! + * \brief Inserts `chain` in the output stream with a compact "user friendly" format. + * + * \param stream The output stream. + * \param chain The chain to display. + * + * \return A reference to the modified stream. + */ + friend std::ostream& operator<<(std::ostream &stream, const SparseChain& chain); + + /** + * @brief Write a sparse chain to an output stream using the `.osm` file format. + * + * Outputs a sparse chain (the chain can be reloaded using `read_chain`), see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + template + friend std::ostream& write_chain (const Sparse_chain<_CT, _SF>& chain, std::ostream& out); + + /** + * @brief Write a sparse chain to a file using the `.osm` file format. + * + * Outputs a sparse chain (the chain can be reloaded using `read_chain`), see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + template + friend void write_chain (const Sparse_chain<_CT, _SF>& chain, std::string filename); + + + /** + * \brief Reads a sparse chain from an input stream using the `.osm` file format. + * + * Loads a sparse chain (the chain can be exported using `write_chain`), see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + template + friend std::istream& read_chain (Sparse_chain<_CT, _SF>& chain, std::istream& in); + + /** + * \brief Reads a sparse chain from a file using the `.osm` file format. + * + * Loads a sparse chain (the chain can be exported using `write_chain`), see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + template + friend void read_chain (Sparse_chain<_CT, _SF>& chain, std::string filename); + + /// @} + + /// \name Linear algebra operators + /// @{ + + /*! + * \brief Adds two chains. + * + * Adds `other` chain to current chain and returns the result in a new matrix. + * + * \pre Chains must have the same `Coefficient_ring` and the same `Storage_format`. + * + * \warning Will raise an error if the two chains are not the same `Coefficient_ring`. + * \warning Will raise a compilation error if the two chains don't have the same `Storage_format`. + * + * \param other The other chain. + * + * \return A new chain representing the result. + */ + Sparse_chain operator+(const Sparse_chain &other); + + /*! + * \brief Subtracts a chain from current chain. + * + * Subtracts `other` chain from current chain and returns the result in a new matrix. + * + * \pre Chains must have the same `Coefficient_ring` and the same `Storage_format`. + * + * \warning Will raise an error if the two chains are not the same `Coefficient_ring`. + * \warning Will raise a compilation error if the two chains don't have the same `Storage_format`. + * + * \param other The other chain. + * + * \return A new chain representing the result. + */ + Sparse_chain operator-(const Sparse_chain &other); + + /*! + * \brief Multiplies each coefficient of `chain` by `lambda`. + * + * \param lambda The factor to apply. + * \param chain The chain. + * + * \return A new chain representing the result. + */ + friend SparseChain operator*(const Coefficient_ring& lambda, const SparseChain &chain); + + /*! + * \brief Multiplies each coefficient of `chain` by `lambda`. + * + * \param lambda The factor to apply. + * + * \return A new chain representing the result. + */ + Sparse_chain operator*(const Coefficient_ring& lambda); + + /*! + * \brief Performs matrix multiplication between two chains (COLUMN x ROW) and return a COLUMN matrix. + * + * Generate a column-based matrix from the matrix multiplication and return it. + * + * \pre Chains must have the same `Coefficient_ring`. + * \pre `column.is_column()` and `row.is_row()` must be `true` + * + * \warning Will raise an error if chains do not have the same `Coefficient_ring`. + * + * \param column The column chain. + * \param row The row chain. + * + * \return The result of the matrix multiplication, column-based. + */ + friend Sparse_matrix_derived operator*(const SparseChain &column, const SparseChain &row); + + /*! + * \brief Performs matrix multiplication between two chains (COLUMN x ROW) and return a ROW matrix. + * + * Generate a row-based matrix from the matrix multiplication and return it. + * + * \pre Chains must have the same `Coefficient_ring`. + * \pre `column.is_column()` and `row.is_row()` must be `true` + * + * \warning Will raise an error if chains do not have the same `Coefficient_ring`. + * + * \param column The column chain. + * \param row The row chain. + * + * \return The result of the matrix multiplication, row-based. + */ + friend SparseMatrix operator%(const Sparse_chain &column, const Sparse_chain &row); + + /*! + * \brief Performs dot product between two chains (ROW x COLUMN). + * + * \pre Chains must have the same `Coefficient_ring`. + * \pre `row.is_row()` and `column.is_column()` must be `true` + * + * \warning Will raise an error if the chains do not have the same `Coefficient_ring`. + * + * \param row The row chain. + * \param column The column chain. + * + * \return The result of type `Coefficient_ring`. + */ + friend Coefficient_ring operator*(const Sparse_chain &row, const Sparse_chain &column); + + /*! + * \brief Adds a chain to `this`. + * + * Add a chain to `this`. + * + * \pre Chains must have the same `Coefficient_ring` and the same `Storage_format`. + * + * \warning Will raise an error if the two chains are not the same `Coefficient_ring`. + * \warning Will raise an error if the two chains don't have the same `Storage_format`. + * + * \param other The other chain. + * + * \return The modified chain representing the result. + */ + SparseChain& operator+=(const SparseChain &other); + + /*! + * \brief Sustracts a chain to `this`. + * + * Subtract a chain to `this`. + * + * \pre Chains must have the same `Coefficient_ring` and the same `Storage_format`. + * + * \warning Will raise an error if the two chains are not the same `Coefficient_ring`. + * \warning Will raise an error if the two chains don't have the same `Storage_format`. + * + * \param other The other chain. + * + * \return The modified chain representing the result. + */ + SparseChain& operator-=(const SparseChain &other); + + /*! + * \brief Multiplies each coefficient of `this` by `lambda`. + * + * If `lambda` is null, this function comes to nullify the chain. + * + * \param lambda The factor to apply. + * + * \return The modified chain representing the result. + */ + SparseChain& operator*=(const Coefficient_ring& lambda); + + /*! + * \brief Transposes a sparse chain. + * + * The result is a chain with `Storage_format` switched between COLUMN and ROW. + * + * \return A new chain where the `Storage_format` is changed. + */ + SparseChain transpose(); + + /// @} + + /// \name Test, access and modifications + /// @{ + + /*! + * \brief Checks if chain is a column. + */ + bool is_column() const; + + /*! + * \brief Checks if chain is a row. + */ + bool is_row() const; + + /*! + * \brief Checks if a coefficient is null (that is, not store in the sparse chain). + * + * \param index The index to check. + * \return `true` if the data at given index is null, `false`. + */ + bool is_null(size_t index) const; + + /*! + * \brief Checks if the chain is null. + * + * \return `true` if the chain is null (that is, contains no non zero coefficients), `false` otherwise. + */ + bool is_null() const; + + /*! + * \brief Tests if two chains are equal. + * + * If the storage formats of chains differ, return `false`, otherwise, tests if all coefficients are equal. + * + * \param chain First chain to compare. + * \param other Second chain to compare. + */ + friend bool operator==(const Sparse_chain& chain, const Sparse_chain& other); + + /*! + * \brief Gets the value of a coefficient index of the chain. + * + * \warning The chain will perform boundary check. + * + * \param index The coefficient index. + * + * \return The value of the coefficient. + */ + Coefficient_ring get_coefficient(size_t index) const ; + + /** + * \brief Sets coefficient index of the chain to value `d`. + * + * Sets the value of the coefficient in the chain at `index`. + * + * \warning The chain will perform boundary check. + * + * \param index The coefficient index. + * \param d Value of the coefficient. + */ + void set_coefficient(size_t index, Coefficient_ring d); + + /*! + * \brief Gets a subchain from the chain. + * + * Returns a copy of `chain` where all coefficients of indices provided in the vector are removed. + * + * \note Will return a copy of the chain if `indices` is empty. + * + * \param indices The indices to remove. + * + * \return A new chain representing the result. + */ + friend SparseChain operator/(const SparseChain &chain, const std::vector &indices); + + /*! + * \brief Gets a subchain from the chain. + * + * Returns a copy of `chain` where the coefficient at a given index is removed. + * + * \param index The index to remove. + */ + friend SparseChain operator/(const SparseChain &chain, size_t index); + + /*! + * \brief Restricts the chain to a sub-chain by removing indices. + * + * Removes all indices provided in the vector from the chain. Returns a reference to the modified chain. + * + * \note Will not alter the chain if `indices` is empty. + * + * \param indices The indices to remove. + * + * \return A reference to the modified chain. + */ + SparseChain& operator/=(const std::vector &indices); + + /** + * \brief Restricts the chain to a sub-chain by removing a given index. + * + * Removes the index provided from the chain. + * + * \param index The index to remove. + * + * \return A reference to the modified chain. + */ + SparseChain& operator/=(size_t index); + + /** + * \brief Removes all coefficients from the chain. + * + * The function comes to set all coefficients to zero. + */ + void nullify(); + /// @} +}; diff --git a/HDVF/doc/HDVF/Concepts/SparseMatrix.h b/HDVF/doc/HDVF/Concepts/SparseMatrix.h new file mode 100644 index 000000000000..aaf536e276d0 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/SparseMatrix.h @@ -0,0 +1,650 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `SparseMatrix` describes the requirements for sparse matrices optimized for topological computations. + Traditionally, sparse matrices data structures encode non zero coefficients of (sparse) matrices in order to optimize + either matrices memory footprint, or linear algebra operations (which usually comes to optimize iterators over non zero coefficients + and access to coefficients). However, topological operations require slightly different features: + + - fast access to row or columns of matrices (which are actually the images under the application encoded by the matrix) + - fast block operations (especially along row or columns) + + The `SparseMatrix` concept describes requirements for such sparse matrix. It relies on the model of `SparseChain` + which encodes sparse row or column vectors. Matrices are either column major or row major (hence they either store column sparse chains or row sparse chains). + + The following integer constants, called `Storage_format`, encode the major direction of both sparse chains and sparse matrices. + - `CGAL::OSM::COLUMN` for column-major chains and matrices (which is the default), + - `CGAL::OSM::ROW` for row-major chains and matrices. + + For instance, given the \f$5\times 4\f$ matrix: + \f[ + A = \left(\begin{array}{cccc} + 1 & \cdot & \cdot & \cdot \\ + -1 & \cdot & 2 & \cdot\\ + \cdot & \cdot & 1 & \cdot \\ + \cdot & \cdot & \cdot & \cdot \\ + \cdot & \cdot & \cdot & \cdot \\ + \end{array}\right) + \f] + where \f$\cdot\f$ means \f$0\f$. + - A column-major representation of \f$A\f$ is: \f$[0\mapsto c_0, 2\mapsto c_2]\f$ with the column-chains: \f$c_0 = [0\mapsto 1, 1\mapsto -1]\f$ and \f$c_2 = [1\mapsto 2, 2\mapsto 1]\f$. + - A row-major representation of \f$A\f$ is: \f$[0\mapsto c_0, 1\mapsto c_1, 2\mapsto c_2]\f$ with the row-chains: \f$c_0 = [0\mapsto 1]\f$ and \f$c_1 = [0\mapsto -1, 2\mapsto 2]\f$ and \f$c_2 = [2\mapsto 1]\f$. + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::OSM::Sparse_matrix`} + \cgalHasModelsBare{`CGAL::OSM::Sub_sparse_matrix`} + \cgalHasModelsEnd + + \sa `IntegralDomainWithoutDivision` + \sa `SparseChain` + */ + +class SparseMatrix { + +public: + /// \name Member data + /// @{ + + /*! + * \brief Matrix storage format (either `CGAL::OSM::ROW` or `CGAL::OSM::COLUMN`). + */ + int Storage_format; + + /// @} + + /// \name Types + /// @{ + + /*! + * \brief Type of coefficients stored in the matrix (a model of `IntegralDomainWithoutDivision`). + */ + typedef unspecified_type Coefficient_ring; + + /*! + * \brief Type of `SparseChain` used for storage. + */ + typedef unspecified_type Sparse_chain_type; + + /*! + * \brief A range of indices of non empty chains. + */ + typedef unspecified_type Non_zero_chain_indices; + + /*! + * \brief Type of the chains stored in the matrix. + */ + typedef unspecified_type Matrix_chain; + + + + + /// \name Creation, Filling + /// @{ + + /*! + * \brief Creates an empty new sparse matrix object. + * + * Default constructor, initialize an empty matrix of type `Storage_format` with coefficients of type `Coefficient_ring`. + * The default matrix size is 0x0. + */ + SparseMatrix() ; + + /*! + * \brief Creates a new sparse matrix object with given rows/columns sizes. + * + * Constructor with sizes, initialize an empty matrix of type `Storage_format` with coefficients of type `Coefficient_ring` and a given size along rows/columns. + */ + SparseMatrix(const size_t rowCount, const size_t columnCount) ; + + /** + * \brief Cleans a sparse matrix (set all coefficients to zero). + * + * Empties all structures of the sparse matrix. + * + */ + void nullify(); + + /** \brief Sets an identity matrix. + * + * \param nrows Number of rows of the matrix. + * \param ncols Number of columns of the matrix. + */ + void eye (size_t nrows, size_t ncols); + + /** \brief Sets an identity matrix. + * + * \param dimensions Pair containing the number of rows and number of columns of the matrix. + */ + void eye (std::pair dimensions); + + /// @} + + /// \name Matrix informations and iterators + /// @{ + + /** + * \brief Tests if a sparse matrix is null. + * + * The function returns `true` if the sparse matrix is null (technically, the sparse matrix is empty, and thus encodes a matrix in which all coefficients are zero) and `false` otherwise. + */ + bool is_null(); + + /** + * \brief Tests if a `Sparse_matrix` is empty. + * + * The function return `true` is the `Sparse_matrix` is empty (that is, if the number of columns or rows is null) and `false` otherwise. + */ + bool is_empty(); + + /** \brief Returns the storage format of the matrix. */ + int storage_format(); + /** + * \brief Gets the matrix size. + * + * The matrix size as a row/column pair. + */ + std::pair dimensions() const; + + /** + * \brief Iterator to the index of the first non null chain. + * + * Return an iterator to the index of the first non null chain (the iterator visits indices of non null chains along the major dimension of the matrix). + * + * \return The iterator to the index of the first non null chain. + */ + inline Non_zero_chain_indices::iterator begin() const noexcept; + + /** + * \brief Iterator past-the-end of chain indices. + * + * \return The iterator past-the-end of chain indices. + */ + inline Non_zero_chain_indices::iterator end() const noexcept; + + /** + * \brief Reverse iterator to the index of the last non null chain. + * + * Return a reverse iiterator to the index of the last non null chain (the iterator visits indices of non null chains, in decreading order, along the major dimension of the matrix). + * + * \return The reverse iterator to the index of the last non null chain. + */ + inline Non_zero_chain_indices::iterator reverse_begin() const noexcept; + + /** + * \brief Reverse iterator past-the-end of chain indices. + * + * \return The reverse iterator past-the-end of chain indices. + */ + inline Non_zero_chain_indices::iterator reverse_end() const noexcept; + + /// @} + + /// \name Input, output + /// @{ + + /** + * \brief Writes a sparse matrix in the output stream with a compact "user friendly" format. + * + * \param _stream The output stream. + * \param matrix The matrix to display. + * + * \return A reference to the modified stream. + */ + friend std::ostream& operator<<(std::ostream &_stream, const SparseMatrix &matrix); + + /** + * \brief Write a sparse matrix to an output stream using the `.osm` file format. + * + * Output a sparse matrix (the matrix can be reloaded using `read_matrix`), see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + friend std::ostream& write_matrix (const SparseMatrix& matrix, std::ostream& out); + + /** + * \brief Write a sparse matrix to a file using the `.osm` file format. + * + * Output a sparse matrix (the matrix can be reloaded using `read_matrix`), see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + friend std::ostream& write_matrix (const SparseMatrix& matrix, std::string filename); + + /** + * @brief Read a sparse matrix from an input stream using the `.osm` file format. + * + * Read a sparse matrix (the input must respect the `.osm` file format), .see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + + template + friend std::istream& read_matrix (SparseMatrix& matrix, std::istream& in); + + /** + * @brief Read a sparse matrix from a file using the `.osm` file format. + * + * Read a sparse matrix (the input must respect the `.osm` file format), .see \ref OSM_FILE_FORMAT "OSM file format documentation". + */ + + template + friend std::istream& read_matrix (SparseMatrix& matrix, std::string filename); + + /// @} + + /// \name Linear algebra operators + /// @{ + + /** + * \brief Perform addition of matrices and assign the result to `matrix`. + * + * Perform standard linear algebra addition. Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. The addition is optimized for each combination of `Storage_format`. + * + * @param matrix The first matrix. + * @param other The second matrix. + * @return A reference to the modified matrix `matrix` containing the result. + */ + friend SparseMatrix& operator+=(SparseMatrix &matrix, const SparseMatrix &other); + + /** + * \brief Perform addition of matrices and assign the result to `matrix`. + * + * Perform standard linear algebra addition. Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. The addition is optimized for each combination of `Storage_format`. + * + * @param first The first matrix. + * @param second The second matrix. + * @return A reference to the modified matrix `matrix` containing the result. + */ + friend SparseMatrix operator+(const SparseMatrix &first, const SparseMatrix &second); + + + /** + * \brief Subtracts two matrices into a new matrix. + * + * Subtracts each coefficient of the matrix `other` and returns a new matrix (of the same type as `matrix`) representing the result (when possible, prefer `-=` for efficiency). + * + * \pre Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. + * + * \warning Will raise an error if the other matrix is not the same `Coefficient_ring`. + * + * \param matrix The first matrix. + * \param other The second matrix. + * + * \return A new matrix representing the result. + */ + SparseMatrix& operator-=(SparseMatrix &matrix, const SparseMatrix &other); + + /** + * \brief Subtracts two matrices into a new matrix. + * + * Subtracts each coefficient of the matrix `other` and returns a new matrix (of the same type as `this`) representing the result (when possible, prefer `-=` for efficiency). + * + * \pre Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. + * + * \warning Will raise an error if the other matrix is not the same `Coefficient_ring`. + * + * @param first The first matrix. + * @param second The second matrix. + * + * \return A new matrix representing the result. + */ + friend SparseMatrix operator-(const SparseMatrix &first, const SparseMatrix &second); + + /** + * \brief Computes the negative of a matrix (unary operator). + * + * \return The resulting matrix. + */ + friend SparseMatrix operator-(const SparseMatrix& matrix); + + /** + * \brief Applies factor on each coefficients into a new matrix. + * + * This method creates a new matrix obtained by multiplying the matrix by a scalar factor `lambda`. + * + * If `lambda` is zero, the function comes to nullify the matrix (when possible, prefer `*=` for efficiency). + * + * \param lambda The factor to apply. + * \param matrix The matrix. + * + * \return A new matrix representing the result. + */ + friend SparseMatrix operator*(const Coefficient_ring& lambda, const SparseMatrix &matrix); + + /** + * \brief Applies factor on each coefficients into a new matrix. + * + * This method creates a new matrix obtained by multiplying the matrix by a scalar factor `lambda`. + * + * If `lambda` is zero, the function comes to nullify the matrix (when possible, prefer `*=` for efficiency). + * + * \param lambda The factor to apply. + * + * \return A new matrix representing the result. + */ + friend SparseMatrix operator*(const SparseMatrix &matrix, const Coefficient_ring& lambda); + + /** + * \brief Applies factor on each coefficient and assign. + * + * This method multiplies the matrix by a scalar factor `lambda`. + */ + SparseMatrix& operator*=(const Coefficient_ring& lambda); + + /** + * @brief Perform multiplication of matrices and assign the result to `matrix`. + * + * Perform standard linear algebra multiplication. Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. The multiplication is optimized for each combination of `Storage_format`. + * + * @param matrix The first matrix. + * @param other The second matrix. + * @return A reference to the modified matrix `matrix` containing the result. + */ + friend SparseMatrix& operator*=(SparseMatrix &matrix, const SparseMatrix &other); + + /** + * @brief Perform multiplication of matrices and return a new column-major matrix. + * + * Perform standard linear algebra multiplication of matrices and return a new column-major matrix (when possible, prefer `*=` for efficiency). Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. The multiplication is optimized (using standard definition or block multiplications) for each combination of `Storage_format`. However, efficiency depends on `Storage_format` (when possible, prefer row-major by column-major multiplications). + * + * @param first The first matrix. + * @param second The second matrix. + * @return The result of the matrix multiplication, column-based. + */ + friend SparseMatrix operator*(const SparseMatrix &first, const SparseMatrix &second); + + /** + * @brief Perform multiplication of matrices and return a new row-major matrix. + * + * Perform standard linear algebra multiplication of matrices and return a new row-major matrix (when possible, prefer `*=` for efficiency). Matrices must have the same `Coefficient_ring` but can have different `Storage_format`. The multiplication is optimized (using standard definition or block multiplications) for each combination of `Storage_format`. However, efficiency depends on `Storage_format`. + * + * @param first The first matrix. + * @param second The second matrix. + * @return The result of the matrix multiplication, row-based. + */ + friend SparseMatrix operator%(const SparseMatrix &first, const SparseMatrix &second); + + /** + * @brief Perform multiplication of a sparse matrix (column or row major) and a column-chain. The function returns a new column-major chain. + * + * Perform standard linear algebra multiplication of a matrix and a column-chain (ie.\ matrix / column vector multiplication) and returns a new column-major chain. Both arguments must have the same `Coefficient_ring` but the matrix can have any `Storage_format` (and the multiplication is optimized for each of them). + * + * @param matrix The matrix. + * @param column The column-major chain. + * @return The result of the matrix multiplication, column-based. + * + * \pre the matrix and the chain must have the same `Coefficient_ring`. + * \pre `column.is_column()` must be `true` + */ + friend SparseChain operator*(const SparseMatrix& matrix, const SparseChain& column); + + /** + * @brief Perform multiplication of a row-chain and a sparse matrix (column or row major). The function returns a new row-major chain. + * + * Perform standard linear algebra multiplication of a row-chain and a matrix (ie.\ row vector / matrix multiplication) and returns a new row-major chain. Both arguments must have the same `Coefficient_ring` but the matrix can have any `Storage_format` (and the multiplication is optimized for each of them). + * + * @param row The row-major chain. + * @param matrix The matrix. + * @return The result of the matrix multiplication, row-based. + * + * \pre the matrix and the chain must have the same `Coefficient_ring`. + * \pre `row.is_row()` must be `true` + */ + friend SparseChain operator*(const SparseChain& row, const SparseMatrix& matrix); + + /** + * \brief Transposes a matrix. + * + * \return A new matrix where the `Storage_format` has been swapped between COLUMN and ROW and data chains have been transposed. + */ + SparseMatrix transpose(); + + + /// @} + + /// \name Access and blocks operations + /// @{ + + /** + * \brief Gets the value of a chain from a const matrix. + * + * For COLUMN matrices, returns the `index`-th column, for ROW matrices, returns the `index`-th row. + * + * \warning The matrix will perform boundary check. + * + * \param index The chain index. + * + * \return The chain stored at given index. + */ + Matrix_chain operator[](size_t index) const; + + /** + * \brief Sets a given coefficient in `matrix`. + * + * Assign the scalar `d` to the coefficient on row `i` and column `j`. + * + * \warning The matrix will perform boundary check. + * + * \param matrix Reference on the matrix to modify. + * \param i The row index. + * \param j The column index. + * \param d The value. + */ + friend void set_coefficient(SparseMatrix& matrix, size_t i, size_t j, const Coefficient_ring d); + + /** + * \brief Gets a given coefficient. + * + * Returns the coefficient on row `i` and column `j` of the matrix. + * + * \warning The matrix will perform boundary check. + * + * \param matrix Constant reference on the matrix. + * \param i The row index. + * \param j The column index. + * + * \return The value of the given coefficient. + */ + friend Coefficient_ring get_coefficient(const SparseMatrix& matrix, size_t i, size_t j); + + /** + * @brief Gets the value of the column at a given `index` from the matrix (whatever the `Storage_format` of the matrix). + * + * For column-matrices, the operation comes to copy non zero coefficients of the column; for row-matrices a traversal of the matrix is required (in \f$\mathcal O(n)\f$). + * Whenever possible, prefer `cget_column()`. + * + * \warning The matrix will perform boundary check. + * + * @param matrix The matrix considered. + * @param index The coefficient index. + * @return The column at given index. + */ + friend SparseChain get_column(const SparseMatrix &matrix, size_t index); + + /** + * @brief Get sthe value of the row at a given `index` from the matrix (whatever the `Storage_format` of the matrix). + * + * For row-matrices, the operation comes to copy non zero coefficients of the row; for column-matrices a traversal of the matrix is required (in \f$\mathcal O(n)\f$). + * Whenever possible, prefer `cget_row()`. + * + * \warning The matrix will perform boundary check. + * + * @param matrix The matrix considered. + * @param index The coefficient index. + * @return The row at given index. + */ + friend SparseChain get_row(const SparseMatrix &matrix, size_t index); + + /** + * \brief Gets a const reference over a column from a column matrix. + * + * Constant time get. + * + * \warning The matrix will perform boundary check. + * + * \param matrix The matrix considered. + * \param index The column index. + * + * \return A constant reference over the column stored at given index. + * \pre `matrix.is_column()` must be `true`. + */ + const SparseChain& cget_column(const SparseMatrix& matrix, size_t index); + + /** + * \brief Gets a constant reference over a row from a row matrix + * + * Constant time get. + * + * \warning The matrix will perform boundary check. + * + * \param matrix The matrix considered. + * \param index The row index. + * + * \return A const reference over the row stored at given index. + * \pre `matrix.is_row()` must be `true`. + */ + const SparseChain& cget_row(const SparseMatrix& matrix, size_t index); + + /** + * \brief Set the value of the column at a given `index` from the matrix to `column` (whatever the `Storage_format` of the matrix). + * + * \note For column-matrices, the operation copies non zero coefficients of the `column` chain, for row-matrices a traversal of the matrix is required (in \f$\mathcal O(n)\f$). + * + * \warning The matrix will perform boundary check. + * + * @param matrix The matrix. + * @param index The column index. + * @param column The new column value. + * \pre `column.is_column()` must be `true` + */ + void set_column(SparseMatrix &matrix, size_t index, const SparseChain &column); + + /** + * @brief Set the value of the row at a given `index` from the matrix to `chain` (whatever the `StorageFormat` of the matrix). + * + * \note For row-matrices, the operation copies non zero coefficients of the `row` chain, for column-matrices a traversal of the matrix is required (in \f$\mathcal O(n)\f$). + * + * \warning The matrix will perform boundary check. + * + * @param matrix The matrix. + * @param index The row index. + * @param row The new row value. + * \pre `row.is_row()` must be `true` + */ + void set_row(SparseMatrix &matrix, size_t index, const SparseChain &row); + + /** + * @brief Build and return a new matrix by copying a `matrix` and removing the chain at a given `index`. The result is thus a block of the initial matrix. When possible, for efficiency, prefer `/=`. + * + * \note Will return a copy of the matrix if given vector is empty. + * + * \warning The matrix will perform boundary check. + * + * @param matrix The initial matrix. + * @param index The index of the chain to remove. + * + * @return A new matrix containing the result. + */ + friend SparseMatrix operator/(const SparseMatrix &matrix, size_t index); + + /** + * @brief Build and return a new matrix by copying a `matrix` and removing the chains with indices in the `indices` vector. The result is thus a block of the initial matrix. When possible, for efficiency, prefer `/=`. + * + * \note Will return a copy of the matrix if given vector is empty. + * + * \warning The matrix will perform boundary check. + * + * @param matrix The initial matrix. + * @param indices The indices of the chains to remove. + * + * @return A new matrix containing the result. + */ + friend SparseMatrix operator/(const SparseMatrix &matrix, const std::vector &indices); + + /** + * @brief Remove the chain at a given `index`. The result is thus a block of the initial matrix. + * + * \warning The matrix will perform boundary check. + * + * @param index The index of the chain to remove. + * + * @return A reference on the modified matrix. + */ + SparseMatrix& operator/=(size_t index); + + /** + * @brief Build and return a new matrix by copying a `matrix` and removing all chains with indices in the `indices` vector. The result is thus a block of the initial matrix. When possible, for efficiency, prefer `/=`. + * + * \note Will return a copy of the matrix if given vector is empty. + * + * \warning The matrix will perform boundary check. + * + * @param indices The indices of the chains to remove. + * + * @return A new matrix containing the result. + */ + SparseMatrix& operator/=(const std::vector &indices); + + /** + * \brief Removes a column from the matrix. + * + * Removes column of index `index` whatever the `Storage_format` of the matrix. For column matrices, it just comes to the `/=` operator and for row matrices, it entails a traversal of the matrix. + * + * \param matrix Reference on the matrix to modify. + * \param index The index to remove. + * + * \return A reference over the modified matrix. + */ + friend SparseMatrix& remove_column(SparseMatrix& matrix, size_t index); + + /** + * \brief Removes a row from the matrix. + * + * Removes row of index `index` whatever the `Storage_format` of the matrix. For row matrices, it just comes to the `/=` operator and for column matrices, it entails a traversal of the matrix. + * + * \param matrix Reference on the matrix to modify. + * \param index The index to remove. + * + * \return A reference over the modified matrix. + */ + friend SparseMatrix& remove_row(SparseMatrix& matrix, size_t index); + + /** + * \brief Removes a coefficient from the matrix. + * + * Removes coefficient at row `i` and column `j`. + * + * \param matrix Reference on the matrix to modify. + * \param i Index of the row + * \param j Index of the column + * + * \return The modified matrix representing the result. + */ + friend SparseMatrix& remove_coefficient(SparseMatrix& matrix, size_t i, size_t j); + + /** + * \brief Swaps columns in a matrix. + * + * Swaps columns at indices `i` and `j` whatever the `StorageFormat` of the matrix. For column matrices, the operation runs in constant time, while it entails a traversal of the matrix for row matrices. + * + * \param matrix Reference on the matrix to modify. + * \param i Index of the first column. + * \param j Index of the second column. + * + * \return A reference over the modified matrix. + */ + friend Sparse_matrix& swap_columns(Sparse_matrix& matrix, size_t i, size_t j); + + /** + * \brief Swaps rows in a matrix. + * + * Swaps rows at indices `i` and `j` whatever the `Storage_format` of the matrix. For row matrices, the operation runs in constant time, while it entails a traversal of the matrix for column matrices. + + \param matrix Reference on the matrix to modify. + \param i Index of the first row. + \param j Index of the second row. + + \return A reference over the modified matrix. + */ + friend Sparse_matrix& swap_rows(Sparse_matrix& matrix, size_t i, size_t j); + + + + /// @} + +}; diff --git a/HDVF/doc/HDVF/Concepts/SparseMatrixStructure.h b/HDVF/doc/HDVF/Concepts/SparseMatrixStructure.h new file mode 100644 index 000000000000..2a973b8a4ca1 --- /dev/null +++ b/HDVF/doc/HDVF/Concepts/SparseMatrixStructure.h @@ -0,0 +1,37 @@ +/*! + \ingroup PkgHDVFConcepts + \cgalConcept + + The concept `SparseMatrixStructure` describes the requirements for a class providing a model of `SparseChain` and an associated model of `SparseMatrix`. + + \cgalHasModelsBegin + \cgalHasModelsBare{`CGAL::OSM::Sparse_matrix_structure`} + \cgalHasModelsBare{`CGAL::OSM::Sub_sparse_matrix_structure`} + \cgalHasModelsEnd + + \sa `IntegralDomainWithoutDivision` + \sa `SparseMatrix` + \sa `SparseChain` + */ + +class SparseMatrixStructure { + +public: + /// \name Types + /// @{ + + /*! + * \brief Type of sparse chains used for storage (a model of `SparseChain`). + */ + typedef unspecified_type Sparse_chain_type; + + /** + * \brief Associated type of sparse matrices (a model of `SparseMatrix`). + * + * This model uses `Sparse_chain_type` as internal storage (thus `SparseMatrix::Sparse_chain_type` are `Sparse_chain_type` similar). + */ + typedef unspecified_type Sparse_matrix_type; + + /// @} + +}; diff --git a/HDVF/doc/HDVF/Doxyfile.in b/HDVF/doc/HDVF/Doxyfile.in new file mode 100644 index 000000000000..b750db4e6fc7 --- /dev/null +++ b/HDVF/doc/HDVF/Doxyfile.in @@ -0,0 +1,7 @@ +@INCLUDE = ${CGAL_DOC_PACKAGE_DEFAULTS} + +PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - Homological Discrete Vector Fields" + +EXTRACT_ALL = false +HIDE_UNDOC_MEMBERS = true +HIDE_UNDOC_CLASSES = true diff --git a/HDVF/doc/HDVF/HDVF.txt b/HDVF/doc/HDVF/HDVF.txt new file mode 100644 index 000000000000..8854c45cb010 --- /dev/null +++ b/HDVF/doc/HDVF/HDVF.txt @@ -0,0 +1,861 @@ +namespace CGAL { +/*! + +\mainpage User Manual +\anchor Chapter_HDVF + +\cgalAutoToc +\author Alexandra Bac + +Algebraic topology, introduced more than a century ago, studies topological spaces using algebraic tools derived from them (e.g. groups). Since the 70's and 80's, pioneering works (MacLane, Hilton, Munkres...) paved the way to computational homology, which studies the "holes" of an object. +In particular, Munkres introduced an algorithm computing Betti numbers based on the pseudo-diagonalization of boundary matrices (Smith Normal Form). + +In the 90's, persistent homology emerged independently through the work of H. Edelsbrunner et al. (North Carolina), P. Frosini et al. (Bologna), and V. Robins (Colorado), extending the study of homology to sequences of complexes (filtrations). +This later theory proved to have a wide range of applications in topological data analysis and hence quickly developed and spread in the scientific community. +In parallel, in the late 80's, R. Forman defined a discrete version of Morse theory for CW-complexes, while F. Sergeraert introduced effective homology theory. + +In [AGL, 2017], A. Gonzalez-Lorenzo et al. defined the **homological discrete vector field** (which we refer to as HDVF in the present package), a combinatorial object that generalizes previous approaches. +Precisely, HDVFs combine discrete Morse and effective homology theory and fully encode homology information (homology groups decomposition, Betti numbers, homology generators) for cellular complexes. +HDVFs encompass discrete Morse theory, effective homology and Smith Normal form, and: persistent homology can be computed with HDVFs (see [AGL, 2016]), relative homology and Alexander duality (see [AGL, 2025]). + +\cgalFigureBegin{HDVFtwirl,HDVF_twirl.png} +HDVF on a twirl model. **Left:** a perfect HDVF over the model; **Middle:** Homology generators associated to this HDVF; **Right:** Corresponding cohomology generators. +\cgalFigureEnd + +Unlike other main stream state of art approaches and librairies (largely based on persistent homology), HDVFs provide a framework for "geometric" homology and cohomology computation, where holes are not only enumerated, but also "localized" (through homology, cohomology generators, annotations...). + + + +\section secHDVFdefinitions Overview + +The HDVF package is dedicated to homology and cohomology computation for discrete objects and provides tools to compute: +- basic homological information (such as Betti numbers, ie.\ number of holes per dimension), +- but also geometric, higher level information (homology and cohomology generators, homology/cohomology annotation, cycles and co-cycles comparison...), +- moreover HDVFs compute persistent homology (and provide "persistent generators" information) as well as Alexander duality and relative homology. + +\section secHDVFHomology Homology/cohomology and related reminders + +Homology and cohomology are well know topological invariants which accounts for the holes of an object using commutative groups built from the object. In Mathematics, homology and cohomology can be defined for general Hausdorff topological spaces; we speak then about "singular homology and cohomology". However, such a general definition is not computable and thus, homology/cohomology computation requires a discretization of spaces into cells of various dimensions. Such a discretization is called a **complex**: +- **simplicial complexes** are intuitively meshes built from vertices, edges, triangles, tetrahedra... +- **cubical complexes** are built from vertices, edges, squares, cubes... +- **cellular complexes** are built from vertices, edges, polygons (without holes), polyhedra (without holes)... + +Actually, a set of cells is called a complex \f$K\f$ when: +- it is closed under the "face" operation (ie.\ all the faces of a cell of the complex also belong to the complex) +- intersections of cells are either empty of they are a proper cell of the complex +The dimension of \f$K\f$ is the largest dimension of its cells. + +\cgalFigureBegin{HDVFcomplexes,HDVF_cub_simpl_complex.png} +Illustration of complexes. **Left:** a simplicial complex of dimension 3; **Right:** A cubical complex of dimension 3. +\cgalFigureEnd + +In order to compute homology and cohomology, algebraic objects are built from such "geometric" complexes, namely chain complexes. + +\subsection secHDVFChainComplexes Chain complexes + +In order to define this algebraic structure, one first need to chose a **ring of coefficients** \f$A\f$ (\f$A\f$ is often taken to be \f$Z\f$ or a \f$\mathbb Z/p\mathbb Z\f$ field). + +Given a complex \f$K\f$ of dimension \f$d\f$, a **chain complex** \f$C\f$ is: + +\f\[C = \cdots C_{q+1} \xrightarrow{\partial_{q+1}} C_q \xrightarrow{\partial_{q}} C_{q-1} \xrightarrow{\partial_{q-1}} \cdots \to C_1 \xrightarrow{\partial_1} C_0 \xrightarrow{\partial_0 = 0} 0\f\] + + + +- a sequence of commutative groups, called **chain groups** \f$C_q\f$ for any \f$0\leqslant q \leqslant d\f$ defined as the formal linear combinations of cells with coefficients in \f$A\f$ (or \f$A\f$-module).
+Intuitively, elements of \f$C_q\f$, called **chains** are sums \f$\gamma = \sum_{i=1}^k\lambda_i\cdot \sigma_i\f$ with \f$\lambda_i\in A\f$ and \f$\sigma_i\f$ cells of dimension \f$q\f$, which can be imagined as a set of paths of oriented cells with multiplicity (see \cgalFigureRef{HDVFchain_complexes} where two 1-chains are represented). +- a sequence of morphisms between chain groups, called **boundary morphisms** \f$\partial_q\,:\, C_q\to C_{q-1}\f$ for any \f$0\leqslant q \leqslant d\f$, "mimicking" algebraically the geometric boundary and satisfying + +\f\[\partial_q\partial_{q+1} = 0\f\] + +Coming back to the example of \cgalFigureRef{HDVFchain_complexes}, as we will see in section \ref secHDVFsimplicial_chain_complex, the boundary of the edge \f$e_{45}\f$ is the 0-chain \f$v_5-v_4\f$ (ordering is given by vertices indices). Hence, on easily gets that \f$\partial_1(\gamma_1)=0\f$ and \f$\partial_1(\gamma_2)=v_8-v_4\f$, which is a very intuitive result. + +\cgalFigureBegin{HDVFchain_complexes,simplicial_complex_chains.png} +Examples of chains representation in a simplicial complex (with \f$A = \mathbb Z\f$). **Pink:** representation the 1-chain (of edges) \f$\gamma_1 = e_{24}+e_{46}-e_{26}\f$; **Blue:** representation of the 1-chain \f$\gamma_2 = e_{45}+e_{56}+e_{67}+e_{78}\f$. +\cgalFigureEnd + +The definition of the boundary operator depends on the underlying complex, it is detailed in next sections for simplicial and cubical complexes respectively. + +Notation + +Given a chain \f$\gamma\in C_q\f$ and \f$\sigma\f$ a \f$q\f$-cell, we denote by \f$\langle\gamma,\sigma\rangle\f$ the coefficient of \f$\sigma\f$ in \f$\gamma\in C_q\f$. + +\subsubsection secHDVFsimplicial_chain_complex Simplicial chain complexes + +An **abstract simplicial complex** of dimension \f$d\f$ is built from: +- a set of vertices identified by an index of type `std::size_t` (cells of dimension 0 or **0-simplices**) +- for any \f$q\f$ with \f$0< q\leqslant d\f$, cells of dimension \f$q\f$ (or **q-simplices**) are defined by \f$q+1\f$ vertices (denoted \f$\langle v_0,\ldots,v_{q}\rangle\f$ with vertices sorted by *increasing indices*). + +A **geometric simplicial complex** equips vertices with coordinates in \f$\mathbb R^q\f$ so that \f$q\f$-simplices are convex hulls of \f$q+1\f$ points. + +Given a simplicial complex (abstract or geometric) \f$K\f$, the associated chain complex \f$(C,\partial)\f$, called a **simplicial chain complex** is defined as follows: + +- The \f$q\f$-th chain group \f$C_q\f$ is the free \f$A\f$-module generated by \f$q\f$-cells described in the section \ref secHDVFChainComplexes. + +- The \f$q\f$-th boundary morphism is defined over the \f$q\f$-simplices (ie.\ the basis) by: + +\f\[\partial_q : \begin{array}{rcl} +C_q & \to & C_{q-1} \\ +\langle v_0,\ldots,v_{q}\rangle & \mapsto & \sum_{i=0}^q (-1)^i\cdot \langle v_0,\ldots,\hat{v_i}, \ldots, v_{q}\rangle \\ +\end{array} +\f\] +where \f$\hat{v_i}\f$ means that the \f$i\f$-th vertex has been discarded. + +For instance, in our previous example: +- For the edge \f$\langle 4,5\rangle\f$, we get \f$\partial_1(\langle 4,5\rangle) = \langle 5\rangle - \langle 4\rangle\f$ +- For the triangle \f$\langle 2,4,6\rangle\f$, we get \f$\partial_2(\langle 2,4,6\rangle) = \langle 4,6\rangle - \langle 2,6\rangle + \langle 2,4\rangle\f$ + +\subsubsection secHDVFcubical_chain_complex Cubical chain complexes + +A **cubical complex** of dimension \f$d\f$ is a set of cells defined as cartesian products: +\f\[\sigma = [n_1,n_1+\delta_1]\times\cdots \times [n_d,n_d+\delta_d]\f\] +with \f$n_i\in \mathbb N\f$ and \f$\delta_i\in\{0,1\}\f$ for \f$i=1\ldots d\f$. +The dimension of such a cell is \f$\sum_{i=1}^d \delta_i\f$. +As stated in previous section, a complex is closed under the "face" operation (if a cell belongs to the complex, so do its faces). + +**Khalimsky coordinates** associate to each cell, whatever its dimension, a unique coordinate in \f$\mathbb N^d\f$ defined as: +\f\[\mathrm{khal}(\sigma) = (2 n_1+\delta_1,\ldots, 2 n_d+\delta_d)\f\] +The dimension of a cell in this coordinate system is thus the number of odd coordinates. + +\cgalFigureBegin{HDVFcubical_complex_fig,cubical_complex.png} +A cubical complex of dimension 3. +\cgalFigureEnd + +For instance, in the example above: +- \f$v = [2,2] \times [1,1] \times [0,0]\f$ and \f$\mathrm{khal}(\sigma) = (4,2,0)\f$ (cell of dimension 0) +- \f$e = [0,1] \times [1,1] \times [0,0]\f$ and \f$\mathrm{khal}(\sigma) = (1,2,0)\f$ (cell of dimension 1) +- \f$f = [2,3] \times [0,1] \times [1,1]\f$ and \f$\mathrm{khal}(\sigma) = (5,1,2)\f$ (cell of dimension 2) + +Given a cubical complex \f$K\f$, the associated chain complex \f$(C,\partial)\f$, called a **cubical chain complex** is defined as follows: +- The \f$q\f$-th chain group \f$C_q\f$ is the free \f$A\f$-module generated by \f$q\f$-cells described in the section \ref secHDVFChainComplexes. +- The \f$q\f$-th boundary morphism is defined over Khalimsky coordinates of cells (ie.\ the basis) as follows: +\f\[\partial_q(x_1,\ldots,x_d) = \sum_{\substack{i=1\\i\text{ odd}}}^d (-1)^{i+1}\Big((x_1,\ldots, x_i+1,\ldots, x_d) - (x_1,\ldots, x_i-1,\ldots, x_d)\Big)\f] + +For instance, in the example above: +- \f$\partial(e) = (2,2,0)-(0,2,0)\f$ +- \f$\partial(f) = \big((6,1,2)-(4,1,2)\big) - \big((5,2,2)-(5,0,2)\big)\f$ + + + + + +\subsubsection secHDVFcochain Co-chain complex (for cohomology) + +In the "mathematical world", singular cohomology is defined on dual groups \f$C^q = C_q^* = \mathrm{Hom}(C_q,A)\f$ with coboundary morphisms: \f$\partial^q\,:\, C^q \to C^{q+1} = \partial_q^*\f$. + +However in our discrete setting of finite complexes, a space and its dual can be identified. Hence cohomology can be defined in a more intuitive way: + +\f\[C^* = \cdots C^{q+1} \xleftarrow{\partial^{q}} C^q \xleftarrow{\partial^{q-1}} C^{q-1} \leftarrow \cdots \leftarrow C^2 \xleftarrow{\partial^1} C^1 \xleftarrow{\partial^0} C^0\f\] + +- **cochain groups** \f$C^q\f$ for any \f$0\leqslant q \leqslant d\f$ are identified with \f$C_q\f$. Hence chains and cochains are similar. +- a sequence of morphisms between chain groups, called **coboundary morphisms** \f$\partial^q\,:\,C^q\to C^{q+1}\f$ for any \f$0\leqslant q \leqslant d\f$ with \f$\partial^q = \partial_q^t\f$ (thus, the matrix of the coboundary operator is the transpose of the boundary operator matrix). The coboundary operator "mimicks" algebraically the geometric coboundary (ie.\ cofaces) and satisfies: +\f\[\partial^q\partial^{q-1} = 0\f\] + +The following figure illustrates the coboundary morphism + +\cgalFigureBegin{HDVFcoboundary,simplicial_complex_coboundary.png} +Illustration of the coboundary morphism (with \f$A = \mathbb Z\f$) **Pink:** coboundary of vertex \f$v_6\f$ (\f$\partial^0(v_6) = e_{26} + e_{46}+e_{56} - e_{67} - e_{68}\f$); **Blue:** coboundary of edge \f$e_{24}\f$ (\f$\partial^1(e_{24} ) = f_{246} - f_{234}\f$). +\cgalFigureEnd + +\subsection secHDVFhomology Homology of discrete chain complexes + +Homology is a topological invariant characterizing the holes of an object of various dimension. What is a hole? +- holes of dimension 0 are connected components +- holes of dimension 1 are tunnels or handles of the object +- holes of dimension 2 are cavities + +\cgalFigureBegin{HDVFholes_example,holes_example.png} +Illustration of the holes of a double torus. **Black:** 1 connected component (dimension 0); **Red:** 4 holes of dimension 1; **Blue** 1 cavity of dimension 2. +\cgalFigureEnd + +Let us now come back to our simplicial complex example: +\cgalFigureBegin{HDVFsimplicial_cycles_example,simplicial_complex_homology.png} +Cycles on a simplicial complex. **Blue:** this cycle is the boundary of \f$f_{234}+f_{246}\f$; **Pink:** this cycle is not a boundary. +\cgalFigureEnd + +This example suggests that holes are delineated by *cycles which are not a boundaries*: +- **Cycles** of dimension \f$q\f$ are chains in the kernel of \f$\partial_q\f$: +\f\[Z_q(C) = \ker(\partial_q) \f\] +- **Boundaries** of dimension \f$q\f$ are chains in the image of \f$\partial_{q+1}\f$ +\f\[B_q(C) = \mathrm{Im}(\partial_{q+1}) \f\] +- As \f$\partial_{q}\partial_{q+1} = 0\f$, \f$B_q(C)\subseteq Z_q(C)\f$. +- Hence we can define the quotient \f$Z_q(C)/B_q(C)\f$ which contains exactly "cycles which are not boundaries", ie.\ the \f$q\f$th homology group: +\f\[H_q(C) = Z_q(C)/B_q(C) \f\] + +For finite complexes, these groups are finitely generated and we have the following decomposition (theorem of decomposition of finitely generated groups): +\f\[H_q(C) = A^{\beta_q}\oplus A/\lambda_1A\oplus \cdots \oplus A/\lambda_k A \f\] +where: +- \f$\beta_q\in\mathbb N\f$ is called the **\f$q\f$th Betti number** (giving the number of holes of dimension \f$q\f$); +- coefficients \f$\lambda_i\f$ are called the **torsion coefficients in dimension \f$q\f$** and satisfy \f$\lambda_i |Ā \lambda_{i+1}\f$. + +Let us assume homology groups have no torsion; homology can be computed "at different levels": +- **level 1:** Computing Betti numbers. +- **level 2:** Computing \f$\beta_q\f$ cycles generating \f$H_q(C)\f$ called **generators** (hence cycles "delineating" the holes). + +Most libraries focus on persistent homology computation for topological data analysis (see section \ref secHDVFpersistent_homology), which is a "level 1" homology computation. +The **HDVF package is dedicated to a "level 2" homology computation**, which is computationally more complex but provides "geometric" informations about holes (generators, annotations...). + +\subsection secHDVFcohomology Cohomology of discrete chain complexes + +Starting from a cochain complex: + +\f\[C^* = \cdots C^{q+1} \xleftarrow{\partial^{q}} C^q \xleftarrow{\partial^{q-1}} C^{q-1} \leftarrow \cdots \leftarrow C^2 \xleftarrow{\partial^1} C^1 \xleftarrow{\partial^0} C^0\f\] + +one can define: +- **Cocycles** of dimension \f$q\f$ are (co)chains in the kernel of \f$\partial^q\f$: +\f\[Z^q(C^*) = \ker(\partial^q) \f\] +- **Coboundaries** of dimension \f$q\f$ are chains in the image of \f$\partial^{q-1}\f$ +\f\[B^q(C^*) = \mathrm{Im}(\partial^{q-1}) \f\] +- As \f$\partial^{q}\partial^{q-1} = 0\f$, \f$B^q(C^*)\subseteq Z^q(C^*)\f$. +- Hence we can define the quotient \f$Z^q(C^*)/B^q(C^*)\f$, ie.\ the \f$q\f$th cohomology group: +\f\[H^q(C^*) = Z^q(C^*)/B^q(C^*) \f\] + +\cgalFigureBegin{HDVFex_hom_cohom,HDVF_ex_hom_cohom.png} +Illustration of homology and cohomology generators for a simple 2D cubical complex \f$C\f$ (computed with HDVFs). **Left:** both generators of \f$H_1(C)\f$; **Middle, right:** both cohomology generators \f$H^1(C)\f$ (for convenience, the dual of cohomology generators has been displayed with bulleted pink lines). +\cgalFigureEnd + +\cgalFigureBegin{HDVFex_hom_cohom_cofaces,HDVF_ex_hom_cohom_cofaces.png} +Cofaces representation of cohomology generators. +\cgalFigureEnd + +Figure \cgalFigureRef{HDVFex_hom_cohom} and \cgalFigureRef{HDVFex_hom_cohom_cofaces} illustrate homology and cohomology generators in a simple cubical complex. Actually: +- homology generators, intuitively delineate holes; +- cohomology generators provide cocycles to open or break the holes (therefore, it is sometimes more convenient to display the cofaces of cohomology generators as in figure \cgalFigureRef{HDVFex_hom_cohom_cofaces}). + +Therefore homology and cohomology provide isomorphic (and dual) results. Actually, the following proposition holds in finite dimension: +\f\[H^q(C^*) \sim H_q(C)\f\] + + +\subsection secHDVFcompHomology Computing homology and cohomology + +First results on the computation of discrete homology and cohomology date back to the 80's (see for instance [Munkres]). They are based on a diagonalisation of boundary matrices (called Smith Normal form). +Nowadays, computational (co)homology approaches can be classified into three branches illustrated in figure \cgalFigureRef{HDVFoverview_comput_hom}. + +\cgalFigureBegin{HDVFoverview_comput_hom,HDVFoverview_comput_hom.png} +Overview of approaches in computational homology. HDVFs are in-between discrete Morse theory and effective homology (and they encompass persistent homology, relative homology and tri-partitions). +\cgalFigureEnd + +\subsubsection secHDVFreduction Effective homology and reductions + +Effective homology considers homology computations from a categorical perspective ; its main results are theorems building functors providing a computation of homology for infinite spaces (locally effective spaces) starting from finite ones (effective spaces). + +Among the tools of effective homology, **reductions** characterise how to map a (large) chain complex onto a (small) one with isomorphic homology groups. Formally, a reduction from a chain complex \f$C\f$ to a chain complex \f$C'\f$ is a triple of graded maps \f$(f,g,h)\f$: +\f\[\begin{array}{cccccc} +\cdots & C_{q+1} & \substack{\xrightarrow{\partial_{q+1}}\\ \xleftarrow[h_{q}]{}} & C_{q} & \substack{\xrightarrow{\partial_{q}}\\ \xleftarrow[h_{q-1}]{}} & \cdots \\ +& f_{q+1}\Big\downarrow \Big\uparrow g_{q+1} & & f_{q}\Big\downarrow \Big\uparrow g_{q} & & \\ +\cdots & C'_{q+1} & \xrightarrow{\partial'_{q+1}} & C'_{q} & \xrightarrow{\partial'_{q}} & \cdots \\ +\end{array} +\f\] +such that: + +- \f$f\f$ and \f$g\f$ are chain maps (ie.\ commute with \f$\partial\f$ and \f$\partial'\f$) +- \f$f_q g_q = \mathrm{Id}_{C'_q}\f$ for any \f$q\f$ +- \f$g_q f_q = \mathrm{Id}_{C_q} - \partial_{q+1} h_q - h_q \partial_{q-1}\f$ for any \f$q\f$ +- \f$h_{q+1} h_q = 0\f$, \f$f_{q+1} h_{q} = 0\f$ and \f$h_{q} g_q = 0\f$ for any \f$q\f$ + + +\f$C'\f$ is called the **reduced complex** and \f$\partial'\f$ the **reduced boundary**. + +Under these assumptions, the homology and cohomology groups of \f$C\f$ and \f$C'\f$ are isomorphic. + +A reduction is **perfect** when the reduced boundary is null; then for any \f$q\f$: +\f\[H_q(C) \sim g(C'_q)\f\] +in other words, homology groups are isomorphic to the image of the reduced complex by \f$g\f$, +\f\[H^q(C) \sim f^*(C'_q)\f\] +and cohomology groups are isomorphic to the image of the reduced complex by \f$f^*\f$. + +\subsubsection secHDVFdiscrete_Morse A few words about discrete Morse theory + +Discrete Morse theory was introduced by Forman in the 90's as a combinatorial approach for homology computation directly inspired by Morse theory (on compact manifolds). +He showed that in the discrete context, Morse functions are better defined through their gradient: a vector field defined over the complex connecting cells and one of their co-faces. In this context, gradients of Morse functions can be easily characterized by acyclicity conditions. + +More precisely, given a complex \f$K\f$, a **discrete gradient vector field** (called DGVF here after) can be built as follows: +- consider the Hasse diagram of the complex (connecting cells to their boundary - figure (left)) + +\subsection secHDVFpersist_hom Persistent homology + +\subsubsection secHDVFfiltr_file_format The ".filtr" file format + +The %FILTR file format is a very simple text format encoding filtrations: + +- Size of the filtration +- For each cell in the filtration: + time -> (cell index - dim q , degree) + + +\subsection secHDVFrelative_hom Relative homology + +\subsection secHDVFAlexander Alexander duality + +\subsubsection secHDVFTetgen Tetgen version + +\section secHDVF The HDVF framework + +This section presents a brief introduction to HDVFs, see [AGL, 2016] for more details. + +\subsection secHDVFHDVF_definitions Definition of HDVFs + +HDVFs are an "in-between" theory: +- in-between Discrete Morse theory (combinatorial approach) and effective homology (categorical approach) +- in-between combinatorics and algebra +but they cannot be reduced to any of these frameworks. As we will see, they provide a bridge in-between them. + +Let \f$K\f$ be a complex (simplicial, cubical...) and let \f$(\mathscr C, \partial)\f$ be its associated chain complex. + +**Notation** Starting from now on, we will use \f$\mathscr C\f$ for chain complexes and \f$C\f$ for critical cells. + +Formally a **HDVF** \f$X=(P,S,C)\f$ is a partition of the cells of \f$K\f$: +\f\[K = P\cup S\cup C\f\] +with \f$P\f$ primary cells, \f$S\f$ secondary cells and \f$C\f$ critical cells, such that the sub-matrix: +\f\[\partial (S)|_P\text{ is invertible.} +\f\] +where \f$\partial (S)|_P\f$ means that the matrix of \f$\partial\f$ is restricted to columns of secondary cells and rows of primary cells. + +As stated earlier, this definition is both combinatorial (partition of cells) and algebraic (invertibility of a sub-matrix) but it cannot be simplified or reduced to either of these approaches. + +Given such an HDVF \f$X=(P,S,C)\f$, we can define the following associated reduction \f$(f,g,h)\f$: + +\f\[\begin{array}{cccccc} +\cdots & \mathscr C_{q+1} & \substack{\xrightarrow{\partial_{q+1}}\\ \xleftarrow[h_{q}]{}} & \mathscr C_{q} & \substack{\xrightarrow{\partial_{q}}\\ \xleftarrow[h_{q-1}]{}} & \cdots \\ +& f_{q+1}\Big\downarrow \Big\uparrow g_{q+1} & & f_{q}\Big\downarrow \Big\uparrow g_{q} & & \\ +\cdots & C_{q+1} & \xrightarrow{\mathrm d_{q+1}} & C_{q} & \xrightarrow{\mathrm d_{q}} & \cdots \\ +\end{array} +\f\] +\f$\mathrm d\f$ is the **reduced boundary** (over critical cells) and the reduction is defined as follows: + +- the matrix of \f$h_q\f$ is: +\f\[\begin{array}{c} +\ \ \ \ \ \ \ \ \ \ P_q \ \ \ \ S_q \ \ \ \ C_q \\ +\begin{array}{c} +P_{q+1} \\ +S_{q+1} \\ +C_{q+1} \\ +\end{array} +\begin{array}{|c|c|c|} +\hline +0 & 0 & 0 \\ +\hline +H_q & 0 & 0 \\ +\hline +0 & 0 & 0 \\ +\hline +\end{array} +\end{array} +\f\] +with \f\[H_q = (\partial (S_{q+1})|_{P_q})^{-1}\f\]. + +- the matrix of \f$f_q\f$ is: +\f\[\begin{array}{c} +\ \ \ \ \ \ P_q \ \ \ \ S_q \ \ \ \ C_q \\ +\begin{array}{c} +C_q \\ +\end{array} +\begin{array}{|c|c|c|} +\hline +F_q & 0 & \mathrm{Id} \\ +\hline +\end{array} +\end{array} +\f\] +with +\f\[ F_q = - \partial(S_{q+1})|_{C_q}\cdot H_q\f\] + +- the matrix of \f$g_q\f$ is: +\f\[\begin{array}{c} +\ \ \ \ \ \ C_q \\ +\begin{array}{c} +P_q \\ +S_q \\ +C_q \\ +\end{array} +\begin{array}{|c|} +\hline +0 \\ +\hline +G_q \\ +\hline +\mathrm{Id} \\ +\hline +\end{array} +\end{array} +\f\] +with +\f\[ G_q = - H_{q-1} \cdot \partial(C_{q})|_{P_{q-1}}\f\] + +- last, the matrix of the reduced boundary \f$\mathrm d_q\f$ is: +\f\[\begin{array}{c} +\ \ \ \ \ \ C_q \\ +\begin{array}{c} +C_{q-1} \\ +\end{array} +\begin{array}{|c|} +\hline +DD_q \\ +\hline +\end{array} +\end{array} +\f\] +with +\f\[ D_q = \partial(C_{q})|_{C_{q-1}} + F_{q-1} \cdot \partial(C_{q})|_{P_{q-1}} = \partial(C_{q})|_{C_{q-1}} + \partial(S_{q})|_{C_{q-1}} \cdot G_q \f\] + +Therefore the HDVF "reduces" the complex to a smaller complex over critical cells. + +A HDVF is called **perfect** when the reduced boundary is null, then for any \f$q \in \{0\ldots d\}\f$: +- \f$\beta_q = | C_q |\f$ +- Homology generators of dimension \f$q\f$ are \f$\{g(\sigma) \,;\, \sigma\in C_q \}\f$ +- Cohomology generators of dimension \f$q\f$ are \f$\{f^*(\sigma) \,;\, \sigma\in C_q \}\f$ + +Hence a perfect HDVF computes homology and provides a rich homological information. +- when \f$A\f$ is a field, perfect HDVFs always exist and any HDVF can be completed into a perfect HDVF (see \ref secHDVFcomput) +- when \f$A\f$ is a ring: +- if the torsion part is null, we hypothesise that a perfect HDVF always exist +- with torsion, we hypothesise that a torsion-perfect HDVF always exist (where the reduced boundary is limited to torsion) +Both questions are still an open and we have no counter-examples. + +Moreover, the reduction provides various informations on cycles and co-cycles: +- An **annotation** associates to a cycle its decomposition in the basis of homology generators, up to boundaries:
+Given \f$\gamma\in Z_q(\mathscr C)\f$, \f$f(\gamma)\f$ is an annotation of \f$\gamma\f$: +\f\[\text{if }f(\gamma) = \sum_i \alpha_i\cdot \sigma_i,\ \ \ \sigma_i\in C_q\f\] +\f\[\text{then }\ \ \ \gamma = \sum_i \alpha_i\cdot g(\sigma_i) + \beta,\ \ \ \beta \in B_q(\mathscr C)\f\] +- A **co-annotation** associates to a cycle its decomposition in the basis of cohomology generators, up to coboundaries:
+Given \f$\gamma\in Z^q(\mathscr C)\f$, \f$g^*(\gamma)\f$ is a co-annotation of \f$\gamma\f$: +\f\[\text{if }g^*(\gamma) = \sum_i \alpha_i\cdot \sigma_i,\ \ \ \sigma_i\in C^q\f\] +\f\[\text{then }\ \ \ \gamma = \sum_i \alpha_i\cdot f^*(\sigma_i) + \beta,\ \ \ \beta \in B^q(\mathscr C)\f\] + +Annotations and co-annotations provide convenient tests to decide if to cycles (resp. co-cycles) belong to the same homology (resp. cohomology) class: +- Two cycles \f$\gamma\f$ and \f$\gamma'\f$ belong to the same homology class if they have the same annotation (ie.\ \f$f(\gamma) = f(\gamma')\f$) +- Two co-cycles \f$\gamma\f$ and \f$\gamma'\f$ belong to the same cohomology class if they have the same co-annotation (ie.\ \f$g^*(\gamma) = g^*(\gamma')\f$) + +Figure \cgalFigureRef{HDVFex_hom_cohom} shows an example of HDVF together with corresponding homology and cohomology generators. In all the package, we use the following colors convention: +- PRIMARY cells are depicted in green +- SECONDARY cells are depicted in red +- CRITICAL cells are depicted in blue + +Let us point out that HDVFs can be represented in a "discrete Morse theory" style, with arrows (ie.\ a vector field) pairing a primary and a secondary cell among its co-faces (the vector field associated to a HDVF is not unique). But DGVF are a strict subset of HDVFs: +- each DGVF is a HDVF (labelling sources of arrows as PRIMARY, targets of arrows as SECONDARY, unpaired cells as CRITICAL always produces an HDVF) +- but HDVF may produce vector fields with cycles + +The following figures show an HDVF and an associated vector field:
+ + + +And the following figure exhibits the vector field representation of a HDVF with a cycle (on the right, the cycle is highlighted in purple):
+ +
+Hence this vector field is a valid HDVF (\f$\partial(S)|_P\f$ is invertible) but it is not a DGVF. + +\subsection secHDVFcomput Computing and deforming a HDVF + +HDVFs can be computed using previous matrix expression; however, Aldo Gonzalez-Lorenzo has shown that several operations on HDVF can be introduced, not only to built, but also transform HDVFs. + +- **A operation** (pairing of two critical cells)
+Given a HDVF \f$X=(P,S,C)\f$, and two CRITICAL cells \f$\gamma_1\in C_q\f$, \f$\gamma_2\in C_{q+1}\f$ +\f\[ A(X) = \Big(P\cup\{\gamma_1\}, S\cup\{\gamma_2\}, C\backslash \{\gamma_1, \gamma_2\}\Big)\f\] +and the operation is valid (ie.\ produces an HDVF) if and only if: +\f\[ \langle \mathrm d(\gamma_2),\gamma_1\rangle \text{ is invertible}\f\] + +- Any perfect HDVF can be built by iterating the A operation (with complexity at most \f$\mathscr O(n^3))\f$) +- This operation can also complete a HDVF into a perfect HDVF + +Figures below illustrate this operation on a cubical chain complex: (left) an HDVF such that \f$(\gamma_1,\gamma_2)\f$ meet the A condition, (right) the HDVF obtained after \f$A(\gamma_1,\gamma_2)\f$. + + + + +- **R operation** (unpairing of two critical cells)
+Given a HDVF \f$X=(P,S,C)\f$, a PRIMARY cell \f$\pi\in P_q\f$ and a SECONDARY cells \f$\sigma\in S_{q+1}\f$ +\f\[ R(X) = \Big(P\backslash\{\pi\}, S\backslash\{\sigma\}, C\cup \{\pi, \sigma\}\Big)\f\] +and the operation is valid (ie.\ produces an HDVF) if and only if: +\f\[ \langle h(\pi),\sigma\rangle \text{ is invertible}\f\] + +Operations A and R are inverse one another. + +- **M operation** (exchange a PRIMARY and a CRITICAL cell)
+Given a HDVF \f$X=(P,S,C)\f$, a PRIMARY cell \f$\pi\in P_q\f$ and a CRITICAL cell \f$\gamma_1\in C_{q}\f$ +\f\[ M(X) = \Big(P\backslash \{\pi\} \cup\{\gamma\}, S, C\backslash \{\gamma\}\cup \{\pi\}\Big)\f\] +and the operation is valid (ie.\ produces an HDVF) if and only if: +\f\[ \langle \mathrm f(\pi),\gamma\rangle \text{ is invertible}\f\] + +The M operation modifies the homology generator associated to \f$\gamma\f$ (while preserving is associated cohomology generator). +The following figure illustrates the M operation. + +\cgalFigureBegin{HDVF_M,HDVF_M.png} +M operation on a cubical complex \f$K\f$. **Left:** perfect HDVF over \f$K\f$ with cells \f$(\pi, \gamma)\f$ meeting the M operation condition (blue: homology generator associated with \f$\gamma\f$, pink: cohomology generator associated with \f$\gamma\f$); **Right:** perfect HDVF obtained with \f$M(\pi,\gamma)\f$ and updated generators (associated to \f$\pi\f$ which is now critical). +\cgalFigureEnd + +- **W operation** (exchange a SECONDARY and a CRITICAL cell)
+Given a HDVF \f$X=(P,S,C)\f$, a SECONDARY cell \f$\sigma\in S_q\f$ and a CRITICAL cell \f$\gamma_1\in C_{q}\f$ +\f\[ W(X) = \Big(P, S\backslash\{\sigma\}\cup \{\gamma\}, C\backslash \{\gamma\}\cup \{\sigma\}\Big)\f\] +and the operation is valid (ie.\ produces an HDVF) if and only if: +\f\[ \langle \mathrm g(\gamma),\sigma\rangle \text{ is invertible}\f\] + +The W operation modifies the cohomology generator associated to \f$\gamma\f$ (while preserving is associated homology generator). +The following figure illustrates the W operation. + +\cgalFigureBegin{HDVF_W,HDVF_W.png} +W operation on a cubical complex \f$K\f$. **Left:** perfect HDVF over \f$K\f$ with cells \f$(\sigma, \gamma)\f$ meeting the W operation condition (blue: homology generator associated with \f$\gamma\f$, pink: cohomology generator associated with \f$\gamma\f$); **Right:** perfect HDVF obtained with \f$W(\pi,\gamma)\f$ and updated generators (associated to \f$\sigma\f$ which is now critical). +\cgalFigureEnd + +- **MW operation** (exchange a PRIMARY and a SECONDARY cell)
+Given a HDVF \f$X=(P,S,C)\f$, a PRIMARY cell \f$\pi\in P_q\f$ and a SECONDARY cell \f$\sigma\in S_{q}\f$ +\f\[ MW(X) = \Big(P\backslash\{\pi\}\cup \{\sigma\}, S\backslash\{\sigma\}\cup \{\pi\}, C\Big)\f\] +and the operation is valid (ie.\ produces an HDVF) if and only if: +\f\[ \langle \partial h(\pi),\sigma\rangle \text{ and } \langle h\partial(\sigma),\pi\rangle\text{ are invertible}\f\] + +The MW operation modifies both homology and cohomology generators. +The following figure illustrates the W operation. + +\cgalFigureBegin{HDVF_MW,HDVF_MW.png} +MW operation on a cubical complex \f$K\f$. **Top:** perfect HDVF over \f$K\f$ with cells \f$(\pi_1, \sigma_1)\f$ and \f$(\pi_2, \sigma_2)\f$ meeting the MW operation condition (blue: homology generators, pink: cohomology generators); **Down, left:** perfect HDVF obtained with \f$MW(\pi_1,\sigma_1)\f$ and updated generators (a cohomology generator is modified); **Down, right:** perfect HDVF obtained with \f$MW(\pi_2,\sigma_2)\f$ and updated generators (a homology generator is modified). +\cgalFigureEnd + +Recent works by Yann-Situ Gazull [YSG1], [YSG2] proved that (when \f$A\f$ is a field): +- the space of perfect HDVFs is connected under M, W and MW operations (see [YSG2]) +- homology bases computed by HDVFs and other computational approaches (Smith Normal Form, persistent homology, tripartitions...) can be precisely characterised as **explicit basis** (see [YSG1]) +- any perfect HDVF can be adapted in time \f$\mathscr O(kn^2)\f$ to compute a free set of generators of size \f$k\f$ (e.g. a given basis) + + +Hence, using appropriate combinations of such operations, one can change a HDVF until corresponding homology or cohomology generators meet a given basis or delineate a hole. + +Let us consider the following simple cubical complex and a perfect HDVF (top) together with the three corresponding homology generators (bottom, highlighted in pink): + +
+ + + + +A W operation between cells of Khalimsky coordinates \f$\sigma = (3,2)\f$ (`CRITICAL`) and \f$\tau=(5,2)\f$ (`SECONDARY`) produces the following HDVF (homology generators did not change; note that cohomology generators are modified): + + + + + + + +Then, a MW operation between cells of Khalimsky coordinates \f$\sigma' = (4,1)\f$ (`PRIMARY`) and \f$\tau=(3,2)\f$ (`SECONDARY`) produces the following HDVF where homology generators become "minimal": + + + + + + + + +\subsection secHDVFpersistent_homology Persistent homology + +\subsection secHDVFreduced_Alexander Relative homology and Alexander duality + +\section secHDVFHDVFpackage The HDVF package + +This section describes the HDVF package implementing the theory described in previous sections. + +In order to compute the homology/cohomology of a discrete object with an HDVF, the process is as follows: +-# Define the ring of coefficients type +-# Build a chain complex from one of the models of `AbstractChainComplex` (`Geometric_simplicial_chain_complex`, `Abstract_simplicial_chain_complex` or `Cubical_chain_complex`) +-# Build a HDVF from this chain complex +-# Compute a perfect HDVF (fast mode or random mode) + +\cgalFigureBegin{HDVFoverview_HDVF,HDVFoverview_HDVF.png} +Overview of homology/cohomology computation with the HDVF package. +\cgalFigureEnd + +\subsection secHDVFCoefficientType Defining the ring of coefficients + +The ring of coefficients must be a model of the `IntegralDomainWithoutDivision` concept: +- the ring \f$\mathbb Z\f$ +\code +typedef int CoefficientType ; +\endcode +- a field \f$\mathbb Z/p\mathbb Z\f$ +\code +#include +typedef Zp

CoefficientType ; +\endcode +with \f$p\f$ a given prime number. +- the field \f$\mathbb Z/2\mathbb Z\f$ (the class`Zp` can also be used, but `Z2` is optimized for \f$p=2\f$) +\code +#include +typedef Z2 CoefficientType ; +\endcode + +\subsection secHDVFChainComplex Building a chain complex + +The HDVF package provides several chain complexes: `Abstract_simplicial_chain_complex`, `Geometric_simplicial_chain_complex` and `Cubical_chain_complex`. + +In order to build such a chain complex, discrete objects are first imported into a temporary combinatorial structure storing input cells. The chain complex is then built from this intermediate structure by adding all the faces of cells and computing the corresponding boundary matrix. + +Let us point out that whatever the type of chain complex, cells are indexed along each dimension; hence a cell is identified by an index and a dimension. + +- `Abstract_simplicial_chain_complex` (see section \ref secHDVFsimplicial_chain_complex) are built using the `Mesh_object_io` intermediate class from abstract simplices (vertices have no coordinates). Therefore, such complexes are usually computed from `.simp` files (a simple file format enumerating on each line vertices of simplices, see section secHDVFsimpFile): +\code +using Complex = Abstract_simplicial_chain_complex ; +Mesh_object_io mesh ; +mesh.read_simp(filename); +Complex complex(mesh); +\endcode +- `Simplicial_chain_complex` are also built using the `Mesh_object_io` intermediate structure to import mesh files (`.off` format): +\code +using Complex = Simplicial_chain_complex ; +Mesh_object_io mesh ; +mesh.read_off(filename); +Complex complex(mesh); +\endcode +- `Cubical_chain_complex` are built using the `Cub_object_io` intermediate structure by importing either simple `.cub` files (enumerating Khalimsky coordinates of cells, see section \ref secHDVFcubFile) or `.pgm` files. +- For `.cub` files enumerating Khalimsky coordinates, cells are imported and their faces are added to produce a proper complex. +\code +using Complex = Cubical_chain_complex ; +Cub_object_io mesh ; +mesh.read_cub(filename, true); +Complex complex(mesh); +\endcode +- For `.pgm` files (ie.\ for binary images or volumes), two constructions are available for cubical complexes: +- PRIMAL, encoding \f$3^d-1\f$-adjacency (where cells are imported as such, see figure \cgalFigureRef{HDVF_primal_dual}, middle) +\code +using Complex = Cubical_chain_complex ; +Cub_object_io mesh ; +// Read pgm file (store Khalimsky coordinates) +mesh.read_pgm(filename, true); +// Build the PRIMAL complex associated +Complex complex(mesh, CGAL::Homological_discrete_vector_field::Complex::PRIMAL); +\endcode +- DUAL, encoding \f$2d\f$-adjacency (where cubes produce vertices... see figure \cgalFigureRef{HDVF_primal_dual} right) +\code +using Complex = Cubical_chain_complex ; +Cub_object_io mesh ; +// Read pgm file (store voxel coordinates) +mesh.read_pgm(filename, false); +// Build the DUAL complex associated +Complex complex(mesh, CGAL::Homological_discrete_vector_field::Complex::DUAL); +\endcode + +\cgalFigureBegin{HDVF_primal_dual,HDVF_primal_dual.png} +Primal and dual constructions of cubical chain complexes from a binary volume. **Left:** a 3D binary volume; **Middle:** PRIMAL cubical (chain) complex associated to the volume; **Right:** DUAL cubical (chain) complex associated to the volume).
+Figure taken from [AGL, 2016]. +\cgalFigureEnd + +\subsubsection secHDVFsimpFile The %SIMP file format\anchor SIMP_FILE_FORMAT + +The %SIMP file format is a very simple text format listing, encoding simplicial complexes. It enumerates simplices line by line (described by the indices of their vertices). +The file doesn't need to provide an exhaustive enumeration of simplices (with all their faces), indeed, the simplicial chain complex constructors fill them with faces. Figure \cgalFigureRef{HDVF_simp_file} illustrate the format on a simple simplicial complex. + +\cgalFigureBegin{HDVF_simp_file,HDVF_simp_file.png} +Example of `simp` file. **Left:** a simplicial complex; **Right:** associated `simp` files describing maximal simplices. +\cgalFigureEnd + +\subsubsection secHDVFcubFile The %CUB file format\anchor CUB_FILE_FORMAT + +The %CUB file format is a very simple text format encoding binary volumes: +- first line: dimension +- second line: size of Khalimsky coordinates of the bounding box along each axis +- following lines: list of cells in Khalimsky coordinates + +The file doesn't need to provide an exhaustive enumeration of cells (with all their faces), indeed, the cubical chain complex constructors fill them with faces. Figure \cgalFigureRef{HDVF_cub_file} illustrate the format on a simple cubical complex. + +\cgalFigureBegin{HDVF_cub_file,HDVF_cub_file.png} +Example of `cub` file. **Left:** a cubical complex; **Right:** associated `cub` files describing maximal cells. +\cgalFigureEnd + +\subsection secHDVFbuild Building and computing a HDVF + +An empty HDVF is built from a chain complex: +\code +using HDVF_type = Hdvf ; +HDVF_type hdvf(complex, OPT_FULL) ; +\endcode + +Several HDVF options are available: +- `OPT_D`: fastest option - compute only the reduced boundary \f$\mathrm d\f$ (under this option, HDVFs provide only low level homological information: Betti numbers and critical cells +- `OPT_F`, `OPT_G`: compute the reduction partially (only \f$f\f$ for cohomology generators or \f$g\f$ for homology generators) +- `OPT_FULL`: compute the full reduction + +\subsubsection secHDVFcomputePerfect Computing a perfect HDVF automatically + +The HDVF obtained is empty (all cells are critical). In order to compute homology/cohomology that is a perfect HDVF, the package provides two methods: +- `compute_perfect_hdvf()` (fastest method: cells are paired using the A operation by decreasing dimension using the first valid pair) +- `compute_rand_perfect_hdvf()` (slowest: compute all valid pairs for the A operation are select one randomly) + +\code +hdvf.compute_perfect_hdvf(); +\endcode +or +\code +hdvf.compute_rand_perfect_hdvf(); +\endcode + +When the ring of coefficients is a field, these methods always produce a perfect HDVF. However for \f$\mathbb Z\f$-(co)homology (without torsion), the existence of complexes admitting no perfect HDVF is still an open question (and the answer is negative for "reasonable" objects, indeed we still have no counter example). +The method `is_perfect_hdvf()` checks if a HDVF is perfect. + +\subsubsection secHDVFcomputePerfectA Computing a perfect HDVF with A operations + +For specific applications, HDVFs can be built by iterating the A operation: +\code +hdvf.A(id1, id2, q) ; +\endcode +performs a pairing between the cells \f$\gamma_1\f$ (index `id1` in dimension `q`) and \f$\gamma_2\f$ (index `id2` in dimension `q+1`). + +The package provide a family of methods to search for valid pairings: +\code +Cell_pair pair; +std::vector pairs; +bool found; + +// Search for the first valid pair in dimension `q` +pair = find_pair_A(q, found); +// Search for the first cell (of dimension `q-1` or `q+1`) forming a valid pair with tau (index `id`, dimension `q`) +pair = find_pair_A(q, id, found); +// Search for all valid pairs in dimension `q` +pairs = find_pairs_A(q, found); +// Search for all cells (of dimension `q-1` or `q+1`) forming a valid pair with tau (index `id`, dimension `q`) +pairs = find_pairs_A(q, id, found); +\endcode + +\subsection secHDVFoutput Extracting and saving homological/cohomological information + +\warning All `vtk` exports are only available for geometric chain complexes (i.e. `Simplicial_chain_complex` and `Cubical_chain_complex`). + +How to extract (co)homological information then? Various solutions and output are available: +- **HDVF extraction / visualization**: +The method `psc_labels()` extracts cells flags in any dimensions. The complex annotated with labels can then be exported to `vtk` for visualization. The `vtk` output includes a `Label` property encoding the HDVF PSC_flag (-1 for PRIMARY, 1 for SECONDARY and 0 for CRITICAL cells), and a `CellId` property storing cells indices: +\code +// Labels extraction +vector > labels = hdvf.psc_labels() ; +// VTK export +Complex::chain_complex_to_vtk(complex, filename, &labels) ; +\endcode +- **Betti numbers**: +For a perfect HDVF, the Betti number in dimension \f$q\f$ equals \f$|C_q|\f$, the number of critical cells. +Critical cells can be obtained: +- either in all dimensions: +\code +std::vector > criticals = hdvf.psc_flag(CGAL::Homological_discrete_vector_field::CRITICAL); +\endcode +- or in a given dimension `q`: +\code +std::vector criticals_q = hdvf.psc_flag(CGAL::Homological_discrete_vector_field::CRITICAL, q) ; +\endcode +- **Homology/cohomology generators** +Each critical cell \f$\gamma\f$ bears an associated homology generator: \f$g(\gamma)\f$ and cohomology generator: \f$f^*(\gamma)\f$. +- Generators associated to \f$\gamma\f$ (index `id`, dimension `q`) can be extracted as column chains (ie.\ column `Sparse_chain`): +\code +HDVF_type::Column_chain hom_generator(hdvf.homology_chain(id, q)) ; +HDVF_type::Column_chain cohom_generator(hdvf.cohomology_chain(id, q)) ; +\endcode +These chains can be visited as described in section \ref secHDVFmatrixChain. +- The HDVF package also provides a vtk exporter for such chains (cells with a non zero coefficient are exported). For instance: +\code +Complex::chain_to_vtk(complex, filename, hom_generator, q, id) ; +\endcode +If `id` is provided, a `Label` integer property is added to the vtk file (0 for the cell `id`, 2 for other cells). +- **Reduction maps** +Matrices of the reduction (\f$F\f$, \f$G\f$, \f$H\f$ and \f$D\f$) can be also obtained using `matrix_f()`, `matrix_g()`, `matrix_h()` and `matrix_dd()`. +- **Automated vtk export** +In order to ease `vtk` export, an "automated" method `CGAL::IO::write_VTK()` is also provided, exporting in distinct files, the HDVF flags and all the generators computed (homology / cohomology depending of the HDVF option): +\code +CGAL::IO::write_VTK(hdvf, complex, outfile_root, co_faces) ; +\endcode +where `outfile_root` is the root of the `.vtk` file names and `co_faces` is a %Boolean (if `true`, cohomology generators are represented through their cofaces). +- HDVFs and reductions can also be saved / loaded to `.hdvf` files (a simple text file format described in section \ref secHDVFHDVF_format) with `insert_hdvf_reduction()` and `extract_hdvf_reduction()`. + +\subsubsection secHDVFHDVF_format The %HDVF file format + +The %HDVF file format is a simple text format used to save HDVFs. It relies on the %OSM format described in section \ref secHDVFOSM_format. + +\warning The file format does not store the underlying complex, therefore the dimension and the number of cells in each dimension are used to check the coherence of the `.hdvf` file with the underlying complex provided by the user. + +- HDVF prefix ("#HDVF_core", "#HDVF_persistence" or "#HDVF_duality") +- HDVF type (0 for HDVF and reduction, 1 for HDVF only) +- Dimension of the complex +- List of the number of cells in each dimension (one dimension by row) +- HDVF option (OPT_BND | OPT_F | OPT_G | OPT_FULL) +- List of the flags of cells in each dimension (one dimension by row: -1 for PRIMARY, 1 for SECONDARY, 0 for CRITICAL) +- According to HDVF option: output F matrices in each dimension (`.osm` format) +- According to HDVF option: output G matrices in each dimension (`.osm` format) +- Output H matrices in each dimension (`.osm` format) +- Output DD matrices in each dimension (`.osm` format) + +\subsection secHDVFexHDVF HDVF examples + +The following examples exhibit the computation of a perfect HDVF over a simplicial (resp. cubical) chain complex built from a `.off` file (resp. a `.pgm` file). Generators are then exported to `.vtk`. +\cgalExample{HDVF/hdvf_simplicial.cpp} +\cgalExample{HDVF/hdvf_cubical.cpp} + +\subsection secHDVFmatrixChain A few words about sparse matrices and chains + +All maps (boundary operator, \f$f\f$, \f$g\f$, \f$h\f$ and \f$\mathrm d\f$) are stored in sparse matrices and chains are stored in sparse chains. +The `OSM` library provides an implementation of the `SparseMatrix` and `SparseChain` concepts, that is sparse structures optimized for topological operations (block operations). + +The structure is based on a mapped matrix implementation, hence, matrices and chains are either column or row major (`CGAL::OSM::COLUMN` or `CGAL::OSM::ROW` type). + +In order to speed up both block operations and iterators (over non empty columns or row according to the type), a **bitboard** structure is used (inherited from chess programming community). Hence, in order to iterate over the major dimension of a matrix, one should actually iterate a bitboard (providing indices of non empty columns / rows). See `Sparse_matrix_structure` documentation for more details. + +Iterating over sparse chains them comes to iterate over a map. + +The following example builds a column major matrix, iterate over its non empty columns (ie.\ chains) and then iterate over each chain. + +\cgalExample{HDVF/matrix_chain.cpp} + +All standard linear algebra operators are provided (and optimized according to the matrix/chain types). + +\subsubsection secHDVFOSM_format The OSM file format\anchor OSM_FILE_FORMAT + +The `.osm` file format is a simple text format to store OSM sparse matrices: +- Matrix type (0 for COLUMN, 1 for ROW) +- Number of rows and number of columns (separated by a space) +- Number of coefficients +- List of coefficients (one by row):
+`id_row id_col value` + +\subsection secHDVFoperations HDVF operations + +\subsection secHDVFannotations HDVFs and annotations + +\subsection secHDVFpersistentHom_package HDVFs and persistent homology + +\subsection secHDVFAlexander_package HDVFs, Alexander duality and relative homology + + + +[AGL, 2017] Aldo Gonzalez-Lorenzo, Alexandra Bac, Jean-Luc Mari, Pedro Real. Allowing cycles in discrete Morse theory, Topology and its Applications, Volume 228, 2017, Pages 1-35. + +[AGL, 2016] Aldo Gonzalez-Lorenzo. Computational Homology Applied to Discrete Objects. Discrete Mathematics [PhD]. Aix-Marseille Université; Universidad de Sevilla, 2016. + +[AGL, 2025] Aldo Gonzalez-Lorenzo, Alexandra Bac and Yann-Situ Gazull. A constructive approach of Alexander duality. J Appl. and Comput. Topology 9, 2 (2025). + +[YSG1, 2025] Yann-Situ Gazull, Aldo Gonzalez-Lorenzo and Alexandra Bac. Characterization of the computed homology and cohomology bases. DGMM 2025 (to appear). + +[YSG2, 2025] Yann-Situ Gazull, Aldo-Gonzalez-Lorenzo and Alexandra Bac. Space of homological computations: connectivity and paths (submitted to Journal of Applied and Computational Topology). + +[Munkres] J. Munkres. Elements of Algebraic Topology, Addison Wesley Publishing Company, (1984) + +*/ +} /* namespace CGAL */ diff --git a/HDVF/doc/HDVF/PackageDescription.txt b/HDVF/doc/HDVF/PackageDescription.txt new file mode 100644 index 000000000000..4971550c3c27 --- /dev/null +++ b/HDVF/doc/HDVF/PackageDescription.txt @@ -0,0 +1,96 @@ +// PRETTY HDVF should equal the project title in Doxyfile.in + +/// \defgroup PkgHDVFRef Homological Discrete Vector Fields Reference +/// \defgroup PkgHDVFConcepts Concepts +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFAlgorithmClasses Sparse Matrices and Vectors Classes +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFTraitsClasses Traits Classes +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFChainComplexesClasses Chain Complexes Classes +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFHDVFClasses HDVFs Classes +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFFiltrationsClasses Filtrations Classes +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFToolsClasses Tools Classes and Functions +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFDualityClasses Duality Classes +/// \ingroup PkgHDVFRef + +/// \defgroup PkgHDVFIOClasses IO Classes and Functions +/// \ingroup PkgHDVFRef + + +/*! +\addtogroup PkgHDVFRef +\cgalPkgDescriptionBegin{Homological Discrete Vector Fields, PkgHDVF} +\cgalPkgPicture{HDVF-small.png} + +\cgalPkgSummaryBegin +\cgalPkgAuthors{Alexandra Bac} +\cgalPkgDesc{ +The package provides an implementation of Homological Discrete %Vector Fields, a framework computing homology and cohomology information (Betti numbers, generators, annotation functions...) for discrete objects (including persistent homology, Alexander duality isomorphism and reduced homology).} +\cgalPkgManuals{Chapter_HDVF,PkgHDVFRef} +\cgalPkgSummaryEnd + +\cgalPkgShortInfoBegin +\cgalPkgSince{6.2} +\cgalPkgBib{cgal:b-hdvf} +\cgalPkgLicense{\ref licensesGPL "GPL"} + +\cgalPkgShortInfoEnd + +\cgalPkgDescriptionEnd + +\cgalClassifedRefPages + +\cgalCRPSection{Concepts} +- `AbstractChainComplex` +- `GeometricChainComplex` +- `SparseChain` +- `SparseMatrix` +- `HDVF` +- `HDVFTraits` +- `Filtration` + +\cgalCRPSection{Classes} +- `CGAL::Homological_discrete_vector_field::Abstract_simplicial_chain_complex` +- `CGAL::Homological_discrete_vector_field::Simplicial_chain_complex` +- `CGAL::Homological_discrete_vector_field::Simplex` +- `CGAL::Homological_discrete_vector_field::Cubical_chain_complex` +- `CGAL::Homological_discrete_vector_field::Hdvf_core` +- `CGAL::Homological_discrete_vector_field::Hdvf` +- `CGAL::Homological_discrete_vector_field::Hdvf_persistence` +- `CGAL::Homological_discrete_vector_field::Hdvf_duality` +- `CGAL::Homological_discrete_vector_field::Hdvf_traits_2` +- `CGAL::Homological_discrete_vector_field::Hdvf_traits_3` +- `CGAL::Homological_discrete_vector_field::Hdvf_traits_d` +- `CGAL::Homological_discrete_vector_field::Filtration_core` +- `CGAL::Homological_discrete_vector_field::Filtration_lower_star` +- `CGAL::Homological_discrete_vector_field::Hdvf_tools` +- `CGAL::Homological_discrete_vector_field::Geometric_chain_complex_tools` +- `CGAL::Homological_discrete_vector_field::Mesh_object_io` +- `CGAL::Homological_discrete_vector_field::Icosphere_object_io` +- `CGAL::Homological_discrete_vector_field::Surface_mesh_io` +- `CGAL::Homological_discrete_vector_field::Tet_object_io` +- `CGAL::Homological_discrete_vector_field::Triangulation_3_io` +- `CGAL::Homological_discrete_vector_field::Mesh_object_io` +- `CGAL::Homological_discrete_vector_field::Sub_chain_complex_mask` +- `CGAL::Homological_discrete_vector_field::Sub_sparse_matrix` +- `CGAL::Homological_discrete_vector_field::Sub_sparse_matrix_strcture` +- `CGAL::OSM::OSM` +- `CGAL::OSM::Sparse_matrix` +- `CGAL::OSM::Sparse_matrix_structure` +- `CGAL::OSM::Sparse_chain` +- `CGAL::OSM::Bitboard` +- `CGAL::OSM::Full_lu` + +*/ diff --git a/HDVF/doc/HDVF/dependencies b/HDVF/doc/HDVF/dependencies new file mode 100644 index 000000000000..8005911abb6e --- /dev/null +++ b/HDVF/doc/HDVF/dependencies @@ -0,0 +1,12 @@ +Manual +Kernel_23 +Kernel_d +STL_Extension +Algebraic_foundations +Number_types +Circulator +Stream_support +Surface_mesh +Polygon +Polygon_mesh_processing +Property_map diff --git a/HDVF/doc/HDVF/examples.txt b/HDVF/doc/HDVF/examples.txt new file mode 100644 index 000000000000..c559a068b8cd --- /dev/null +++ b/HDVF/doc/HDVF/examples.txt @@ -0,0 +1,9 @@ +/*! +\example HDVF/main_dual_hdvf.cpp +\example HDVF/main_hdvf.cpp +\example HDVF/main_per_hdvf.cpp +\example HDVF/hdvf_simplicial.cpp +\example HDVF/hdvf_surface_mesh_simplicial.cpp +\example HDVF/hdvf_cubical.cpp +\example HDVF/matrix_chain.cpp +*/ \ No newline at end of file diff --git a/HDVF/doc/HDVF/fig/HDVF-small.png b/HDVF/doc/HDVF/fig/HDVF-small.png new file mode 100644 index 000000000000..096b04a15da2 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF-small.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_M.png b/HDVF/doc/HDVF/fig/HDVF_M.png new file mode 100644 index 000000000000..612a66959c2e Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_M.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_MW.png b/HDVF/doc/HDVF/fig/HDVF_MW.png new file mode 100644 index 000000000000..34ba63cd9016 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_MW.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_W.png b/HDVF/doc/HDVF/fig/HDVF_W.png new file mode 100644 index 000000000000..66ca07bb7960 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_W.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_annot_1cycle_mod.png b/HDVF/doc/HDVF/fig/HDVF_annot_1cycle_mod.png new file mode 100644 index 000000000000..238c52ac0922 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_annot_1cycle_mod.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_annot_3cycles.png b/HDVF/doc/HDVF/fig/HDVF_annot_3cycles.png new file mode 100644 index 000000000000..1ee83e094a03 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_annot_3cycles.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_annot_hom1.png b/HDVF/doc/HDVF/fig/HDVF_annot_hom1.png new file mode 100644 index 000000000000..ff41d7ec20df Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_annot_hom1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_annotations.png b/HDVF/doc/HDVF/fig/HDVF_annotations.png new file mode 100644 index 000000000000..f368d28c9073 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_annotations.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_cub_file.png b/HDVF/doc/HDVF/fig/HDVF_cub_file.png new file mode 100644 index 000000000000..7512e7beb6d9 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_cub_file.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_cub_simpl_complex.png b/HDVF/doc/HDVF/fig/HDVF_cub_simpl_complex.png new file mode 100644 index 000000000000..4496f12b9c87 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_cub_simpl_complex.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_cycle.png b/HDVF/doc/HDVF/fig/HDVF_cycle.png new file mode 100644 index 000000000000..083915cf12b3 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_cycle.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_cycle_highlighted.png b/HDVF/doc/HDVF/fig/HDVF_cycle_highlighted.png new file mode 100644 index 000000000000..07d261754259 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_cycle_highlighted.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_dtorus_all.png b/HDVF/doc/HDVF/fig/HDVF_dtorus_all.png new file mode 100644 index 000000000000..22d5a9119f24 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_dtorus_all.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom1.png b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom1.png new file mode 100644 index 000000000000..7c5560bc23e9 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom1_co.png b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom1_co.png new file mode 100644 index 000000000000..0af00cd7e44c Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom1_co.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom2.png b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom2.png new file mode 100644 index 000000000000..94cd4b0e0bfb Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom2_co.png b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom2_co.png new file mode 100644 index 000000000000..c49853033714 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_dtorus_cohom2_co.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_dtorus_homs.png b/HDVF/doc/HDVF/fig/HDVF_dtorus_homs.png new file mode 100644 index 000000000000..3d33bb190046 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_dtorus_homs.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_eight_view1.png b/HDVF/doc/HDVF/fig/HDVF_eight_view1.png new file mode 100644 index 000000000000..a4bee8a122e6 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_eight_view1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_eight_view2.png b/HDVF/doc/HDVF/fig/HDVF_eight_view2.png new file mode 100644 index 000000000000..18224e91458c Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_eight_view2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1.png b/HDVF/doc/HDVF/fig/HDVF_ex1.png new file mode 100644 index 000000000000..d0d8ae4e2638 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A1.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A1.png new file mode 100644 index 000000000000..dbf88490a9f9 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A1_arrow.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A1_arrow.png new file mode 100644 index 000000000000..33d6261aa20f Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A1_arrow.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A2.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A2.png new file mode 100644 index 000000000000..29936d296b82 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A2_A.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A2_A.png new file mode 100644 index 000000000000..6703235b689e Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A2_A.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A2_arrow.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A2_arrow.png new file mode 100644 index 000000000000..84cd4fcf1d17 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A2_arrow.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A3.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A3.png new file mode 100644 index 000000000000..a83599ebc321 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A3.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A3_DGVF.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A3_DGVF.png new file mode 100644 index 000000000000..5145af54f276 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A3_DGVF.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_A3_arrow.png b/HDVF/doc/HDVF/fig/HDVF_ex1_A3_arrow.png new file mode 100644 index 000000000000..bd09c40c620f Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_A3_arrow.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect.png new file mode 100644 index 000000000000..f26356c134a0 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom1.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom1.png new file mode 100644 index 000000000000..ffc6a4d2e785 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom2.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom2.png new file mode 100644 index 000000000000..ef0b65f1acfe Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom3.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom3.png new file mode 100644 index 000000000000..ec30b3f1fde3 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_cohom3.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom1.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom1.png new file mode 100644 index 000000000000..a006fd9733c3 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom2.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom2.png new file mode 100644 index 000000000000..a92010d6c528 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom3.png b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom3.png new file mode 100644 index 000000000000..ce6d3e72d5c4 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex1_perfect_hom3.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex_hom_cohom.png b/HDVF/doc/HDVF/fig/HDVF_ex_hom_cohom.png new file mode 100644 index 000000000000..5a2b7444589a Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex_hom_cohom.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_ex_hom_cohom_cofaces.png b/HDVF/doc/HDVF/fig/HDVF_ex_hom_cohom_cofaces.png new file mode 100644 index 000000000000..d2509a580683 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_ex_hom_cohom_cofaces.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op1.png b/HDVF/doc/HDVF/fig/HDVF_op1.png new file mode 100644 index 000000000000..93a378d719ca Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op1_g1.png b/HDVF/doc/HDVF/fig/HDVF_op1_g1.png new file mode 100644 index 000000000000..aee8104cebae Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op1_g1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op1_g2.png b/HDVF/doc/HDVF/fig/HDVF_op1_g2.png new file mode 100644 index 000000000000..0ebebdcaa680 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op1_g2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op1_g3.png b/HDVF/doc/HDVF/fig/HDVF_op1_g3.png new file mode 100644 index 000000000000..3808e1de135d Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op1_g3.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op2_W7_11.png b/HDVF/doc/HDVF/fig/HDVF_op2_W7_11.png new file mode 100644 index 000000000000..93af4f12afa8 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op2_W7_11.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op2_g1.png b/HDVF/doc/HDVF/fig/HDVF_op2_g1.png new file mode 100644 index 000000000000..4302c9628c4d Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op2_g1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op2_g2.png b/HDVF/doc/HDVF/fig/HDVF_op2_g2.png new file mode 100644 index 000000000000..bec7e0d13b4e Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op2_g2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op2_g3.png b/HDVF/doc/HDVF/fig/HDVF_op2_g3.png new file mode 100644 index 000000000000..78ce05b4a870 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op2_g3.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op3_MW5_7.png b/HDVF/doc/HDVF/fig/HDVF_op3_MW5_7.png new file mode 100644 index 000000000000..54e5b5d17ab1 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op3_MW5_7.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op3_g1.png b/HDVF/doc/HDVF/fig/HDVF_op3_g1.png new file mode 100644 index 000000000000..b6bfcd2b7eed Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op3_g1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op3_g2.png b/HDVF/doc/HDVF/fig/HDVF_op3_g2.png new file mode 100644 index 000000000000..cdf151c93395 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op3_g2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_op3_g3.png b/HDVF/doc/HDVF/fig/HDVF_op3_g3.png new file mode 100644 index 000000000000..d5c5d8ef7e9b Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_op3_g3.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_primal_dual.png b/HDVF/doc/HDVF/fig/HDVF_primal_dual.png new file mode 100644 index 000000000000..e5072fd82ff9 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_primal_dual.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_primal_dual_ex.png b/HDVF/doc/HDVF/fig/HDVF_primal_dual_ex.png new file mode 100644 index 000000000000..b45b9ce40b4b Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_primal_dual_ex.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_simp_file.png b/HDVF/doc/HDVF/fig/HDVF_simp_file.png new file mode 100644 index 000000000000..32715bdad118 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_simp_file.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_twirl.png b/HDVF/doc/HDVF/fig/HDVF_twirl.png new file mode 100644 index 000000000000..852519a0b9d7 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_twirl.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_twirl_quartet.png b/HDVF/doc/HDVF/fig/HDVF_twirl_quartet.png new file mode 100644 index 000000000000..ba49ac23cf24 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_twirl_quartet.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_twirl_view1.png b/HDVF/doc/HDVF/fig/HDVF_twirl_view1.png new file mode 100644 index 000000000000..a9ce6b90aac7 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_twirl_view1.png differ diff --git a/HDVF/doc/HDVF/fig/HDVF_twirl_view2.png b/HDVF/doc/HDVF/fig/HDVF_twirl_view2.png new file mode 100644 index 000000000000..e936efe10595 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVF_twirl_view2.png differ diff --git a/HDVF/doc/HDVF/fig/HDVFoverview_HDVF.png b/HDVF/doc/HDVF/fig/HDVFoverview_HDVF.png new file mode 100644 index 000000000000..1fc1da43db50 Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVFoverview_HDVF.png differ diff --git a/HDVF/doc/HDVF/fig/HDVFoverview_comput_hom.png b/HDVF/doc/HDVF/fig/HDVFoverview_comput_hom.png new file mode 100644 index 000000000000..f78ccc508a0b Binary files /dev/null and b/HDVF/doc/HDVF/fig/HDVFoverview_comput_hom.png differ diff --git a/HDVF/doc/HDVF/fig/Sparse_matrix_example_col.pdf b/HDVF/doc/HDVF/fig/Sparse_matrix_example_col.pdf new file mode 100644 index 000000000000..ff6befb8d97c Binary files /dev/null and b/HDVF/doc/HDVF/fig/Sparse_matrix_example_col.pdf differ diff --git a/HDVF/doc/HDVF/fig/Sparse_matrix_example_row.pdf b/HDVF/doc/HDVF/fig/Sparse_matrix_example_row.pdf new file mode 100644 index 000000000000..bc69bc24d4ba Binary files /dev/null and b/HDVF/doc/HDVF/fig/Sparse_matrix_example_row.pdf differ diff --git a/HDVF/doc/HDVF/fig/cubical_complex.png b/HDVF/doc/HDVF/fig/cubical_complex.png new file mode 100644 index 000000000000..4992f3ad4bba Binary files /dev/null and b/HDVF/doc/HDVF/fig/cubical_complex.png differ diff --git a/HDVF/doc/HDVF/fig/cubical_implementation.png b/HDVF/doc/HDVF/fig/cubical_implementation.png new file mode 100644 index 000000000000..b3066e386ff4 Binary files /dev/null and b/HDVF/doc/HDVF/fig/cubical_implementation.png differ diff --git a/HDVF/doc/HDVF/fig/holes_example.png b/HDVF/doc/HDVF/fig/holes_example.png new file mode 100644 index 000000000000..3e17d413df75 Binary files /dev/null and b/HDVF/doc/HDVF/fig/holes_example.png differ diff --git a/HDVF/doc/HDVF/fig/lower_star_filtration_y.png b/HDVF/doc/HDVF/fig/lower_star_filtration_y.png new file mode 100644 index 000000000000..8a7ddb91850a Binary files /dev/null and b/HDVF/doc/HDVF/fig/lower_star_filtration_y.png differ diff --git a/HDVF/doc/HDVF/fig/lower_star_filtration_z.png b/HDVF/doc/HDVF/fig/lower_star_filtration_z.png new file mode 100644 index 000000000000..e971e109083b Binary files /dev/null and b/HDVF/doc/HDVF/fig/lower_star_filtration_z.png differ diff --git a/HDVF/doc/HDVF/fig/mesh_object_io_bbox.png b/HDVF/doc/HDVF/fig/mesh_object_io_bbox.png new file mode 100644 index 000000000000..d0e4db47bb8f Binary files /dev/null and b/HDVF/doc/HDVF/fig/mesh_object_io_bbox.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_2generator1.png b/HDVF/doc/HDVF/fig/persistence_2generator1.png new file mode 100644 index 000000000000..c7325b75f0d4 Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_2generator1.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_after1.png b/HDVF/doc/HDVF/fig/persistence_after1.png new file mode 100644 index 000000000000..3ca88a0ed900 Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_after1.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_cycles1.png b/HDVF/doc/HDVF/fig/persistence_cycles1.png new file mode 100644 index 000000000000..a5b0a7b5b5cf Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_cycles1.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_hole2.png b/HDVF/doc/HDVF/fig/persistence_hole2.png new file mode 100644 index 000000000000..117f2345f975 Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_hole2.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_hole3.png b/HDVF/doc/HDVF/fig/persistence_hole3.png new file mode 100644 index 000000000000..12048909ceab Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_hole3.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_hole4.png b/HDVF/doc/HDVF/fig/persistence_hole4.png new file mode 100644 index 000000000000..82b5612c7b88 Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_hole4.png differ diff --git a/HDVF/doc/HDVF/fig/persistence_hole_inf.png b/HDVF/doc/HDVF/fig/persistence_hole_inf.png new file mode 100644 index 000000000000..58cc5616ab73 Binary files /dev/null and b/HDVF/doc/HDVF/fig/persistence_hole_inf.png differ diff --git a/HDVF/doc/HDVF/fig/primal_dual.png b/HDVF/doc/HDVF/fig/primal_dual.png new file mode 100644 index 000000000000..4f53c4d946b6 Binary files /dev/null and b/HDVF/doc/HDVF/fig/primal_dual.png differ diff --git a/HDVF/doc/HDVF/fig/simplicial_complex.png b/HDVF/doc/HDVF/fig/simplicial_complex.png new file mode 100644 index 000000000000..345bacc6313c Binary files /dev/null and b/HDVF/doc/HDVF/fig/simplicial_complex.png differ diff --git a/HDVF/doc/HDVF/fig/simplicial_complex_chains.png b/HDVF/doc/HDVF/fig/simplicial_complex_chains.png new file mode 100644 index 000000000000..35bf07d5820e Binary files /dev/null and b/HDVF/doc/HDVF/fig/simplicial_complex_chains.png differ diff --git a/HDVF/doc/HDVF/fig/simplicial_complex_coboundary.png b/HDVF/doc/HDVF/fig/simplicial_complex_coboundary.png new file mode 100644 index 000000000000..be2248eb43fd Binary files /dev/null and b/HDVF/doc/HDVF/fig/simplicial_complex_coboundary.png differ diff --git a/HDVF/doc/HDVF/fig/simplicial_complex_homology.png b/HDVF/doc/HDVF/fig/simplicial_complex_homology.png new file mode 100644 index 000000000000..95293ec9746c Binary files /dev/null and b/HDVF/doc/HDVF/fig/simplicial_complex_homology.png differ diff --git a/HDVF/examples/HDVF/CMakeLists.txt b/HDVF/examples/HDVF/CMakeLists.txt new file mode 100644 index 000000000000..ead5cd2e4bef --- /dev/null +++ b/HDVF/examples/HDVF/CMakeLists.txt @@ -0,0 +1,26 @@ +# Created by the script cgal_create_cmake_script +# This is the CMake script for compiling a CGAL application. + +cmake_minimum_required(VERSION 3.12...3.31) +project(HDVF_Examples) + +find_package(CGAL REQUIRED) + + +find_package(Eigen3 3.1.0 QUIET) #(requires 3.1.0 or greater) +include(CGAL_Eigen3_support) + + + +# create a target per cppfile +file( + GLOB cppfiles + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +foreach(cppfile ${cppfiles}) + create_single_source_cgal_program("${cppfile}") +endforeach() + +if(TARGET CGAL::Eigen3_support) +target_link_libraries(hdvf_simplicial_4 PUBLIC CGAL::Eigen3_support) +endif() diff --git a/HDVF/examples/HDVF/arguments.h b/HDVF/examples/HDVF/arguments.h new file mode 100644 index 000000000000..de621c0c7d45 --- /dev/null +++ b/HDVF/examples/HDVF/arguments.h @@ -0,0 +1,394 @@ +#ifndef ARGUMENTS_H +#define ARGUMENTS_H + +#include + +using namespace std; +using namespace CGAL; +using namespace Homological_discrete_vector_field; + +enum class Algorithm { HDVF, DualHDVF, PerHDVF }; +enum class InputFormat { SIMP, OFF, CUB, PGM }; +enum class StarFiltrStd { FiltrX, FiltrY, FiltrZ }; + +struct Options +{ + std::string outfile_root = "res", in_file ; + Algorithm algorithm = Algorithm::HDVF ; + InputFormat in_format = InputFormat::OFF ; + int HDVF_opt = CGAL::Homological_discrete_vector_field::OPT_FULL ; + int scalar = 0 ; // 0 : Z, n : Z/nZ + bool with_export = true ; // Export reduction information to a file + bool with_vtk_export = false ; // Export generators/cogenerators and PSC labels as vtk + bool with_output = true ; // Consol output + bool with_frame = true ; // Only for persistent homology (cub and pgm in_files) + bool loop = false ; // Only for HDVF (enter an interaction loop for M, W, MW) + double BB_ratio = 1.5 ; // Only for persistent homolgy (off in_files: size of the bounding sphere) + bool primal = true ; // Only for cub_complexes (for dual construction, set primal to false) + StarFiltrStd star_filtr = StarFiltrStd::FiltrZ ; // Only for per_hdvf (when filtrataion is a standard lower star filtration) + bool random = false ; // Activate the computation of random perfect HDVF + bool verbose = false ; // Activate verbose mode (matrix output after each A operation for HDVF computation) + bool co_faces = false ; // Output co-faces of cohomology generators (or not) + + std::string comment() const + { + std::string str; + if (algorithm == Algorithm::HDVF) + str = "HDVF computation"; + else if (algorithm == Algorithm::DualHDVF) + str = "Alexander duality HDVF computation"; + else if (algorithm == Algorithm::PerHDVF) + str = "Persistent HDVF computation"; + else + str = "undefined"; + return str; + } +}; + +inline void usage() { + cout << "usage:" << endl ; + cout << "\talgorithm [options] in_file" << endl ; + cout << "with:" << endl ; + cout << "algorithm = hdvf (for hdvf (co)homology compuation)" << endl ; + cout << "\t dual_hdvf (for hdvf Alexander duality)" << endl ; + cout << "\t per_hdvf (for hdvf persistent (co)homology)" << endl ; + cout << "and options:" << endl ; + cout << "\t-outfile_root = xxx : sets the outfile root for outfiles names to xxx (default: res)" << endl ; + cout << "\t-opt (bnd|f|g|full) : HDVF option (default: full)" << endl ; + cout << "\t-s n : ring used to compute homology, 0: Z, n: Z/nZ (default: 0)" << endl ; + cout << "\t-rand : compute a random perfect HDVF (default: false, computing a random HDVF is slower)" << endl ; + cout << "\t-verbose : verbose HDVF computation, output reduction matrices after each A (default: false)" << endl ; + cout << "\t-with_output / -no_output : output / do not output the HDVF and reduction to the console (default: with_output)" << endl ; + cout << "\t-with_export / -no_export : export / do not export the HDVF and reduction to a file (default: with_export)" << endl ; + cout << "\t-with_vtk_export / -no_vtk_export : export / do not export the HDVF and reduction to vtk (default: no_export)" << endl ; + cout << "\t-cofaces_cohomology / -no_cofaces_cohomology : do not export co-faces of cohomology generators (default: co_faces_cohomology)" << endl ; + cout << "For CubComplex only:" << endl ; + cout << "\t-primal / -dual: build the primal / dual complex associated to data (default: primal)" << endl ; + cout << "For Alexander duality only:" << endl ; + cout << "\tFor SimpComplex:" << endl ; + cout << "\t\t-BB_ratio x : ratio for the closing bounding sphere (default: 1.5)" << endl ; + cout << "\tFor CubComplex:" << endl ; + cout << "\t\t-with_frame / -no_frame : enclose / do not inclose the complex in a larger bounding box (default: with_frame, adds 1 pixel around)" << endl ; + cout << "For persistent HDVF only:" << endl ; + cout << "\t-lower (x|y|z) : compute lower star filtration with degree x, y or z" << endl ; + cout << "\tFor other filtrations, define the filtration into the main and set #define OwnFiltration" << endl; +} + +inline Options read_arguments_hdvf(int argc, char** argv) { + Options opt_res ; + + // Minumum number of arguments: 2 + // HDVF_algorithm file + + if (argc <= 2) + { + cout << "HDVF wrong number of arguments. HDVF -h for help" << endl ; + throw "HDVF wrong number of arguments" ; + } + // Read algorithm from argv[0] (last word of the path - separated by /) + const string algo(argv[0]) ; + string word1 ; + { + std::stringstream string_parse(algo); + std::string tmp_word; + + std::getline(string_parse, tmp_word, '/') ; + word1 = tmp_word ; + while(std::getline(string_parse, tmp_word, '/')) + { + word1 = tmp_word ; + } + } + + if (word1.compare("main_hdvf") == 0) + opt_res.algorithm = Algorithm::HDVF ; + else if (word1.compare("main_dual_hdvf") == 0) + opt_res.algorithm = Algorithm::DualHDVF ; + else if (word1.compare("main_per_hdvf") == 0) + opt_res.algorithm = Algorithm::PerHDVF ; + else + { + cerr << "HDVF algorithm unknown. HDVF -h for help" << endl ; + throw "HDVF algorithm unknown" ; + } + + // Read in_file from argv[argc-1] ; + opt_res.in_file = argv[argc-1] ; + // Deduce the file type from the file name + string word ; + { + // Get the file name (last word of the path - separated by /) + std::stringstream string_parse(opt_res.in_file); + std::string tmp_word; + + std::getline(string_parse, tmp_word, '/') ; + word = tmp_word ; + while(std::getline(string_parse, tmp_word, '/')) + { + word = tmp_word ; + } + } + { + // Get the extension (last word of the word - separated by .) + string filename = word ; + std::stringstream string_parse(filename); + std::string tmp_word; + + std::getline(string_parse, tmp_word, '.') ; + word = tmp_word ; + while(std::getline(string_parse, tmp_word, '.')) + { + word = tmp_word ; + } + } + // Assign in_format accordingly + if (word.compare("simp")==0) + opt_res.in_format = InputFormat::SIMP ; + else if (word.compare("off")==0) + opt_res.in_format = InputFormat::OFF ; + else if (word.compare("cub")==0) + opt_res.in_format = InputFormat::CUB ; + else if (word.compare("pgm")==0) + opt_res.in_format = InputFormat::PGM ; + else + { + cerr << "HDVF input format unkown. HDVF -h for help" << endl ; + throw "HDVF input format unknown" ; + } + + + // Read next options + for(int i = 1; i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +//typedef HDVF::Zp<5,int,true> Coefficient_ring; +typedef CGAL::Z2 Coefficient_ring; + +typedef CGAL::OSM::Sub_sparse_matrix_structure Sparse_matrix_structure; + +typedef HDVF::Cubical_chain_complex Complex; +typedef HDVF::Hdvf_duality HDVF_type; +typedef HDVF::Duality_cubical_complex_tools Tools_type; +typedef HDVF::Sub_chain_complex_mask Sub_chain_complex; + +// PRIMAL / DUAL construction + +#define PRIMAL_DUAL true // or false + +// Frame or not the complex (add 1 pixel around) + +#define FRAME true // or false + +int main(int argc, char **argv) +{ + std::string filename ; + if (argc > 2) std::cout << "usage: dual_hdvf_cubical pgm_file" << std::endl; + else if (argc == 1) filename = "data/data_cubical/Eight_10.pgm"; + else filename = argv[1]; + + // Set variables to chose loading mode (PRIMAL or DUAL associated complex, pgm read using Khalimsky coordinates or not) + Complex::Cubical_complex_primal_dual primal_dual; + bool khalimsky; + if (PRIMAL_DUAL) { + primal_dual = Complex::PRIMAL; + khalimsky = true; + } + else { + primal_dual = Complex::DUAL; + khalimsky = false; + } + + // Load pgm object + HDVF::Cub_object_io cub_object ; + cub_object.read_pgm(filename, khalimsky); + + // Frame the object + if (FRAME) + cub_object.frame(); + + cub_object.print_infos(); + + // Build K and L + typename Tools_type::Complex_duality_data t(Tools_type::dualize_complex(cub_object, primal_dual)) ; + std::cout << "--- Triangulation built" << std::endl ; + std::shared_ptr L(t.L_complex) ; + std::shared_ptr K(t.K_complex) ; + std::cout << "--- K,L built" << std::endl ; + + std::cout << "----> complex informations" << std::endl ; + std::cout << "------> complex L" << std::endl ; + std::cout << *L; + std::cout << "------> subcomplex K" << std::endl ; + std::cout << *K << std::endl ; + + // Create and compute a perfect HDVF + HDVF_type hdvf(*L, *K, HDVF::OPT_FULL); + hdvf.compute_perfect_hdvf(); + + // Export K HDVF + hdvf.set_mask_K(); + CGAL::IO::write_VTK(hdvf, *L, "tmp/res_complex_K", false) ; + // Export L-K HDVF + hdvf.set_mask_L_K(); + CGAL::IO::write_VTK(hdvf, *L, "tmp/res_cocomplex_L_K", false) ; + // Compute pairing + std::vector pairs = hdvf.compute_pairing_hdvf(); + // Output pairing + for (const auto& pair : pairs) { + std::cout << "Sigma: " << pair.sigma << ", Tau: " << pair.tau << ", Dim: " << pair.dim << std::endl; + } + + return 0; +} diff --git a/HDVF/examples/HDVF/dual_hdvf_simplicial_cgal.cpp b/HDVF/examples/HDVF/dual_hdvf_simplicial_cgal.cpp new file mode 100644 index 000000000000..e96d61dcc70b --- /dev/null +++ b/HDVF/examples/HDVF/dual_hdvf_simplicial_cgal.cpp @@ -0,0 +1,82 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +//typedef CGAL::Zp<5,int,true> Coefficient_ring; +typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::OSM::Sub_sparse_matrix_structure Sparse_matrix_structure; +typedef HDVF::Simplicial_chain_complex Complex; +typedef HDVF::Hdvf_duality HDVF_type; +typedef HDVF::Duality_simplicial_complex_tools Tools_type; +typedef HDVF::Sub_chain_complex_mask Sub_chain_complex; +typedef CGAL::Surface_mesh Surface_mesh; + +int main(int argc, char **argv) +{ + std::string filename ; + if (argc > 2) std::cout << "usage: dual_hdvf_simplicial off_file" << std::endl; +// else if (argc == 1) filename = "data/data_simplicial/two_rings.off"; + else if (argc == 1) filename = "data/data_simplicial/three_triangles.off"; + else filename = argv[1]; + + // Load simplicial object into Surface_mesh + Surface_mesh sm; + CGAL::IO::read_polygon_mesh(filename, sm); + + if (sm.number_of_vertices() == 0) + { + std::cerr << "Emtpy mesh read" << std::endl; + throw("Emtpy mesh read"); + } + +// std::cout << sm; + + // Build K and L + typename Tools_type::Complex_duality_data t(Tools_type::dualize_complex(sm, 1.8, 1)); + std::cout << "--- Triangulation built" << std::endl ; + std::shared_ptr L(t.L_complex); + std::shared_ptr K(t.K_complex); + std::cout << "--- K,L built" << std::endl ; + + std::cout << "----> complex informations" << std::endl ; + std::cout << "------> complex L" << std::endl ; + std::cout << *L; + std::cout << "------> subcomplex K" << std::endl ; + std::cout << *K << std::endl ; + + // Create and compute a perfect HDVF + HDVF_type hdvf(*L, *K, HDVF::OPT_FULL); + hdvf.compute_perfect_hdvf(); + + // Export K HDVF + hdvf.set_mask_K(); + CGAL::IO::write_VTK(hdvf, *L, "tmp/res_complex_K", false) ; + // Export L-K HDVF + hdvf.set_mask_L_K(); + CGAL::IO::write_VTK(hdvf, *L, "tmp/res_cocomplex_L_K", false) ; + // Compute pairing + std::vector pairs = hdvf.compute_pairing_hdvf(); + // Output pairing + for (const auto& pair : pairs) { + std::cout << "Sigma: " << pair.sigma << ", Tau: " << pair.tau << ", Dim: " << pair.dim << std::endl; + } + + return 0; +} diff --git a/HDVF/examples/HDVF/dual_hdvf_simplicial_tetgen.cpp b/HDVF/examples/HDVF/dual_hdvf_simplicial_tetgen.cpp new file mode 100644 index 000000000000..e664dc5576cd --- /dev/null +++ b/HDVF/examples/HDVF/dual_hdvf_simplicial_tetgen.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +typedef Kernel::Point_3 Point_3; + +typedef CGAL::Surface_mesh Surface_mesh; + +//typedef CGAL::Zp<5,int,true> Coefficient_ring; +typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::OSM::Sub_sparse_matrix_structure Sparse_matrix_structure; +typedef HDVF::Simplicial_chain_complex Complex; +typedef HDVF::Hdvf_duality HDVF_type; +typedef HDVF::Duality_simplicial_complex_tools Tools_type; +typedef HDVF::Sub_chain_complex_mask Sub_chain_complex; + +int main(int argc, char **argv) +{ + std::string filename ; + if (argc > 2) std::cout << "usage: dual_hdvf_simplicial mesh_file" << std::endl; + else if (argc == 1) filename = "data/data_simplicial/three_triangles.obj"; + else filename = argv[1]; + + // Load mesh object + Surface_mesh sm; + CGAL::IO::read_polygon_mesh(filename, sm); + HDVF::Surface_mesh_io mesh(sm) ; + + mesh.print_infos(); + + // Build K and L + typename Tools_type::Complex_duality_data t = Tools_type::dualize_complex(mesh, 1.5, "tmp/file_K_closed.off", 1) ; + std::cout << "--- Triangulation built" << std::endl ; + std::shared_ptr L(t.L_complex) ; + std::shared_ptr K(t.K_complex) ; + std::cout << "--- K,L built" << std::endl ; + + std::cout << "----> complex informations" << std::endl ; + std::cout << "------> complex L" << std::endl ; + std::cout << *L; + std::cout << "------> subcomplex K" << std::endl ; + std::cout << *K << std::endl ; + + // Create and compute a perfect HDVF over K, L respectively + HDVF_type hdvf(*L, *K, HDVF::OPT_FULL); + hdvf.compute_perfect_hdvf(); + + // Export K HDVF + std::cout << "======> K" << std::endl; + hdvf.set_mask_K(); + hdvf.write_reduction_sub(); + CGAL::IO::write_VTK(hdvf, *L, "tmp/res_complex_K", false) ; + // Export L-K HDVF + std::cout << "======> L-K" << std::endl; + hdvf.set_mask_L_K(); + hdvf.write_reduction_sub(); + CGAL::IO::write_VTK(hdvf, *L, "tmp/res_cocomplex_L_K", false) ; + // Compute pairing + std::vector pairs = hdvf.compute_pairing_hdvf(); + // Output pairing + std::cout << "---> Alexander duality pairs" << std::endl; + for (const auto& pair : pairs) { + std::cout << "Sigma: " << pair.sigma << ", Tau: " << pair.tau << ", Dim: " << pair.dim << std::endl; + } + + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_abstract_simplicial.cpp b/HDVF/examples/HDVF/hdvf_abstract_simplicial.cpp new file mode 100644 index 000000000000..b0e58829d8c7 --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_abstract_simplicial.cpp @@ -0,0 +1,103 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +int main(int argc, char **argv) +{ +#if 1 + using Complex = HDVF::Abstract_simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + std::string filename ; + if (argc > 2) std::cerr << "usage: hdvf_abstract_simplicial simp_file" << std::endl; + else if (argc == 1) filename = "data/three_triangles_2.simp"; + else filename = argv[1]; + + // Load simp object + HDVF::Mesh_object_io mesh ; + mesh.read_simp(filename); + + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh); + + std::cout << complex; + + // // Build empty HDVF + // Without dimension restriction + // HDVF_type hdvf(complex, HDVF::OPT_FULL) ; + // With computation restricted to dimension 1 + HDVF_type hdvf(complex, HDVF::OPT_FULL, 1) ; + + // Compute a perfect HDVF + // hdvf.compute_perfect_hdvf(); + hdvf.compute_rand_perfect_hdvf(); + hdvf.write_hdvf_reduction("tmp/hdvf_init.hdvf"); + +// hdvf.read_hdvf_reduction("tmp/hdvf_init.hdvf"); + + // Output HDVF to console + hdvf.write_flags(std::cout, -1); + hdvf.write_matrices(std::cout, -1); + +// // M operation +// { +// bool found; +// std::vector pairs(hdvf.find_pairs_M(1, found)); +// if (found) { +// hdvf.M(pairs.at(0)); +// std::cout << "===========" << std::endl; +// hdvf.write_flags(std::cout, -1); +// hdvf.write_matrices(std::cout, -1); +// } +// } + +// // W operation +// { +// bool found; +// std::vector pairs(hdvf.find_pairs_W(1, found)); +// if (found) { +// hdvf.W(pairs.at(0)); +// std::cout << "===========" << std::endl; +// hdvf.write_flags(std::cout, -1); +// hdvf.write_matrices(std::cout, -1); +// } +// } + +// // MW operations +// { +// bool found; +// std::vector pairs(hdvf.find_pairs_MW(1, found)); +// if (found) { +// hdvf.MW(pairs.at(0)); +// std::cout << "===========" << std::endl; +// hdvf.write_flags(std::cout, -1); +// hdvf.write_matrices(std::cout, -1); +// } +// } + + // Save HDVF to .hdvf file + hdvf.write_hdvf_reduction("tmp/test.hdvf") ; + +#endif + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_cubical.cpp b/HDVF/examples/HDVF/hdvf_cubical.cpp new file mode 100644 index 000000000000..1ef649319f1b --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_cubical.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; +//typedef HDVF::Hdvf_traits_2 Traits; + +int main(int argc, char **argv) +{ + using Complex = HDVF::Cubical_chain_complex > ; + using HDVF_type = HDVF::Hdvf ; + + std::string filename ; + if (argc > 2) std::cout << "usage: hdvf_cubical pgm_file" << std::endl; + else if (argc == 1) filename = "data/data_cubical/Eight_3D.pgm"; + else filename = argv[1]; + + std::cout << "Loading file: " << filename << std::endl; + + // Choose between PRIMAL and DUAL construction + const Complex::Cubical_complex_primal_dual primal_dual = Complex::PRIMAL; + // Adapt pgm loading into Cub_complex accordingly + const bool khalimsky_coords = (primal_dual == Complex::PRIMAL) ? true : false ; + + HDVF::Cub_object_io mesh ; + + // Load pgm into cub object + mesh.read_pgm(filename, khalimsky_coords); + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh, primal_dual); + + std::cout << complex; + + // Build empty HDVF + HDVF_type hdvf(complex, HDVF::OPT_FULL) ; + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + // hdvf.compute_rand_perfect_hdvf(); + + // Output HDVF to console + hdvf.write_matrices(); + hdvf.write_reduction(); + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_cubical_restrict.cpp b/HDVF/examples/HDVF/hdvf_cubical_restrict.cpp new file mode 100644 index 000000000000..6a5c34bc8438 --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_cubical_restrict.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +//typedef HDVF::Hdvf_traits_3 Traits; +typedef HDVF::Hdvf_traits_3 Traits; + +int main(int argc, char **argv) +{ + using Complex = HDVF::Cubical_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + std::string filename ; + if (argc > 2) std::cout << "usage: hdvf_cubical_restrict pgm_file" << std::endl; + else if (argc == 1) filename = "data/data_cubical/Eight_3D.pgm"; + else filename = argv[1]; + + // Choose between PRIMAL and DUAL construction + const Complex::Cubical_complex_primal_dual primal_dual = Complex::PRIMAL; + // Adapt pgm loading into Cub_complex accordingly + const bool khalimsky_coords = (primal_dual == Complex::PRIMAL) ? true : false ; + + HDVF::Cub_object_io mesh ; + + // Load pgm into cub object + mesh.read_pgm(filename, khalimsky_coords); + mesh.print_infos(); + + // Build cubical chain complex + Complex complex(mesh, primal_dual); + + std::cout << complex; + + // Build empty HDVF + HDVF_type hdvf(complex, HDVF::OPT_FULL, 1); + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + // hdvf.compute_rand_perfect_hdvf(); + + // Output HDVF to console + // hdvf.write_matrices(std::cout); + hdvf.write_reduction(std::cout); + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true); + + hdvf.dimension_restriction(2); + + + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_simplicial.cpp b/HDVF/examples/HDVF/hdvf_simplicial.cpp new file mode 100644 index 000000000000..93b88f82e19c --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_simplicial.cpp @@ -0,0 +1,74 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +typedef CGAL::Surface_mesh Surface_mesh; + +int main(int argc, char **argv) +{ +#if 1 + using Sparse_matrix_structure = CGAL::OSM::Sparse_matrix_structure; + using Complex = HDVF::Simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + std::string filename ; + if (argc > 2) std::cerr << "usage: example_hdvf_simplicial off_file" << std::endl; + else if (argc == 1) filename = "data/data_simplicial/two_rings.off"; +// else if (argc == 1) filename = "data/data_simplicial/three_triangles.off"; + else filename = argv[1]; + + // Load Surface_mesh object and create Surface_mesh_io + Surface_mesh sm; + CGAL::IO::read_polygon_mesh(filename, sm); + HDVF::Surface_mesh_io mesh(sm) ; + + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh); + + std::cout << complex; + + // // Build empty HDVF + // Without dimension restriction + HDVF_type hdvf(complex, HDVF::OPT_FULL) ; + // With computation restricted to dimension 1 +// HDVF_type hdvf(complex, HDVF::OPT_FULL, 1) ; + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); +// hdvf.compute_rand_perfect_hdvf(); + + hdvf.write_matrices(); + + // Output HDVF to console +// hdvf.write_flags(std::cout, -1); + hdvf.write_reduction(std::cout, -1); + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + // Save HDVF to .hdvf file + hdvf.write_hdvf_reduction("tmp/test.hdvf") ; + +#endif + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_simplicial_4.cpp b/HDVF/examples/HDVF/hdvf_simplicial_4.cpp new file mode 100644 index 000000000000..b7125c032139 --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_simplicial_4.cpp @@ -0,0 +1,71 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Epick_d> Kernel; +typedef HDVF::Hdvf_traits_d Traits; + +int main(int argc, char **argv) +{ + using Complex = HDVF::Simplicial_chain_complex > ; + using HDVF_type = HDVF::Hdvf ; +#if 1 + std::string nodes_filename, simp_filename ; + if (argc > 3) std::cerr << "usage: hdvf_simplicial_4 nodes_file simp_file" << std::endl; + else if (argc == 1) { + nodes_filename = "data/dim_4/klein4.nodes"; + simp_filename = "data/dim_4/klein4.simp"; + } + else { + nodes_filename = argv[1]; + simp_filename = argv[2]; + } + + // Load nodes + simplicial data + HDVF::Mesh_object_io mesh ; + mesh.read_nodes_file(nodes_filename); + mesh.read_simp(simp_filename); + + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh); + + std::cout << complex; + + // Build empty HDVF + HDVF_type hdvf(complex, HDVF::OPT_FULL) ; + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + // hdvf.compute_rand_perfect_hdvf(); + + // Output HDVF to console + hdvf.write_matrices(); + hdvf.write_reduction(); + + // Output HDVF to vtk + Traits::to_point3 = Traits::pca_frame_builder(complex.points()); + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + // Save HDVF to .hdvf file + hdvf.write_hdvf_reduction("tmp/test.hdvf") ; + +#endif + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_simplicial_restrict.cpp b/HDVF/examples/HDVF/hdvf_simplicial_restrict.cpp new file mode 100644 index 000000000000..14f1befe1163 --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_simplicial_restrict.cpp @@ -0,0 +1,64 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +int main(int argc, char **argv) +{ +#if 1 + using Complex = HDVF::Simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + std::string filename ; + if (argc > 2) std::cerr << "usage: example_hdvf_simplicial off_file" << std::endl; + else if (argc == 1) filename = "data/data_simplicial/two_rings.off"; + else filename = argv[1]; + + // Load cub object + HDVF::Mesh_object_io mesh ; + mesh.read_off(filename); + + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh); + + std::cout << complex; + + // Build empty HDVF with computation restricted to dimension 1 + HDVF_type hdvf(complex, HDVF::OPT_FULL, 1) ; + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + // hdvf.compute_rand_perfect_hdvf(); + + // Output HDVF to console (restricted to dimension 1 by default) + hdvf.write_matrices(std::cout); + hdvf.write_reduction(std::cout); + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + // Save HDVF to .hdvf file + hdvf.write_hdvf_reduction("tmp/test.hdvf") ; + +#endif + return 0; +} diff --git a/HDVF/examples/HDVF/hdvf_surface_mesh_simplicial.cpp b/HDVF/examples/HDVF/hdvf_surface_mesh_simplicial.cpp new file mode 100644 index 000000000000..f36acb254412 --- /dev/null +++ b/HDVF/examples/HDVF/hdvf_surface_mesh_simplicial.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; + +typedef CGAL::Surface_mesh Surface_mesh; + +int main(int argc, char **argv) +{ +#if 1 + using Complex = HDVF::Simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + std::string filename ; + if (argc > 2) std::cerr << "usage: hdvf_surface_mesh_simplicial off_file" << std::endl; + else if (argc == 1) filename = "data/data_simplicial/two_rings.off"; + else filename = argv[1]; + + // Load simplicial object + Surface_mesh sm; + CGAL::IO::read_polygon_mesh(filename, sm); + HDVF::Surface_mesh_io mesh(sm) ; + + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh); + + std::cout << complex; + + // Build empty HDVF + HDVF_type hdvf(complex, HDVF::OPT_FULL) ; + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + // hdvf.compute_rand_perfect_hdvf(); + + // Output HDVF to console + hdvf.write_matrices(); + hdvf.write_reduction(); + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + // Save HDVF to .hdvf file + hdvf.write_hdvf_reduction("tmp/test.hdvf") ; +#endif + return 0; +} diff --git a/HDVF/examples/HDVF/main_dual_hdvf.cpp b/HDVF/examples/HDVF/main_dual_hdvf.cpp new file mode 100644 index 000000000000..0a895fe2e02b --- /dev/null +++ b/HDVF/examples/HDVF/main_dual_hdvf.cpp @@ -0,0 +1,313 @@ +// Dual HDVF computation (command line version) +// -------- +// Computes a "perfect" dual HDVF from a object +// (the object is embedded into a "ball") +// and provides a batch mode to specify arguments +// For help: dual_hdvf -h +// -------- +// A. Bac +// -------- + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "arguments.h" +namespace HDVF = CGAL::Homological_discrete_vector_field; + +// ------- A ring +// For Z/nZ other than Z (ie. n=0) and Z/2Z, uncomment and set the following define properly + +//#define SCALAR 5 + +using Kernel = CGAL::Simple_cartesian; +using Traits = HDVF::Hdvf_traits_3; +using Sparse_matrix_structure = CGAL::OSM::Sub_sparse_matrix_structure; + +template +void mesh_complex_output(const MeshType& mesh, const Complex& L, const HDVF::Sub_chain_complex_mask& K, const Options& options) +{ + if (options.with_output) + { + // Mesh + std::cout << "----> mesh informations" << std::endl ; + mesh.print_infos() ; + + // Complex + std::cout << "----> complex informations" << std::endl ; + std::cout << "------> complex L" << std::endl ; + std::cout << L; + std::cout << "------> subcomplex K" << std::endl ; + std::cout << K << std::endl ; + } +} + +inline std::ostream& dual_pairs_output(const std::vector& pairs, std::ostream& out=std::cout) +{ + out << "Pairs found by compute_perfect_hdvf:" << std::endl; + for (const auto& pair : pairs) { + out << "Sigma: " << pair.sigma << ", Tau: " << pair.tau << ", Dim: " << pair.dim << std::endl; + } + return out ; +} + +template +void dual_HDVF_pair (HDVF::Hdvf_duality& dual_hdvf, const Options &options) +{ + // Compute pairing + std::vector pairs = dual_hdvf.compute_pairing_hdvf() ; + + if (options.with_output) + { + dual_pairs_output(pairs) ; + } + if (options.with_export) + { + std::string file(options.outfile_root+"_pairs.txt") ; + std::ofstream out ( file, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "hdvf: with_export. Fatal Error:\n " << file << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + dual_pairs_output(pairs, out) ; + out.close() ; + } +} + +template +HDVF::Hdvf_duality& dual_HDVF_comput (const Complex& L, HDVF::Sub_chain_complex_mask& K, const Options &options) +{ + using Coefficient_ring = typename Complex::Coefficient_ring; + using HDVF_type = HDVF::Hdvf_duality ; + using SubCCType = HDVF::Sub_chain_complex_mask ; + + HDVF_type& hdvf(*(new HDVF_type(L, K, options.HDVF_opt))); + + std::cout << "----> START computing dual HDVF" << std::endl ; + if (options.random) + hdvf.compute_rand_perfect_hdvf() ; + else + hdvf.compute_perfect_hdvf() ; + std::cout << "------> END computing dual HDVF" << std::endl ; + + if (options.with_output) + { + std::cout << "----> reduction" << std::endl ; + hdvf.write_reduction() ; + } + if (options.with_export) + { + std::cout << "----> exporting..." << std::endl ; + std::string file(options.outfile_root+"_reduction.txt") ; + std::ofstream out ( file, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "hdvf: with_export. Fatal Error:\n " << file << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + out << "----> reduction" << std::endl ; + hdvf.write_reduction(out) ; + + out.close() ; + } + return hdvf ; +} + + +template +void main_code (const Options &options) +{ +#ifndef CUST_FILTRATION + // Standard lower star filtration along x,y or z + using Degree = double ; +#else + // TODO +#endif + + /// SIMP format + if (options.in_format == InputFormat::SIMP) + { +// using Complex = AbstractSimpComplex ; +// using HDVF_type = HDVF ; +// +// // MeshObject +// MeshObject mesh ; +// mesh.read_simp(options.in_file) ; +// +// // Complex +// Complex complex(mesh); +// +// mesh_complex_output(mesh, complex, options) ; +// +// // HDVF computation, export, output +// HDVF_type hdvf(HDVF_comput(complex, options)) ; +// +// // Export to vtk +// // None for SIMP format + std::cout << "not yet..." << std::endl ; + throw("not yet") ; + } + /// OFF format + else if (options.in_format == InputFormat::OFF) + { + using Complex = HDVF::Simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf_duality ; + using ToolsType = HDVF::Duality_simplicial_complex_tools ; + using SubCCType = HDVF::Sub_chain_complex_mask ; + + // MeshObject + HDVF::Mesh_object_io mesh ; + mesh.read_off(options.in_file) ; + + // Build L (bounding sphere meshed with tetgen), K and L-K + + typename ToolsType::Complex_duality_data t = ToolsType::dualize_complex(mesh) ; + std::shared_ptr L(t.L_complex) ; + std::shared_ptr K(t.K_complex) ; + + // Output/export mesh and complex + + mesh_complex_output, Complex>(mesh, *L, *K, options) ; + + // HDVF computation, export, output + HDVF_type& hdvf(dual_HDVF_comput(*L, *K, options)) ; + + // Export to vtk + if (options.with_vtk_export) + { + std::cout << "----> exporting to vtk" << std::endl ; + // K + { + hdvf.set_mask_K() ; + CGAL::IO::write_VTK(hdvf, *L, options.outfile_root+"_complex_K", options.co_faces) ; + } + // L-K + { + hdvf.set_mask_L_K() ; + CGAL::IO::write_VTK(hdvf, *L, options.outfile_root+"_cocomplex_L_K", options.co_faces) ; + } + } + + // Compute pairing + dual_HDVF_pair(hdvf, options) ; + } + // CubComplex + else if ((options.in_format == InputFormat::PGM) || (options.in_format == InputFormat::CUB)) + { + using Complex = HDVF::Cubical_chain_complex ; + using HDVF_type = HDVF::Hdvf_duality ; + using SubCCType = HDVF::Sub_chain_complex_mask ; + using ToolsType = HDVF::Duality_cubical_complex_tools ; + + HDVF::Cub_object_io mesh ; + typename Complex::Cubical_complex_primal_dual primal_dual(Complex::PRIMAL) ; + if (options.primal) + { + if (options.in_format == InputFormat::PGM) + mesh.read_pgm(options.in_file, true) ; // Read with Khalimsky coordinates (for primal) + else + mesh.read_cub(options.in_file, true) ; // Read with Khalimsky coordinates (for primal) + } + else // dual + { + if (options.in_format == InputFormat::PGM) + mesh.read_pgm(options.in_file, false) ; // Read with pixel coordinates (for dual) + else + mesh.read_cub(options.in_file, false) ; // Read with pixel coordinates (for dual) + primal_dual = Complex::DUAL ; + } + + // Frame (add 1 voxel around data) + if (options.with_frame) + { + mesh.frame() ; + } + + // Build L, K and L-K + + typename ToolsType::Complex_duality_data p = ToolsType::dualize_complex(mesh, primal_dual) ; + std::shared_ptr L(p.L_complex) ; + std::shared_ptr K(p.K_complex) ; + + // Output/export mesh and complex + + mesh_complex_output, Complex>(mesh, *L, *K, options) ; + + // HDVF computation, export, output + HDVF_type& hdvf(dual_HDVF_comput(*L, *K, options)) ; + + // Export to vtk + if (options.with_vtk_export) + { + std::cout << "----> exporting to vtk" << std::endl ; + // K + { + hdvf.set_mask_K() ; + CGAL::IO::write_VTK(hdvf, *L, options.outfile_root+"_complex_K", options.co_faces) ; + } + // L-K + { + hdvf.set_mask_L_K() ; + CGAL::IO::write_VTK(hdvf, *L, options.outfile_root+"_cocomplex_L_K", options.co_faces) ; + } + } + + // Compute pairing + dual_HDVF_pair(hdvf, options) ; + } +} + +// Main + +int main(int argc, char **argv) +{ + if (argc <= 2) + usage() ; + else + { + for (int i=0;i(options) ; + } + else if (options.scalar == 2) + { + using Coefficient_ring = CGAL::Z2 ; + main_code(options) ; + } + else + { + std::cerr << "Z" << options.scalar << " not instantiated, use the #define at line 27" << std::endl ; + } +#else + typedef CGAL::Zp Coefficient_ring; +#endif + } + + return 0 ; +} + diff --git a/HDVF/examples/HDVF/main_hdvf.cpp b/HDVF/examples/HDVF/main_hdvf.cpp new file mode 100644 index 000000000000..768c3226efcd --- /dev/null +++ b/HDVF/examples/HDVF/main_hdvf.cpp @@ -0,0 +1,267 @@ +// HDVF computation (command line version) +// -------- +// Computes a "perfect" Hdvf and provides a batch mode to specify arguments +// For help: hdvf -h +// -------- +// A. Bac +// -------- + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "arguments.h" + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +using Kernel = CGAL::Simple_cartesian; +using Traits = HDVF::Hdvf_traits_3; +using Sparse_matrix_structure = CGAL::OSM::Sub_sparse_matrix_structure; + +// ------- A ring +// For Z/nZ other than Z (ie. n=0) and Z/2Z, uncomment and set the following define properly + +//#define SCALAR 5 + +// Hdvf computation, output and export + +template +void mesh_complex_output(const MeshType& mesh, const Complex& complex, const Options& options) +{ + if (options.with_output) + { + // Mesh + std::cout << "----> mesh informations" << std::endl ; + mesh.print_infos() ; + + // Complex + std::cout << "----> complex informations" << std::endl; + std::cout << complex; + } +} + +template +HDVF::Hdvf& HDVF_comput (const Complex& complex, const Options &options) +{ + typedef typename Complex::Coefficient_ring Coefficient_ring; + HDVF::Hdvf& hdvf(*(new HDVF::Hdvf(complex, options.HDVF_opt))); + std::vector pairs ; + if (!options.random) + pairs = hdvf.compute_perfect_hdvf(options.verbose); + else + pairs = hdvf.compute_rand_perfect_hdvf(options.verbose); + + if (options.with_output) + { + std::cout << "----> pairs found by computePerfectHDVF" << std::endl ; + std::cout << pairs ; + std::cout << "----> reduction" << std::endl ; + hdvf.write_reduction() ; + } + if (options.with_export) + { + std::string file(options.outfile_root+"_reduction.txt") ; + std::ofstream out ( file, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "hdvf: with_export. Fatal Error:\n " << file << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + out << "----> pairs found by computePerfectHDVF" << std::endl ; + out << pairs ; + out << "----> reduction" << std::endl ; + hdvf.write_reduction(out) ; + + out.close() ; + } + return hdvf ; +} + +template +void main_code (const Options &options) +{ + /// SIMP format + if (options.in_format == InputFormat::SIMP) + { + using Complex = HDVF::Abstract_simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + // MeshObject + HDVF::Mesh_object_io mesh ; + mesh.read_simp(options.in_file) ; + + // Complex + Complex complex(mesh); + + mesh_complex_output, Complex>(mesh, complex, options) ; + + // Hdvf computation, export, output + HDVF_type hdvf(HDVF_comput(complex, options)) ; + + // Export to vtk + // None for SIMP format + } + /// OFF format + else if (options.in_format == InputFormat::OFF) + { + using Complex = HDVF::Simplicial_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + // MeshObject + HDVF::Mesh_object_io mesh ; + mesh.read_off(options.in_file) ; + + // Complex + Complex complex(mesh); + + mesh_complex_output, Complex>(mesh, complex, options) ; + + // Hdvf computation, export, output + HDVF_type hdvf(HDVF_comput(complex, options)) ; + + // Loop on operations with vtk export + if (options.loop) + { + auto output_vtk_simp = [options](HDVF_type &hdvf, Complex& complex) + { + CGAL::IO::write_VTK(hdvf, complex, options.outfile_root, options.co_faces) ; + } ; + + HDVF::interaction_loop(hdvf, complex, output_vtk_simp) ; + } + // Export to vtk + else if (options.with_vtk_export) + { + std::cout << "----> exporting to vtk" << std::endl ; + CGAL::IO::write_VTK(hdvf, complex, options.outfile_root, options.co_faces) ; + } + } + // CubComplex + else if ((options.in_format == InputFormat::PGM) || (options.in_format == InputFormat::CUB)) + { + using Complex = HDVF::Cubical_chain_complex ; + using HDVF_type = HDVF::Hdvf ; + + HDVF::Cub_object_io mesh ; + typename Complex::Cubical_complex_primal_dual primal_dual(Complex::PRIMAL) ; + if (options.primal) + { + if (options.in_format == InputFormat::PGM) + mesh.read_pgm(options.in_file, true) ; // Read with Khalimsky coordinates (for primal) + else + mesh.read_cub(options.in_file, true) ; // Read with Khalimsky coordinates (for primal) + } + else // dual + { + if (options.in_format == InputFormat::PGM) + mesh.read_pgm(options.in_file, false) ; // Read with pixel coordinates (for dual) + else + mesh.read_cub(options.in_file, false) ; // Read with pixel coordinates (for dual) + primal_dual = Complex::DUAL ; + } + + // Complex + Complex complex(mesh, primal_dual); + + mesh_complex_output, Complex>(mesh, complex, options) ; + + // Hdvf computation, export, output + HDVF_type hdvf(HDVF_comput(complex, options)) ; + + // Loop on operations with vtk export + if (options.loop) + { + auto output_vtk_cub = [options](HDVF_type &hdvf, Complex& complex) + { + CGAL::IO::write_VTK(hdvf, complex, options.outfile_root, options.co_faces) ; + } ; + + HDVF::interaction_loop(hdvf, complex, output_vtk_cub) ; + } + // Export to vtk + else if (options.with_vtk_export) + { + std::cout << "----> exporting to vtk" << std::endl ; + CGAL::IO::write_VTK(hdvf, complex, options.outfile_root, options.co_faces) ; + } + } +} + +// Main + +int main(int argc, char **argv) +{ + if (argc <= 2) + usage() ; + else + { + for (int i=0;i gamma(4) ; + std::cout << "before: " << gamma.is_null() << std::endl ; + gamma.set_coefficient(2, 1) ; + std::cout << "after: " << gamma.is_null() << std::endl ; + gamma.set_coefficient(2, 0) ; + std::cout << "after2: " << gamma.is_null() << std::endl ; + std::cout << "END TEST" << std::endl ; + + // ----- Definition of the Coefficient_ring +#ifndef SCALAR + if (options.scalar == 0) + { + using Coefficient_ring = int ; + main_code(options) ; + } + else if (options.scalar == 2) + { +// using Coefficient_ring = HDVF::Zp<2,int8_t> ; + using Coefficient_ring = CGAL::Z2 ; + main_code(options) ; + } + else + { + std::cerr << "Z" << options.scalar << " not instantiated, use the #define at line 27" << std::endl ; + } +#else + typedef CGAL::Zp Coefficient_ring; +#endif + } + + return 0 ; +} + + + + + + + + + + + + + + + diff --git a/HDVF/examples/HDVF/main_per_hdvf.cpp b/HDVF/examples/HDVF/main_per_hdvf.cpp new file mode 100644 index 000000000000..bc4550e53975 --- /dev/null +++ b/HDVF/examples/HDVF/main_per_hdvf.cpp @@ -0,0 +1,319 @@ +// Persistent HDVF computation (command line version) +// -------- +// Computes a "perfect" persistent HDVF from a given filtration +// and provides a batch mode to specify arguments +// For help: per_hdvf -h +// -------- +// A. Bac +// -------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "arguments.h" + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +// ------- A ring +// For Z/nZ other than Z (ie. n=0) and Z/2Z, uncomment and set the following define properly + +//#define SCALAR 5 + +using Kernel = CGAL::Simple_cartesian; +using Traits = HDVF::Hdvf_traits_3; +typedef CGAL::OSM::Sub_sparse_matrix_structure Sparse_matrix_structure; + +// ------- Filtration + +// For a "custom" filtration, please use the following filtration function and set the +//#define CUST_FILTRATION + +// Degree type definition +#ifndef CUST_FILTRATION +using Degree = double ; +#else // Custom filtration +/// Define proper Degree below +using Degree = double ; +#endif + +// Custom filtration +#ifdef CUST_FILTRATION + +// Example returning x+z value for each vertex +template +std::function degree_function (const Complex& complex) +{ + std::function deg_fun_f = [&complex](int i) + { + const std::vector Xi(complex.point(i)) ; + /// Define proper filtration value below + return Xi.at(0)+Xi.at(2) ; + } ; + return deg_fun_f ; +} + +#endif + +template +void mesh_complex_output(const MeshType& mesh, const Complex& complex, const Options& options) +{ + if (options.with_output) + { + // Mesh + std::cout << "----> mesh informations" << std::endl ; + mesh.print_infos() ; + + // Complex + std::cout << "----> complex informations" << std::endl ; + std::cout << complex; + } +} + +template +HDVF::Hdvf_persistence& per_HDVF_comput (const Complex& complex, const FiltrationType& f, const Options &options) +{ + typedef typename Complex::Coefficient_ring CoefficientType; + typedef HDVF::Hdvf_persistence HDVF_type ; + HDVF_type& hdvf(*(new HDVF_type(complex, f, options.HDVF_opt, options.with_vtk_export))); + + std::cout << "----> START computing persistent homology" << std::endl ; + hdvf.compute_perfect_hdvf() ; + std::cout << "------> END computing persistent homology" << std::endl ; + + if (options.with_output) + { + std::cout << "----> perHDVF" << std::endl ; + hdvf.print_hdvf_persistence_information(std::cout); + std::cout << "----> reduction" << std::endl ; + hdvf.write_reduction() ; + std::cout << "----> persistent diagram" << std::endl ; + std::cout << hdvf ; + } + if (options.with_export) + { + std::cout << "----> exporting..." << std::endl ; + std::string file(options.outfile_root+"_reduction.txt") ; + std::ofstream out ( file, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "hdvf: with_export. Fatal Error:\n " << file << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + out << "----> perHDVF" << std::endl ; + hdvf.print_hdvf_persistence_information(out); + out << "----> reduction" << std::endl ; + hdvf.write_reduction(out) ; + + out.close() ; + + std::string file_per(options.outfile_root+"_per.txt") ; + std::ofstream out_per ( file_per, std::ios::out | std::ios::trunc); + + if ( ! out_per . good () ) { + std::cerr << "hdvf: with_export. Fatal Error:\n " << file << " ! found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + out_per << "----> persistent diagram" << std::endl ; + out_per << hdvf ; + + out_per.close() ; + } + return hdvf ; +} + +template +FiltrationType& build_filtration(const Complex& complex, const Options& options) +{ + typedef FiltrationType Filtration; + std::function deg_function ; +#ifndef CUST_FILTRATION + // Standard lower star filtration along x,y or z + if (options.star_filtr == StarFiltrStd::FiltrX) + deg_function = (degree_function(complex, Kernel::Compute_x_3())) ; + else if (options.star_filtr == StarFiltrStd::FiltrY) + deg_function = (degree_function(complex, Kernel::Compute_y_3())) ; + else if (options.star_filtr == StarFiltrStd::FiltrZ) + deg_function = (degree_function(complex, Kernel::Compute_z_3())) ; + else + { + std::cout << "Unknown lower start filtration" << std::endl ; + throw("Unknown lower start filtration") ; + } +#else + // Use degree_function defined above + deg_function = (degree_function(complex)) ; +#endif + + std::cout << "----> START building filtration" << std::endl ; + Filtration& f = *(new Filtration(complex, deg_function)) ; + std::cout << "------> END building filtration" << std::endl ; + return f ; +} + + +template +void main_code (const Options &options) +{ + /// SIMP format + if (options.in_format == InputFormat::SIMP) + { +// using Complex = AbstractSimpComplex ; +// using HDVF_type = HDVF ; +// +// // MeshObject +// MeshObject mesh ; +// mesh.read_simp(options.in_file) ; +// +// // Complex +// Complex complex(mesh); +// +// mesh_complex_output(mesh, complex, options) ; +// +// // HDVF computation, export, output +// HDVF_type hdvf(HDVF_comput(complex, options)) ; +// +// // Export to vtk +// // None for SIMP format + std::cout << "not yet..." << std::endl ; + throw("not yet") ; + } + /// OFF format + else if (options.in_format == InputFormat::OFF) + { + using Complex = HDVF::Simplicial_chain_complex ; + using FiltrationType = HDVF::Filtration_lower_star ; + using HDVF_type = HDVF::Hdvf_persistence ; + + // MeshObject + HDVF::Mesh_object_io mesh ; + mesh.read_off(options.in_file) ; + + // Complex + Complex complex(mesh); + + // Build filtration + FiltrationType& f(build_filtration(complex, options)) ; + + mesh_complex_output, Complex>(mesh, complex, options) ; + + // HDVF computation, export, output + HDVF_type& hdvf(per_HDVF_comput(complex,f, options)) ; + + // Export to vtk + if (options.with_vtk_export) + { + std::cout << "----> exporting to vtk" << std::endl ; + CGAL::IO::write_VTK(hdvf, complex, options.outfile_root, options.co_faces) ; + } + } + // CubComplex + else if ((options.in_format == InputFormat::PGM) || (options.in_format == InputFormat::CUB)) + { + using Complex = HDVF::Cubical_chain_complex ; + using FiltrationType = HDVF::Filtration_lower_star ; + using HDVF_type = HDVF::Hdvf_persistence ; + + HDVF::Cub_object_io mesh ; + typename Complex::Cubical_complex_primal_dual primal_dual(Complex::PRIMAL) ; + if (options.primal) + { + if (options.in_format == InputFormat::PGM) + mesh.read_pgm(options.in_file, true) ; // Read with Khalimsky coordinates (for primal) + else + mesh.read_cub(options.in_file, true) ; // Read with Khalimsky coordinates (for primal) + } + else // dual + { + if (options.in_format == InputFormat::PGM) + mesh.read_pgm(options.in_file, false) ; // Read with pixel coordinates (for dual) + else + mesh.read_cub(options.in_file, false) ; // Read with pixel coordinates (for dual) + primal_dual = Complex::DUAL ; + } + + // Complex + Complex complex(mesh, primal_dual); + + mesh_complex_output, Complex>(mesh, complex, options) ; + + // Build filtration + FiltrationType& f(build_filtration(complex, options)) ; + + // HDVF computation, export, output + HDVF_type& hdvf(per_HDVF_comput(complex,f, options)) ; + + // Export to vtk + if (options.with_vtk_export) + { + std::cout << "----> exporting to vtk" << std::endl ; + CGAL::IO::write_VTK(hdvf, complex, options.outfile_root, options.co_faces) ; + } + } +} + +// Main + +int main(int argc, char **argv) +{ + if (argc <= 2) + usage() ; + else + { + for (int i=0;i(options) ; + } + else if (options.scalar == 2) + { + using CoefficientType = CGAL::Z2 ; + main_code(options) ; + } + else + { + std::cerr << "Z" << options.scalar << " not instantiated, use the #define at line 27" << std::endl ; + } +#else + typedef CGAL::Zp CoefficientType; +#endif + } + + return 0 ; +} + + + + + + + + + + + + + + + diff --git a/HDVF/examples/HDVF/matrix_chain.cpp b/HDVF/examples/HDVF/matrix_chain.cpp new file mode 100644 index 000000000000..dc6d7fc89e5a --- /dev/null +++ b/HDVF/examples/HDVF/matrix_chain.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include + +typedef CGAL::OSM::Sparse_matrix_structure Sparse_matrix_structure; +typedef typename Sparse_matrix_structure::template Sparse_chain_type Column_chain; +typedef typename Sparse_matrix_structure::template Sparse_matrix_type Column_matrix; + +int main () +{ + // Create a column-major sparse matrix + Column_matrix M(5,4) ; + + // Fill coefficients + CGAL::OSM::set_coefficient(M, 0, 1, 1) ; + CGAL::OSM::set_coefficient(M, 0, 2, -1) ; + CGAL::OSM::set_coefficient(M, 2, 1, 2) ; + + // Iterate over non empty columns + for(CGAL::OSM::Bitboard::iterator it_col = M.begin(); it_col != M.end(); ++it_col) + { + std::cout << "col: " << *it_col << std::endl ; + // Get a constant reference over the column (complexity O(1)) + const Column_chain& col(CGAL::OSM::cget_column(M, *it_col)); + // Iterate over the column + for (Column_chain::const_iterator it = col.begin(); it != col.end(); ++it) + { + std::cout << "row: " << it->first << " - coef: " << it->second << std::endl ; + } + } + // Direct output of the matrix with << operator + std::cout << "M: " << M << std::endl; + + return 0 ; +} diff --git a/HDVF/examples/HDVF/persistent_hdvf_cubical_restrict.cpp b/HDVF/examples/HDVF/persistent_hdvf_cubical_restrict.cpp new file mode 100644 index 000000000000..1a36fe57a4b8 --- /dev/null +++ b/HDVF/examples/HDVF/persistent_hdvf_cubical_restrict.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; +typedef Kernel::Compute_z_3 Compute_z; +typedef Kernel::Point_3 Point_3; + +typedef CGAL::OSM::Sub_sparse_matrix_structure Sparse_matrix_structure; + +typedef HDVF::Cubical_chain_complex Complex; +typedef double Degree; +typedef HDVF::Filtration_lower_star FiltrationType; +typedef HDVF::Hdvf_persistence HDVF_type; + + +int main(int argc, char **argv) +{ + + std::string filename; + if (argc > 2) std::cout << "usage: persistent_hdvf_cubical_restrict pgm_file" << std::endl; + else if (argc == 1) filename = "data/data_cubical/Eight_3D.pgm"; + else filename = argv[1]; + + // Choose between PRIMAL and DUAL construction + const Complex::Cubical_complex_primal_dual primal_dual = Complex::PRIMAL; + // Adapt pgm loading into Cub_complex accordingly + const bool khalimsky_coords = (primal_dual == Complex::PRIMAL) ? true : false ; + + // Load cub object + HDVF::Cub_object_io mesh ; + + // Load pgm into cub object + mesh.read_pgm(filename, khalimsky_coords); + mesh.print_infos(); + + // Build cubical chain complex + Complex complex(mesh, primal_dual); + + std::cout << complex; + + /* Build a lower star filtration "slicing" the object according to the "z" coordinate of vertices + -> GeometricChainComplex required in this case + */ + + // --- First: build the function mapping the index of a vertex to its degree (here the "z" coordinate of a vertex + Compute_z compute_z = Kernel().compute_z_3_object() ; + std::function f(HDVF::degree_function(complex, compute_z)); + + // -- Second: build the associated lower star filtration + FiltrationType filtration(complex, f); + + // Build empty persistent HDVF (with vtk export activated) + HDVF_type hdvf(complex, filtration, HDVF::OPT_FULL, true, 1); + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + + // Output HDVF to console + // hdvf.write_matrices(); + hdvf.write_reduction(); + std::cout << hdvf ; + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + return 0; +} diff --git a/HDVF/examples/HDVF/persistent_hdvf_simplicial.cpp b/HDVF/examples/HDVF/persistent_hdvf_simplicial.cpp new file mode 100644 index 000000000000..95454907588e --- /dev/null +++ b/HDVF/examples/HDVF/persistent_hdvf_simplicial.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HDVF = CGAL::Homological_discrete_vector_field; + +//typedef int Coefficient_ring; +//typedef CGAL::Z2 Coefficient_ring; +typedef CGAL::Zp<5, char, true> Coefficient_ring; + +typedef CGAL::Simple_cartesian Kernel; +typedef HDVF::Hdvf_traits_3 Traits; +typedef Kernel::Compute_z_3 Compute_z; +typedef Kernel::Point_3 Point_3; + +typedef CGAL::OSM::Sub_sparse_matrix_structure Sparse_matrix_structure; +typedef HDVF::Simplicial_chain_complex Complex; +typedef double Degree; +typedef HDVF::Filtration_lower_star FiltrationType; +typedef HDVF::Hdvf_persistence HDVF_type; + +int main(int argc, char **argv) +{ + + std::string filename; + if (argc > 2) std::cout << "usage: persistent_hdvf_simplicial off_file" << std::endl; + else if (argc == 1) filename = "data/data_simplicial/two_rings.off"; + else filename = argv[1]; + + // Load cub object + HDVF::Mesh_object_io mesh ; + mesh.read_off(filename); + + mesh.print_infos(); + + // Build simplicial chain complex + Complex complex(mesh); + + std::cout << complex; + + // -- First: Define the filtration function + + // /* ---- Example 1 : build a lower star filtration "slicing" the object according to the "z" coordinate of vertices + // -> GeometricChainComplex required in this case + // */ + // + // Compute_z compute_z = Kernel().compute_z_3_object() ; + // std::function f(HDVF::degree_function(complex, compute_z)); + + // /* ---- Example 2 : build a lower star filtration "slicing" the object according to "x+y" value for each vertex + // -> GeometricChainComplex required in this case + // */ + // + // std::function sample_f = [](const Point_3& X) + // { + // // Filtration : (x,y,z) -> degree x+y + // return X[0]+X[1]; + // } ; + // std::function f(HDVF::degree_function(complex, sample_f)); + + /* ---- Example 3 : build a lower star filtration "slicing" the object according to the index of vertices (we can imagine any other filtration that does not depend on the geometry (e.g. color of vertices...) + -> AbstractChainComplex in this case + */ + std::function f = [](size_t i) { + return Degree(i) ; + } ; + + // -- Second: build the associated lower star filtration + FiltrationType filtration(complex, f); + + + // Build empty persistent HDVF (with vtk export activated) + HDVF_type hdvf(complex, filtration, HDVF::OPT_FULL, true); + + // Compute a perfect HDVF + hdvf.compute_perfect_hdvf(); + + // Output HDVF to console + hdvf.write_matrices(); + hdvf.write_reduction(); + std::cout << hdvf ; + + // Output HDVF to vtk + CGAL::IO::write_VTK(hdvf, complex, "tmp/res", true) ; + + return 0; +} diff --git a/HDVF/include/CGAL/HDVF/Abstract_simplicial_chain_complex.h b/HDVF/include/CGAL/HDVF/Abstract_simplicial_chain_complex.h new file mode 100644 index 000000000000..d228bfaf2a57 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Abstract_simplicial_chain_complex.h @@ -0,0 +1,475 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_ABSTRACT_SIMPLICIAL_CHAIN_COMPLEX_H +#define CGAL_HDVF_ABSTRACT_SIMPLICIAL_CHAIN_COMPLEX_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFChainComplexesClasses + + The class `Abstract_simplicial_chain_complex` represents (topological) chain complexes associated to abstract simplicial complexes. + + An abstract simplicial complex is a set of simplices, also called cells (class `Simplex`) such that all the faces of a given simplex also belong to the complex and any two simplices intersect exactly along a common face. + + See \ref secHDVFsimplicial_chain_complex "user documentation of simplicial chain complexes" for more details. + + Let us point out that cells are indexed along each dimension, thus each simplex is uniquely determined by its dimension and its index in this dimension. + + \cgalModels{AbstractChainComplex} + + \tparam CoefficientRing a model of the `IntegralDomainWithoutDivision` concept. + \tparam SparseMatrixStruct a model of `SparseMatrixStructure` providing templates of sparse chains and sparse matrices built over a given model of `SparseChain`. + + \warning Check if the documentation of the `SparseMatrixStruct` added is correct and "understandable". + */ + +// +// +//A simplex of dimension q contains exactly q+1 vertices (we will thus denote it by \f$\langle v_0, \ldots, v_q \rangle\f$, vertex indices *must be sorted*). A 0-cell is thus a vertex, a 1-cell contains two vertices (edge), a 2-cell contains three vertices (triangle) while a 3-cell is a tetrahedron. +// +//The boundary map of the complex is computed by the constructor of the class using the standard formula: +//\f[ \partial_q\left( \langle v_0, \ldots, v_q \rangle\right) = \sum_{i=0}^q (-1)^i\cdot \langle v_0, \ldots, \widehat{v_i}, \cdots, v_q \rangle\f] +//where \f$\langle v_0, \ldots, \widehat{v_i}, \cdots, v_q \rangle\f$ denotes the \f$q-1\f$-simplex with \f$v_i\f$ omitted. Hence, matrices of boundary maps are stored in each dimension using sparse matrices (in column-major mode). + +template > +class Abstract_simplicial_chain_complex { +public: + /*! \brief Type of coefficients used to compute homology. */ + typedef CoefficientRing Coefficient_ring; + + /** \brief Type of sparse matrix structure used to compute homology. */ + typedef SparseMatrixStruct Sparse_matrix_structure; + + /** \brief Type of sparse chain used to compute homology (derived from `SparseMatrixStruct`). + * + * \tparam _CR Coefficient ring of the matrix coefficients (a model of `IntegralDomainWithoutDivision`). + * \tparam _SF Storage format (one of the constants `CGAL::OSM::COLUM` or `CGAL::OSM::ROW`). + */ + template + using Sparse_chain_type = typename SparseMatrixStruct::template Sparse_chain_type<_CR, _SF>; + + /** \brief Type of sparse matrix used to compute homology (derived from `SparseMatrixStruct`). + * + * \tparam _CR Coefficient ring of the matrix coefficients (a model of `IntegralDomainWithoutDivision`). + * \tparam _SF Storage format (one of the constants `CGAL::OSM::COLUM` or `CGAL::OSM::ROW`). + */ + template + using Sparse_matrix_type = typename SparseMatrixStruct::template Sparse_matrix_type<_CR, _SF>; + + /** + * \brief %Default constructor (empty simplicial complex of dimension `q`). + * + * Builds an empty abstract simplicial complex of dimension `q`. + */ + Abstract_simplicial_chain_complex(int q = 0) ; + + /** + * \brief Constructor from a `Mesh_object_io`. + * + * Builds the abstract simplicial complex associated to a triangular mesh (i.e. performs the down closure of cells and set the boundary matrices in any dimension). + * + * \param mesh A `Mesh_object_io` containing a triangular mesh. + */ + template + Abstract_simplicial_chain_complex(const Mesh_object_io& mesh); + + /** \brief Type of column-major chains */ + typedef Sparse_chain_type Column_chain; + /** \brief Type of row-major chains */ + typedef Sparse_chain_type Row_chain ; + /** \brief Type of column-major sparse matrices */ + typedef Sparse_matrix_type Column_matrix; + + + /* \brief Checks if `q` belongs to the range of dimensions of cells in the complex. */ + inline bool is_valid_cell_dimension(int q) const { return ((q>=0) && (q<=_dim)); } + /* \brief Checks if a cell of index `i` and dimension `q` belongs to the range of dimensions of cells in the complex. */ + inline bool is_valid_cell(size_t id, int q) const { return is_valid_cell_dimension(q) && (id < number_of_cells(q)); } + /** \brief Checks if simplex `simplex` of dimension `q` belongs to the complex. */ + inline bool is_valid_simplex(const Simplex& simplex, int q) const { return _simp2ind.at(q).find(simplex) != _simp2ind.at(q).end(); } + + /** + * \brief Assignment + */ + Abstract_simplicial_chain_complex& operator= (const Abstract_simplicial_chain_complex& complex) { + _dim = complex._dim; + _ind2simp = complex._ind2simp; + _simp2ind = complex._simp2ind; + _nb_cells = complex._nb_cells; + _d = complex._d; + return *this ; + } + + // Methods of the AbstractChainComplex concept + + /* + * \brief Returns the boundary of the cell `id_cell` in dimension `q`. + * + * Returns a copy of the column-major chain stored in the boundary matrix of dimension dim: boundary of the cell `id_cell` in dimension `q`. + * + * \param id_cell %Index of the cell. + * \param q Dimension of the cell. + * + * \return The column-major chain containing the boundary of the cell id_cell in dimension `q`. If `q` is not in the interval `[0,d]` with `d` the dimension of the complex, the function returns an empty column chain. + */ + Column_chain d(size_t id_cell, int q) const { + if ((q > 0) && (q <= _dim)) { + CGAL_precondition(is_valid_cell(id_cell, q)); + return OSM::get_column(_d[q], id_cell); + } + else + return Column_chain(0) ; + } + + /* + * \brief Returns the co-boundary of the cell `id_cell` in dimension `q`. + * + * Returns a row-major chain containing the co-boundary of the cell `id_cell` in dimension `q` (so actually a row of the boundary matrix). + * + * \warning As the boundary matrix is stored column-major, this entails crossing the full matrix to extract the row coefficients (O(number of non empty columns)) + * + * \param id_cell %Index of the cell. + * \param q Dimension of the cell. + * + * \return The row-major chain containing the co-boundary of the cell id_cell in dimension `q`. If `q` is not in the interval `[0,d]` with `d` the dimension of the complex, the function returns an empty row chain. + */ + Row_chain cod(size_t id_cell, int q) const { + if ((q < _dim) && (q >= 0)) { + CGAL_precondition(is_valid_cell(id_cell, q)); + + return OSM::get_row(_d[q+1], id_cell); + } + else + return Row_chain(0) ; + } + + /* \brief Get (unique) object Id. + * For comparison of constant references to the complex. + */ + size_t id () const { return _complex_id; } + + /* + * \brief Returns the dimension of the complex. + * + * Returns the dimension of the simplicial complex (i.e. largest dimension of cells). + * + * \return The dimension of the complex. + */ + int dimension() const { return _dim ;} + + /* + * \brief Returns the number of cells in a given dimension. + * + * \param q Dimension along which the number of cells is returned. + * + * \return Number of cells in dimension `q`. If `q` is not in the interval `[0,d]` with `d` the dimension of the complex, the function returns an empty column chain. + */ + size_t number_of_cells(int q) const { + if ((q >=0) && (q<=_dim)) + return _nb_cells[q] ; + else + return 0 ; + } + + /** \brief Returns the simplex of index i in dimension q. */ + const Simplex& index_to_cell (size_t i, int q) const { + CGAL_precondition(is_valid_cell(i,q)); + + return _ind2simp.at(q).at(i); + } + + /** \brief Returns the index of a given simplex. */ + size_t cell_to_index (const Simplex& simplex) const { + const int q(simplex.dimension()); + + CGAL_precondition(is_valid_simplex(simplex, q)); + + return _simp2ind.at(q).at(simplex); + } + + /* + * \brief Returns a constant reference to the vector of boundary matrices (along each dimension). + * + * Returns a constant reference to the vector of boundary matrices along each dimension. The q-th element of this vector is a column-major sparse matrix containing the boundaries of q-cells (i.e. rows encode q-1 cells and columns q cells). + * + * \return Returns a constant reference to the vector of column-major boundary matrices along each dimension. + */ + const std::vector & boundary_matrices() const { + return _d ; + } + + /* + * \brief Returns a copy of the dim-th boundary matrix (i.e.\ column-major matrix of \f$\partial_q\f$). + * + * It is a column-major sparse matrix containing the boundaries of q-cells (i.e. rows encode q-1 cells and columns q cells). + * + * \param q Dimension of the boundary matrix (i.e. columns will contain the boundary of dimension q cells). + * + * \return A column-major sparse matrix containing the matrix of the boundary operator of dimension q. + */ + const Column_matrix & boundary_matrix(int q) const { + return _d.at(q) ; + } + + /** + * \brief Returns the 0-simplices of a given simplex (that is, the indices of its 0-simplices). + * + * Returns the indices of the 0-simplices included in the simplex of index `id_cell` and dimension `q`. + * + * \warning This does not come to return vertex indices, as dimension 0 simplices enumerate vertices in any order. For instance, if an abstract simplicial complex is built from 3 vertices {1,2,3} such that the enumeration of dimension 0 simplices is: + * id0: 3, id1 : 2, id2: 1 + * then the bottom_faces of the 1-simplex {1,2} are two 0-simplices with id 2 and 1. + * + * \param id_cell %Index of the simplex. + * \param q Dimension of the simplex. + * + * \return A vector of 0-simplex indices. + */ + std::vector bottom_faces(size_t id_cell, int q) const { + CGAL_precondition(is_valid_cell(id_cell, q)); + + std::vector verts(_ind2simp.at(q).at(id_cell).vertices()) ; + std::vector res ; + // For each vertex in verts, compute the corresponding dimension 0 cell + for (size_t vert_id : verts) { + const size_t i(_simp2ind.at(0).at(Simplex(std::vector({vert_id})))) ; + res.push_back(i) ; + } + return res ; + } + + /* + * \brief Returns the cofaces of a given chain in dimension `q`. + * + * The resulting chain lies in dimension `q`+1 and is null if this dimension exceeds the dimension of the complex. + */ + template + Column_chain cofaces_chain (OSM::Sparse_chain chain, int q) const { + typedef OSM::Sparse_chain ChainType; + // Compute the cofaces + if (q < dimension()) { + Column_chain fstar_cofaces(number_of_cells(q+1)) ; + for (typename ChainType::const_iterator it = chain.cbegin(); it != chain.cend(); ++it) { + // Set the cofaces of it->first in dimension dim+1 + Row_chain cofaces(cod(it->first,q)) ; + for (typename Row_chain::const_iterator it2 = cofaces.cbegin(); it2 != cofaces.cend(); ++it2) + fstar_cofaces.set_coefficient(it2->first, 1) ; + } + return fstar_cofaces ; + } + else + return Column_chain(0) ; + } + +protected: + /* + * \brief Prints informations on the complex. + * + * Displays the number of cells in each dimension and the boundary matrix in each dimension. + */ + std::ostream& print_complex(std::ostream& out = std::cout) const; +public: + /* + * \brief Prints informations on the complex. + * + * Displays the number of cells in each dimension and the boundary matrix in each dimension. + */ + template + friend std::ostream& operator<< (std::ostream& out, const Abstract_simplicial_chain_complex<_CT, _SMS>& complex); + +protected: + /* \brief Dimension of the complex */ + int _dim; + /* \brief Vector of simplices along each dimension: _ind2simp.at(q) contains the vector of all simplices of dimension q */ + std::vector > _ind2simp ; + /* \brief Vector of maps associating indices to simplices in each dimension: _simp2ind.at(q) maps q-simplices to their index in _ind2simp */ + std::vector > _simp2ind ; + /* \brief Vector of number of cells in each dimension */ + std::vector _nb_cells ; + /* \brief Vector of column-major boundary matrices: _d.at(q) is the matrix of the boundary operator in dimension q. */ + mutable std::vector _d; // Boundary matrices + + // Protected methods + + /* + * \brief Method filling the matrix _d.at(q), i.e. computing the boundaries of cells of dimension q. + * + * Compute the boundary with the standard simplicial complex definition: + * \f[\partial (\{v_0,\ldots,v_q\}) = \sum_{i=0}^q (-1)^q \{v_0,\ldots, \hat{v_i},\ldots,v_q\}\f] + * + * \param q Dimension considered for computation. + */ + void compute_d(int q) const; + + /* + * \brief Method inserting a simplex (and its faces if necessary) into the abstract simplicial complex. + * + * The method (recursively) inserts the simplex into `_ind2simp` and `_simp2ind` and its faces. If the simplex is already present, the method does nothing. + * + * \warning `insert_simplex()` does not update the boundary matrix. + * + * \param tau The simplex inserted. + */ + void insert_simplex(const Simplex& tau); + +private: + /* \brief Static counter for objects ids. + * Initialized to 0. + */ + static size_t _id_generator ; // Initialisation 0 + /* \brief Unique object id (for comparison of constant references to the complex). */ + const size_t _complex_id ; +}; + +// Initialization of _id_generator +template +size_t Abstract_simplicial_chain_complex::_id_generator(0); + +//constructors +template +Abstract_simplicial_chain_complex::Abstract_simplicial_chain_complex(int q) : _dim(q), _complex_id(_id_generator++) { + // Initialize vectors of Simplices and cell counts + _ind2simp.resize(_dim + 1); + _simp2ind.resize(_dim + 1); + _nb_cells.resize(_dim + 1, 0); + // Initialize boundary matrix + _d.resize(_dim+1); +} + +template +template +Abstract_simplicial_chain_complex::Abstract_simplicial_chain_complex(const Mesh_object_io& mesh) : _complex_id(_id_generator++) { + // Initialize attributes + + _dim = std::abs(mesh.dimension()); + + // Initialize vectors of simplices and cell counts + _ind2simp.resize(_dim + 1); + _simp2ind.resize(_dim + 1); + _nb_cells.resize(_dim + 1, 0); + + // Iterate through the mesh cells and add them to the complex + for (const auto& cell : mesh.cells()) { + insert_simplex(cell) ; + } + + _d.resize(_dim+1); + for (int dim = 0; dim <= _dim; ++dim) { + compute_d(dim); + } +} + +// Recursive insertion method +template +void Abstract_simplicial_chain_complex::insert_simplex(const Simplex& tau) { + int q = tau.dimension(); + + // If the simplex is empty + if (q == -1) return; + + CGAL_precondition(is_valid_cell_dimension(q)); + + if (_simp2ind[q].find(tau) == _simp2ind[q].end()) { + size_t i = _ind2simp[q].size(); + _ind2simp[q].push_back(tau); + _simp2ind[q][tau] = i; + _nb_cells[q]++; + + std::vector bord = tau.boundary(); + for (const auto& sigma : bord) { + insert_simplex(sigma); + } + } +} + +// compute _d boundary matrix +template +void Abstract_simplicial_chain_complex::compute_d(int dim) const { + size_t nb_lignes = (dim == 0) ? 0 : _nb_cells[dim - 1]; + _d[dim] = Column_matrix(nb_lignes, _nb_cells[dim]); + + // Iterate through the cells of dimension dim + if (dim>0) { + for (size_t i = 0; i < _nb_cells[dim]; ++i) { + // Boundary of the i-th simplex of dimension dim + const Simplex& s = _ind2simp[dim][i]; + std::vector bord = s.boundary(); + + // Create a chain with the correct size + Column_chain chain(nb_lignes); + + // For each element of the boundary + for (size_t j = 0; j < bord.size(); ++j) { + auto it = _simp2ind[dim - 1].find(bord[j]); // Find the index of Simplex j + if (it != _simp2ind[dim - 1].end()) { // If Simplex j is found + size_t ind_j = it->second; // Retrieve the index of Simplex j + chain.set_coefficient(ind_j, (j % 2 == 0) ? 1 : -1); + } + else + throw "compute_d boundary simplex not found!"; + } + + // Insert the chain into the corresponding column of the delta matrix + OSM::set_column(_d[dim], i, chain); + } + } +} + +// Method to display the complex's information +template +std::ostream& Abstract_simplicial_chain_complex::print_complex(std::ostream& out_stream) const { + out_stream << "Complex dimension: " << _dim << std::endl; + + // Total number of cells + size_t nb_total_cells = 0; + for (size_t i = 0; i <= _dim; ++i) { + nb_total_cells += _nb_cells[i]; + } + out_stream << "Total number of cells: " << nb_total_cells << std::endl; + + // Cells per dimension + for (int q = 0; q <= _dim; ++q) { + out_stream << "--- dimension " << q << std::endl; + out_stream << number_of_cells(q) << " cells" << std::endl ; + for (size_t j = 0; j < _nb_cells.at(q); ++j) { + Simplex s(this->_ind2simp.at(q).at(j)); + out_stream << j << " -> " << s << " -> " << _simp2ind.at(q).at(s) << std::endl; + } + } + + // Boundary matrices + out_stream << "---------------------------" << std::endl << "Boundary matrices" << std::endl; + for (int q = 1; q <= _dim; ++q) + out_stream << "_d[" << q << "] : " << _d[q].dimensions().first << "x" << _d[q].dimensions().second << std::endl << _d[q] << std::endl; + return out_stream ; +} + +template +std::ostream& operator<< (std::ostream& out, const Abstract_simplicial_chain_complex& complex) { + return complex.print_complex(out); +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_ABSTRACT_SIMPLICIAL_CHAIN_COMPLEX_H diff --git a/HDVF/include/CGAL/HDVF/Cub_object_io.h b/HDVF/include/CGAL/HDVF/Cub_object_io.h new file mode 100644 index 000000000000..3743518a62ae --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Cub_object_io.h @@ -0,0 +1,396 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_CUB_OBJECT_H +#define CGAL_HDVF_CUB_OBJECT_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +// ------ For cubical complexes +/** \brief Type of cells coordinates in `Cub_object_io` (Khalimsky or voxel coordinates) */ +typedef std::vector IOCubCellType ; +/** \brief Type of pre-chains in `Cub_object_io` (list of cells without coefficients). */ +typedef std::vector IOCubChainType ; + +/*! + \ingroup PkgHDVFIOClasses + The class `Cub_object_io` is an intermediate %IO class, used to load binary volumes and produce cubical complexes. + */ + +template +class Cub_object_io +{ +protected: + /* Dimension of the complex. */ + int _dim = 0 ; // Dimension of the complex + /* Number of cubs in each dimension. */ + std::vector _ncubs ; // Number of cubs in each dimension + /* Size of the bounding box along each dimension. */ + std::vector _N ; // Size of BB along each dimension + /* List of cubs in each dimension. */ + std::vector _cubs ; + + /* Khalimsky or coordinates mode. + * + * If true, `cubs` contains cubical cells encoded with Khalismky coordinates, if false, `cubs` contains voxel coordinates (i.e. cells of dimension `dimension()`). + */ + bool _khalimsky ; + +public: + /** \brief Returns the dimension of the complex. */ + int dimension() const { return _dim; } + + /** \brief Resets the `Cub_object_io` and sets the dimension of the cubical complex and its size. */ + int init(int dim, const std::vector& size_bb) { + _dim = dim; + _N = size_bb; + _ncubs.resize(_dim+1); + } + + /** \brief Returns the number of cubs in each dimension. */ + const std::vector& number_of_cubs_by_dimension() const { return _ncubs; } + + /** \brief Returns the size of the bounding box along each dimension. */ + const std::vector& N() const { return _N; } + + /** \brief Returns the vector of cubs. */ + const std::vector& cubs() const { return _cubs; } + + /** \brief Returns the ith cub. */ + const IOCubCellType& cub(size_t i) const { return _cubs.at(i); } + + /** \brief Returns the total number of cubs. */ + size_t number_of_cubs() const { return _cubs.size(); } + + /** \brief Adds a cub (given by its Khalimsky coordinates) to the `Cub_object_io`. + * + * \param coords boolean index of the cub to add. + */ + void add_cub(const std::vector& coords) { + _cubs.push_back(coords); + const int dtmp(cub_dim(coords)) ; + (_ncubs)[dtmp] += 1 ; + } + + /** \brief %Default constructor. + * + * Create an empty `Cub_object_io` of dimension 3. + */ + Cub_object_io() : _dim(3), _ncubs(std::vector(4)), _N(std::vector(4)), _khalimsky(false) {} + + /** + * \brief Constructor from a vector of cells. + * + * Cells coordinates are given in Khalimsky coordinates if the boolean `khal` is `true`, or as integer coordinates of voxels of dimension `_dim` otherwise. + * + */ + Cub_object_io(int d, const std::vector &vcubs, bool khal = false) : _dim(d), _ncubs(std::vector(d+1)), _N(std::vector(d+1)), _cubs(vcubs), _khalimsky(khal) { + check_dimension() ; + } + + + // Mesh operations + /** \brief Removes all cubs of the `Cub_object_io`. */ + void clear() { _cubs.clear() ; for (size_t i=0; i<_dim; ++i) _ncubs[i] = 0 ; } + + + /** \brief Adds one empty cell of higher dimension all around the complex. + * + * The function enlarges the bounding box with 1 cell of higher dimension on each of its sides. Cubs coordinates are shifted accordingly. + */ + void frame() { + // Enlarge the bouding box to add 1 voxel around + // Enlarge the BB along each dimension + for (size_t i=0; i<_N.size(); ++i) + { + if (_khalimsky) + _N.at(i) += 4 ; + else + _N.at(i) += 2 ; + } + // Shift cells coordinates + for (size_t i = 0; i<_cubs.size(); ++i) + { + for (int k = 0; k<_dim; ++k) + { + if (_khalimsky) + _cubs.at(i).at(k) += 2 ; + else + ++_cubs.at(i).at(k) ; + } + } + } + + /** \brief Imports a %PGM file (%PGM version 2). + * + * \param filename Name of the %PGM file. + * \param khal If true, Khalimsky coordinates of voxels are stored (for `PRIMAL` construction of a cubical complex), otherwise, integer coordinates of voxels are stored (for `DUAL` construction). + * + * \exception File_not_found If `filename` does not exist, raise a `%std::runtime_error` exception. + * + * \exception Incoherent_dimension Raises a `%std::runtime_error` if dimensions of the `Traits` and dimension of the %PGM file do not match. + * + * \exception Invalid_pgm_version If the version stored in the %PGM file is not P2, raises a `%std::runtime_error`. + */ + bool read_pgm(const std::string &filename, bool khal = false) { + _khalimsky = khal ; + size_t row = 0, col = 0, numrows = 0, numcols = 0; + std::ifstream infile(filename); + if(!infile.is_open()) { + std::cerr << "Warning: cannot open file " << filename << std::endl ; + throw std::runtime_error("read_pgm: file "+filename+" not found"); + // failed to open the file + return false; + } + + std::stringstream ss; + std::string inputLine = ""; + + // First line : version + getline(infile,inputLine); + if(inputLine.compare("P2") != 0) { + std::cerr << "Version error" << std::endl; + throw std::runtime_error("Invalid PGM version."); + } + else std::cout << "Version : " << inputLine << std::endl; + + + // Second line: dimensions + getline(infile,inputLine); + std::vector sizes ; + + size_t tmp ; + std::stringstream sseizes (inputLine); + while (sseizes >> tmp) + sizes.push_back(tmp) ; + + std::cout << "dimensions : " ; + for (size_t i=0; i(_dim) ; + for (size_t i=0; i<_dim; ++i) + _N.at(i) = sizes.at(i) ; + + // Throw away next data (255) + + getline(infile,inputLine); + + // Remainder: data + // Read by decreasing dimension + // In order to get : read row, then column, then depth... + + // Continue with a stringstream + ss << infile.rdbuf(); + + size_t NN = 1 ; + for (int i=0; i<_dim; ++i) + NN = NN * _N.at(i) ; + + // Following lines : data + for(size_t i = 0; i < NN; ++i) + { + ss >> tmp ; + if (tmp) // pixel on + { + _cubs.push_back(index_to_coords(i, khal)) ; + ++_ncubs[_dim] ; + } + } + + if (khal) + { + // Change _N to Khalimsky maxima + for(int i=0; i<_dim; ++i) + _N.at(i) = 2*_N.at(i)+1 ; + } + + infile.close(); + } + + // bool write_pgm(const std::string &filename) ; + // TODO... + + /** \brief Imports a %CUB file. + * + * \param filename Name of the %CUB file (cells are described in Khalimsky coordinates). + * \param khalimsky If true, Khalimsky coordinates are loaded (for `PRIMAL` construction of a cubical complex), otherwise, checks that all cells provided are of maximal dimension and load the integer coordinates of voxels (for `DUAL` construction). + * + * \exception File_not_found If `filename` does not exist, raise a `%std::runtime_error` exception. + * + * \exception Incoherent_dimension Raises a `%std::runtime_error` if dimensions of the `Traits` and of the %CUB file do not match. + * + * \exception Invalid_line If a line is invalid (does not contain a sequence of coordinates), raises a `%std::runtime_error`. + * + * \exception Incoherent_cell A `%std::runtime_error` is raised if `khalimsky` is false but a cell of non-maximal %dimension is encoutered. + */ + bool read_cub(const std::string &filename, bool khalimsky = false) { + // 0 - open input file + std::ifstream infile(filename); + if(!infile.is_open()) { + // failed to open the file + std::cerr << "Warning: file " << filename << " does not exist" << std::endl ; + throw std::runtime_error("read_cub: file "+filename+" not found"); + return false; + } + std::size_t line_number = 0; + while ( !(infile.eof()) ) + { + ++line_number; + std::string line; + getline( infile, line ); + // Check that line is sanitized. If not, throw. + for ( size_t i = 0; i < line.size(); ++ i ) { + if ( ! ( std::isspace(line[i]) || std::isdigit(line[i]) ) ) { + std::cerr << "Fatal Error:\n Cannot parse line #" << line_number << " of " << filename << "\n"; + std::cerr << " --> " << line << "\n"; + throw std::runtime_error("File Parsing Error: Invalid line "+std::to_string(i)); + } + } + std::istringstream is( line ); + + if (line_number == 1) { // Header 1 + is >> _dim ; + if (Traits::Dimension::value != _dim) { + std::cerr << "read_cub error: dimensions of the CUB object and of the Traits differ" << std::endl; + throw std::runtime_error("read_cub error: dimensions of the CUB object and of the Traits differ"); + } + } + else if (line_number == 2) // Header 2 + { + for (int i=0; i<_dim; ++i) + is >> _N[i] ; + } + else // Cub line + { + IOCubCellType cub ; + + size_t v; + while ( is >> v ) + cub.push_back(v); + if (cub.size() != _dim) + std::cout << "Discard line " << line_number << " cub of invalid dimension" << std::endl ; + else + { + // Add this cub to _cubs + ++_ncubs[cub_dim(cub)] ; + + if (khalimsky) + _cubs.push_back(cub) ; + else + { + if (cub_dim(cub) != _dim) + throw std::runtime_error("read_cub: cell of dimension "+std::to_string(cub_dim(cub))+" incompatible with integer coordinates output"); + size_t ind(khal_to_index(cub)) ; + _cubs.push_back(index_to_coords(ind, false)) ; + } + } + } + } + infile.close() ; + return true ; + } + + // bool write_cub(const std::string &filename) ; + // TODO + + /** \brief Prints synthetic informations related to the object. */ + void print_infos (size_t level = 0) const { + std::cout << "Cub_object_io infos - _dim : " << _dim << ", cubs : " << _cubs.size() << std::endl ; + for (int q=0; q < _dim; ++q) + std::cout << "\tSize along _dim " << q << " : " << _N[q] << std::endl ; + for (int q = 0; q <= _dim; ++q) + std::cout << "Cubs of dim " << q << " : " << _ncubs[q] << std::endl ; + if (level == 1) // Print coordinates of cubes + { + for (size_t i=0; i<_cubs.size(); ++i) + { + for (size_t j : _cubs.at(i)) + std::cout << j << " " ; + std::cout << std::endl ; + } + } + } + +private: + void check_dimension() { + if (_khalimsky) + { + for (IOCubCellType c : _cubs) + ++_ncubs[cub_dim(c)] ; + } + else + { + _ncubs[_dim] = _cubs.size() ; + } + } + + inline int cub_dim (IOCubCellType c) { + int q = 0 ; + for (size_t i : c) + { + if (i%2 == 1) + ++q ; + } + return q ; + } + + inline IOCubCellType index_to_coords(size_t i, bool khalimsky = false) { + IOCubCellType coords ; + // Convert index in binary object to size_t coordinates + for (int q = 0; q < _dim; ++q) + { + coords.push_back(i % _N[q]) ; + i = i / (_N[q]) ; + } + if (khalimsky) + { + // Convert int coordinates to Khalimsky + for (int q = 0; q < _dim; ++q) + { + coords.at(q) = 2*coords.at(q)+1 ; + } + } + return coords ; + } + + inline size_t khal_to_index (const IOCubCellType& coords) { + size_t cell_index(0); + for (int i = 0; i < _dim; ++i) { + cell_index += coords[i] * _N[i]; + } + return cell_index; + } +} ; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + + +#endif // CGAL_HDVF_CUB_OBJECT_H diff --git a/HDVF/include/CGAL/HDVF/Cubical_chain_complex.h b/HDVF/include/CGAL/HDVF/Cubical_chain_complex.h new file mode 100644 index 000000000000..44b112f0daa9 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Cubical_chain_complex.h @@ -0,0 +1,1054 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_CUBICAL_CHAIN_COMPLEX_H +#define CGAL_HDVF_CUBICAL_CHAIN_COMPLEX_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFChainComplexesClasses + + The class `Cubical_chain_complex` represents (topological) chain complexes associated to cubical complexes. + + A cubical complex is a set of "square" cells such that: all the faces of a given cell also belong to the complex and any two cells intersect exactly along a common face (see the user manual of \ref secHDVFcubical_chain_complex "cubical chain complexes" for more details). + + \cgalModels{GeometricChainComplex} + + \tparam CoefficientRing a model of the `IntegralDomainWithoutDivision` concept. + \tparam Traits a geometric traits class model of the `HDVFTraits` concept. + \tparam SparseMatrixStruct a model of `SparseMatrixStructure` providing templates of sparse chains and sparse matrices built over a given model of `SparseChain`. + */ + +template> +class Cubical_chain_complex { +public: + /*! \brief Type of coefficients used to compute homology. */ + typedef CoefficientRing Coefficient_ring; + + /* \brief Type of sparse matrix structure used to compute homology. */ + typedef SparseMatrixStruct Sparse_matrix_structure; + + /* \brief Type of sparse chain used to compute homology. */ + template + using Sparse_chain_type = typename SparseMatrixStruct::template Sparse_chain_type<_CT, _SF>; + + /* \brief Type of sparse matrix used to compute homology. */ + template + using Sparse_matrix_type = typename SparseMatrixStruct::template Sparse_matrix_type<_CT, _SF>; + + /* \brief Type of vertex coordinates */ + typedef typename Traits::Point Point ; + /* \brief Type of vtk export vertex coordinates */ + typedef typename Traits::Point3 Point3 ; +private: + /* \brief Vector of VTK types associated to cells in each dimension + e.g. {1, 3, 8, 11} */ + static const std::vector VTK_cubtypes ; + +public: + /** \brief Type used to encode primal or dual construction. + * Values can be either `PRIMAL` or `DUAL`. + */ + enum Cubical_complex_primal_dual {PRIMAL, DUAL}; + + /** + * \brief %Default constructor (empty cubical complex). + */ + Cubical_chain_complex() : _complex_id(_id_generator++) {} ; + + /** + * \brief Constructor from a `Cub_object_io` (builds `PRIMAL` or `DUAL` associated complex depending on `type`). + * + * Builds the cubical complex associated to a a set of cells (vertices, edges, squares, cubes...), ie.\ performs the down closure of cells and set the boundary matrices in any dimension. Given a set of cells: + * + * - if the `type` is PRIMAL, the constructor builds the associated complex as such (see below middle), which comes to encode \f$3^q-1\f$ connectivity (with \f$q\f$ the dimension of the complex) + * - if the `type` is DUAL and the Cub_object_io contains only cells of maximal dimension (ie.\ binary object), the constructor build the dual associated complex (see below right), which comes to encode \f$2q\f$ connectivity (with \f$q\f$ the dimension of the complex) + * + * + * + * \param cub A Cub_object_io containing a set of "cubical" cells. + * \param type Type of construction used (PRIMAL or DUAL). + */ + Cubical_chain_complex(const Cub_object_io& cub,Cubical_complex_primal_dual type); + + // /* \brief Friend class `Duality_cubical_complex_tools` provides tools for Alexander duality. */ + // friend Duality_cubical_complex_tools ; + + /** \brief Type of column-major chains */ + typedef Sparse_chain_type Column_chain; + /** \brief Type of row-major chains */ + typedef Sparse_chain_type Row_chain ; + /** \brief Type of column-major sparse matrices */ + typedef Sparse_matrix_type Column_matrix; + + /* \brief Checks if `q` belongs to the range of dimensions of cells in the complex. */ + bool is_valid_cell_dimension(int q) const { return ((q>=0) && (q<=_dim)); } + /* \brief Checks if a cell of "basis" index `i` and dimension `q` belongs to the range of dimensions of cells in the complex. */ + bool is_valid_cell(size_t id, int q) const { return is_valid_cell_dimension(q) && (id < number_of_cells(q)); } + /** \brief Checks if a cell given in Khalimsky coordinates has the right dimension. */ + bool is_valid_cell(const std::vector& cell ) const { return cell.size() == _dim; } + /** \brief Checks if an index belongs to the range of the Boolean vector encoding the complex. */ + bool is_valid_cell(size_t i) const { return i < _P[_dim]; } + + /** + * \brief Assignment + */ + Cubical_chain_complex& operator=(const Cubical_chain_complex& complex) { + _dim = complex._dim; + _size_bb = complex._size_bb; + _P = complex._P; + _cells = complex._cells; + _base2bool = complex._base2bool; + _bool2base = complex._bool2base; + _visited_cells = complex._visited_cells; + return *this; + } + + // Methods of the AbstractChainComplex concept + + /* + * \brief Returns the boundary of the cell id_cell in dimension `q`. + * + * Returns a copy of the column-major chain stored in the boundary matrix of dimension q: boundary of the cell `id_cell` in dimension `q`. If `q` is out the range of dimensions of the complex cells, returns a null matrix. + * + * \param id_cell Index of the cell. + * \param q Dimension of the cell. + * + * \return The column-major chain containing the boundary of the cell `id_cell` in dimension `q`. + */ + Column_chain d(size_t id_cell, int q) const { + if ((q > 0) && (q<= _dim)) { + CGAL_precondition(is_valid_cell(id_cell, q)); + return OSM::cget_column(_d[q], id_cell); + } + else + return Column_chain(0) ; + } + + /* + * \brief Returns the co-boundary of the cell id_cell in dimension `q`. + * + * Returns a row-major chain containing the co-boundary of the cell `id_cell` in dimension `q` (so actually a row of the boundary matrix). If `q` is out the range of dimensions of the complex cells, returns a null matrix. + * + * \warning As the boundary matrix is stored column-major, this entails crossing the full matrix to extract the row coefficients (O(number of non empty columns)) + * + * \param id_cell Index of the cell. + * \param q Dimension of the cell. + * + * \return The row-major chain containing the co-boundary of the cell `id_cell` in dimension `q`. + */ + Row_chain cod(size_t id_cell, int q) const { + if ((q < _dim) && (q>=0)) { + CGAL_precondition(is_valid_cell(id_cell, q)); + return OSM::get_row(_d[q+1], id_cell); + } + else + return Row_chain(0) ; + } + + /* + * \brief Returns the number of cells in a given dimension. + * + * If `q` is out the range of dimensions of the complex cells, returns 0. + * + * \param q Dimension along which the number of cells is returned. + * + * \return Number of cells in dimension q. + */ + size_t number_of_cells(int q) const { + if ((q >=0) && (q <= _dim)) + return _base2bool.at(q).size() ; + else + return 0 ; + } + + /* + * \brief Returns the dimension of the complex. + * + * Returns the dimension of the cubical complex. + * + * \return The dimension of the complex.. + */ + int dimension() const { + return _dim ; + } + + /** \brief Get the size of the Khalimsky bounding box. + * + * The Khalimsky coordinate of a cell `c` in dimension `q` is strictly lesser than `size_bb().at(q)`. + */ + std::vector size_bb() const { + return _size_bb; + } + + /** \brief Returns the total size of the complex (size of its boolean vector representation). + * + * Khalimsky coordinates in dimension `q` are bounded above by `size_bb().at(q)`; the size of the boolean vector representing the complex is the product of these sizes. + */ + size_t size() const { + return _P.at(dimension()); + } + + /** \brief Returns Khalimsky coordinates of the cell of "basis index" i in dimension q. + * In `AbstractChainComplex`, cells of dimenson `q` are identified by their index in the basis of \f$q\f$-cells together with their dimension. In the context of cubical complexes, this index is called "basis index" (to distinguish from its "Boolean index", see \ref secHDVFcubical_chain_complex "user manual" for details). + * + * \param i "basis index" of a cell. + * \param q dimension of the cell. + * \returns Khalimsky coordinates of the cell. + */ + std::vector index_to_cell (size_t i, int q) const { + CGAL_precondition(is_valid_cell(i, q)); + const size_t id_bool(_base2bool.at(q).at(i)); + return bindex_to_cell(id_bool); + } + + /** \brief Returns the "basis index" of a cell given by its Khalimsky coordinates. + * + * In `AbstractChainComplex`, cells of dimenson `q` are identified by their index in the basis of \f$q\f$-cells together with their dimension. In the context of cubical complexes, this index is called "basis index" (to distinguish from its "Boolean index", see \ref secHDVFcubical_chain_complex "user manual" for details). + * + * \param cell Khalimsky coordinates of a cell (of dimension \f$q\f$). + * \returns The "basis index" of the cell among \f$q\f$-cells. + */ + size_t cell_to_index (const std::vector& cell) const { + CGAL_precondition(is_valid_cell(cell)); + const size_t id_bool(cell_to_bindex(cell)); + const int q(dimension(cell)); + return (_bool2base.at(q)).at(id_bool); + } + + /** \brief Returns the dimension of a cell (given in Khalimsky coordinates). + * + * \exception Cell_of_invalid_dimension Raises a `%std::invalid_argument` if the dimension of Khalimsky coordinates of the `cell` differs from the dimension of the complex. + */ + int dimension(const std::vector& cell) const; + + /** \brief Returns Khalimsky coordinates of the cell of Boolean index i. + * + * In `Cubical_chain_complex`, a cells of dimenson `q`, besides its index in the basis of \f$q\f$-cells, has an index in the vectorisation of the Khalimsky representation, called its "Boolean index" (see \ref secHDVFcubical_chain_complex "user manual" for details). + * + * \param i The "Boolean" index of a cell. + * \returns Khalimsky coordinates of the cell. + */ + std::vector bindex_to_cell (size_t i) const { + CGAL_precondition(is_valid_cell(i)); + std::vector khal(_dim); + for (size_t k = 0; k < _dim; ++k) { + khal[k] = i % _size_bb[k]; + i /= _size_bb[k]; + } + return khal; + } + + /** \brief Returns the Boolean index of a cell given by its Khalimsky coordinates. + * + * In `Cubical_chain_complex`, a cells of dimenson `q`, besides its index in the basis of \f$q\f$-cells, has an index in the vectorisation of the Khalimsky representation, called its "Boolean index" (see \ref secHDVFcubical_chain_complex "user manual" for details). + * + * \param cell Khalimsky coordinates of a cell. + * \returns The "Boolean" index of the cell. + */ + size_t cell_to_bindex (const std::vector& cell) const { + CGAL_precondition(is_valid_cell(cell)); + size_t cell_index = 0; + for (size_t i = 0; i < _dim; ++i) { + cell_index += cell[i] * _P[i]; + } + + CGAL_precondition(is_valid_cell(cell_index)); + return cell_index; + } + + /* + * \brief Returns a constant reference to the vector of boundary matrices (along each dimension). + * + * Returns a constant reference to the vector of boundary matrices along each dimension. The q-th element of this vector is a column-major sparse matrix containing the boundaries of q-cells (ie.\ rows encode q-1 cells and columns q cells). + * + * \return Returns a constant reference to the vector of column-major boundary matrices along each dimension. + */ + const std::vector & boundary_matrices() const { + return _d ; + } + + /* + * \brief Returns a copy of the dim-th boundary matrix (ie.\ column-major matrix of \f$\partial_q\f$). + * + * It is a column-major sparse matrix containing the boundaries of dim-cells (ie.\ rows encode q-1 cells and columns q cells). + * + * \param q Dimension of the boundary matrix (ie.\ columns will contain the boundary of dimension q cells). + * + * \return A column-major sparse matrix containing the matrix of the boundary operator of dimension `q`. + */ + const Column_matrix & boundary_matrix(int q) const { + CGAL_precondition(is_valid_cell_dimension(q)); + return _d.at(q) ; + } + + + /* + * \brief Returns dimension 0 cell indices included in the cell with index `id_cell` of dimension `q`. + * + * Returns the dimension 0 vertex indices included in the cell with index `id_cell` of dimension `q`. + * + * \param id_cell Index of the cell. + * \param q Dimension of the cell. + * + * \return A vector of 0-cell indices. + */ + std::vector bottom_faces(size_t id_cell, int q) const { + + CGAL_precondition(is_valid_cell(id_cell, q)); + + // Khalimsky coordinates of the cell + const std::vector coords(bindex_to_cell(_base2bool.at(q).at(id_cell))) ; + return khal_to_verts(coords) ; + } + + /* + * \brief Returns the cofaces of a given chain in dimension `q`. + * + * The resulting chain lies in dimension `q+1` and is null if this dimension exceeds the dimension of the complex. + */ + template + Column_chain cofaces_chain (OSM::Sparse_chain chain, int q) const { + typedef OSM::Sparse_chain ChainType; + // Compute the cofaces + if (q < dimension()) { + Column_chain fstar_cofaces(number_of_cells(q+1)) ; + for (typename ChainType::const_iterator it = chain.cbegin(); it != chain.cend(); ++it) { + // Set the cofaces of it->first in dimension dim+1 + Row_chain cofaces(cod(it->first,q)) ; + for (typename Row_chain::const_iterator it2 = cofaces.cbegin(); it2 != cofaces.cend(); ++it2) + fstar_cofaces.set_coefficient(it2->first, 1) ; + } + return fstar_cofaces ; + } + else + return Column_chain(0) ; + } + +protected: + /* + * \brief Prints informations on the complex. + * + * Displays the number of cells in each dimension and the boundary matrix in each dimension. + */ + std::ostream& print_complex(std::ostream& out = std::cout) const { + for (int q = 0; q <= _dim; ++q) { + out << "-------- dimension " << q << std::endl; + out << "cells of dimension " << q << " : " << _base2bool.at(q).size() << std::endl; + for (size_t id_base = 0; id_base < _base2bool.at(q).size(); ++id_base) { + size_t id_bool = _base2bool.at(q).at(id_base); + std::vector khal = bindex_to_cell(id_bool); + out << id_base << " -> " << id_bool << " -> " << _bool2base.at(q).at(id_bool) << " -> "; + for (size_t k : khal) out << k << " "; + out << std::endl; + } + + if (_base2bool[q].size() > 0 && q <= _dim) { + out << "border matrix of dimension " << q << std::endl; + out << _d[q] << std::endl; + } + } + return out; + } + +public: + /* + * \brief Prints informations on the complex. + * + * Displays the number of cells in each dimension and the boundary matrix in each dimension. + */ + template + friend std::ostream& operator<<(std::ostream& out, const Cubical_chain_complex<_CT,_Traits,_SMS>& complex) ; + + /* \brief Gets (unique) object Id. + * For comparison of constant references to the complex. + */ + size_t id () const { return _complex_id; } + +protected: + /* Compute the point associated to the ith 0-cell */ + Point compute_point(size_t i) const { + const std::vector coords(bindex_to_cell(_base2bool.at(0).at(i))) ; + std::vector res ; + for (size_t c : coords) + res.push_back(c/2. + .5) ; + // If necessary "fill" with zeros + if (res.size() < Traits::Dimension::value){ + for (int i=res.size(); i& points() const { + return _points; + } + + // END Methods of the Cubical_chain_complex concept + + // VTK export + + /* + * \brief Exports a cubical complex (plus, optionally, labels) to a VTK file. + * + * The method generates legacy text VTK files. Labels are exported as such in a VTK property, together with CellID property, containing the index of each cell. + * + * \tparam LabelType Type of labels provided (default: int). + * + * \param K Cubical complex exported. + * \param filename Output file root (output filenames will be built from this root). + * \param labels Pointer to a vector of labels in each dimension. (*labels).at(q) is the set of integer labels of cells of dimension q. If labels is NULL, only CellID property is exported. + * \param label_type_name Typename used in vtk export (e.g. "int" or "unsigned_long", see VTK manual ). + */ + template + static void chain_complex_to_vtk(const Cubical_chain_complex &K, const std::string &filename, const std::vector > *labels=NULL, std::string label_type_name = "int") { + bool with_scalars = (labels != NULL) ; + + // Load out file... + std::ofstream out ( filename, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "CubComplex_to_vtk. Fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + // Header + out << "# vtk DataFile Version 2.0" << std::endl ; + out << "generators" << std::endl ; + out << "ASCII" << std::endl ; + out << "DATASET UNSTRUCTURED_GRID" << std::endl ; + + // Points + size_t nnodes = K.number_of_cells(0) ; + out << "POINTS " << nnodes << " double" << std::endl ; + for (size_t n = 0; n < nnodes; ++n) { + Point3 p(Traits::to_point3(K.point(n))) ; + out << p ; + out << std::endl ; + } + + size_t ncells_tot = 0, size_cells_tot = 0 ; + std::vector types ; + std::vector scalars ; + std::vector ids ; + // all cells must be printed + { + // Cells up to dimension 3 + // Size : size of a cell of dimension q : 2^q + for (int q=0; q<=K.dimension(); ++q) { + ncells_tot += K.number_of_cells(q) ; + const size_t size_cell = 1<::VTK_cubtypes.at(0)) ; + if (with_scalars) + { + scalars.push_back((*labels).at(0).at(i)) ; + ids.push_back(i) ; + } + } + // Cells of higher dimension + for (int q=1; q<=K.dimension(); ++q) { + const size_t size_cell = 1< verts(K.khal_to_verts(K.bindex_to_cell(K._base2bool.at(q).at(id)))) ; + out << size_cell << " " ; + for (size_t i : verts) + out << i << " " ; + out << std::endl ; + types.push_back(Cubical_chain_complex::VTK_cubtypes.at(q)) ; + if (with_scalars) + { + scalars.push_back((*labels).at(q).at(id)) ; + ids.push_back(id) ; + } + } + } + + // CELL_TYPES + out << "CELL_TYPES " << ncells_tot << std::endl ; + for (int t : types) + out << t << " " ; + out << std::endl ; + } + + if (with_scalars) { + // CELL_LABEL + out << "CELL_DATA " << ncells_tot << std::endl ; + out << "SCALARS Label " << label_type_name << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (LabelType s : scalars) + out << s << " " ; + out << std::endl ; + // CELL_IDs + out << "SCALARS CellId " << "int" << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (size_t i : ids) + out << i << " " ; + out << std::endl ; + } + out.close() ; + } + + /* + * \brief Exports a chain over a cubical complex to a VTK file. + * + * The method generates legacy text VTK files. All the cells of the chain with non zero coefficient are exported. If a cellId is provided, labels are exported in a VTK property (2 for all cells, 0 for cell of index cellId). The index of each cell is exported in a CellID property. + * + * \param K Cubical complex exported. + * \param filename Output file root (output filenames will be built from this root). + * \param chain Sparse_chain exported (all the cells with non-zero coefficients in the chain are exported to vtk). + * \param q Dimension of the cells of the chain. + * \param cellId If different from MAX_SIZE_T, labels are exported to distinguish cells of the chain (label 2) from cellId cell (label 0). + */ + static void chain_to_vtk(const Cubical_chain_complex &K, const std::string &filename, const typename Cubical_chain_complex::template Sparse_chain_type& chain, int q, size_t cellId = -1) ; + +protected: + // Methods to access data + + /* \brief Get _P (coefficients used for the vectorisation of the Khalimsky Boolean matrix) */ + std::vector get_P() const { + return _P; + } + + /* \brief Get the Boolean vector of the complex in Khalimsky coordinates */ + std::vector get_cells() const { + return _cells; + } + + /* \brief Get the vector of vectors _base2bool */ + std::vector > get_base2bool() const { + return _base2bool; + } + + /* \brief Get the vector of maps _bool2base */ + std::vector > get_bool2base() const { + return _bool2base; + } + + /* \brief Method computing the boundary of a given cell */ + Column_chain boundary_cell(size_t index_base, int dim) const { + // Ensure dim is within valid range + if (dim < 0 || dim >= _base2bool.size()) { + throw std::out_of_range("Invalid dimension: " + std::to_string(dim)); + } + + // Check if index_base is within bounds of _base2bool[dim] + if (index_base < 0 || index_base >= _base2bool[dim].size()) { + throw std::out_of_range("index_base " + std::to_string(index_base) + " not found in _base2bool[" + std::to_string(dim) + "]"); + } + + size_t nb_lignes = (dim == 0) ? 0 : number_of_cells(dim - 1); + Column_chain boundary(nb_lignes); + + size_t index_bool = _base2bool[dim][index_base]; + std::vector c = bindex_to_cell(index_bool); + + CoefficientRing sign = 1; + for (size_t i = 0; i < _dim; ++i) { + if (c[i] % 2 == 1) { + // compute the coefficient based on the number of odd entries in c from 0 to i-1 + sign *= -1; + + size_t cell1 = index_bool + _P[i]; + size_t cell2 = index_bool - _P[i]; + + // Insert the coefficient in the corresponding row of the boundary matrix + if (cell1 >= 0 && cell1 < _cells.size()) { + size_t index = _bool2base[dim - 1].at(cell1); + boundary.set_coefficient(index, sign); + } + if (cell2 >= 0 && cell2 < _cells.size()) { + size_t index = _bool2base[dim - 1].at(cell2); + boundary.set_coefficient(index, -sign); + } + } + } + + return boundary; + } + + /* \brief Compute vertices of a cell given by its Khalimsky coordinates */ + std::vector khal_to_verts(std::vector c) const { + std::vector > vertices, vertices_tmp ; + // Vertices are obtained by cartesian products + for (size_t i=0; i<_dim; ++i) { + if ((c[i]%2) == 1) { + if (vertices.size()==0) { + vertices.push_back(std::vector(1,c[i]-1)) ; + vertices.push_back(std::vector(1,c[i]+1)) ; + } + else { + vertices_tmp.clear() ; + for (std::vector vert : vertices) { + std::vector tmp(vert) ; + tmp.push_back(c[i]-1) ; + vertices_tmp.push_back(tmp) ; + } + for (std::vector vert : vertices) { + std::vector tmp(vert) ; + tmp.push_back(c[i]+1) ; + vertices_tmp.push_back(tmp) ; + } + vertices = vertices_tmp ; + } + } + else { + if (vertices.size() == 0) + vertices.push_back(std::vector(1,c[i])) ; + else { + vertices_tmp.clear() ; + for (std::vector vert : vertices) { + std::vector tmp(vert) ; + tmp.push_back(c[i]) ; + vertices_tmp.push_back(tmp) ; + } + vertices = vertices_tmp ; + } + } + } + std::vector vertices_id ; + for (std::vector vert : vertices) { + vertices_id.push_back(_bool2base[0].at(cell_to_bindex(vert))) ; + } + return vertices_id ; + } + + // Member data +protected: + /* \brief Dimension of the complex */ + int _dim; + /* \brief Size of the Khalimsky bounding box \f$(N_1,\ldots,N_{\mathrm{\_dim}})\f$*/ + std::vector _size_bb; + /* \brief Vector of coefficients used for vectorization of the Boolean representation */ + std::vector _P; + /* \brief Vectorized Boolean representation of the complex (true if a cell is present, false otherwise) */ + std::vector _cells; + /* \brief Maps from base indices to Boolean indices (ie.\ indices in _cell) in each dimension */ + std::vector> _base2bool; + /* \brief Maps from Boolean indices (ie.\ indices in _cells) to base indices in each dimension */ + std::vector> _bool2base; + /* \brief Vector of boundary matrices in each dimension */ + std::vector _d; + /* \brief Vector of points (vertices coordinates). */ + std::vector _points; +private: + std::vector _visited_cells; // Internal flag + /* \brief Static counter for objects ids. + * Initialized to 0. + */ + static size_t _id_generator ; + /* \brief Unique object id (for comparison of constant references to the complex). */ + const size_t _complex_id ; + + // Protected methods +protected: + /* Initialize _cells, _base2bool and _bool2base */ + void initialize_cells(const Cub_object_io& cub,Cubical_complex_primal_dual type); + + /* \brief compute the dimension of a cell (given by its Boolean index) */ + int dimension(size_t cell_index) const { + return dimension(bindex_to_cell(cell_index)) ; + } + + /* \brief Computes the boundary matrix of dimension q */ + void compute_d(int q) ; + + /* \brief Insert a cell into the complex (and its faces if necessary) */ + void insert_cell(size_t cell); + + /* \brief Compute (the Boolean indices of) cells belonging to the boundary of `cell` (given by its Boolean index) */ + std::vector compute_boundaries(size_t cell) const; + +}; + +// Initialization of static VTK_cubtypes +template const +std::vector Cubical_chain_complex::VTK_cubtypes({1, 3, 8, 11}) ; + +// Initialization of _id_generator +template +size_t Cubical_chain_complex::_id_generator(0) ; + +// Constructor implementation +template +Cubical_chain_complex::Cubical_chain_complex(const Cub_object_io& cub, Cubical_complex_primal_dual type) : _dim(cub.dimension()), _size_bb(_dim+1), _P(_dim+1,1), _base2bool(_dim+1), _bool2base(_dim+1), _complex_id(_id_generator++) { + // Initialize _size_bb and _P + if (type==PRIMAL) + _size_bb = cub.N(); + else { + for (int q=0; q<_dim; ++q) + _size_bb.at(q) = 2*cub.N().at(q)+1 ; + } + + for (size_t i = 1; i <= _dim; ++i) { + _P[i] = _P[i - 1] * _size_bb[i - 1]; + } + + _cells.resize(_P[_dim], false) ; + + // Initialize _visited_cells + _visited_cells.resize(_P[_dim], false) ; + + // Initialize _cells, _base2bool, and _bool2base + initialize_cells(cub,type); + + // Initialize _d + _d.resize(_dim+1); + for (int q = 0; q <= _dim; ++q) { + compute_d(q); + } + + // Initialize _points + compute_points(); +} + +// initialize_cells implementation +template +void Cubical_chain_complex::initialize_cells(const Cub_object_io& cub, Cubical_complex_primal_dual type) { + if (type == PRIMAL) { + for (size_t i=0; i coords(cub.cub(i)) ; + // compute the coordinates of the voxel in the dual complex + for (size_t i=0; i<_dim; ++i) + coords.at(i)*=2 ; + const size_t cell_index(cell_to_bindex(coords)) ; + + _cells.at(cell_index) = true ; + // Add the cell in _base2bool and _bool2base + const int dim(dimension(coords)) ; + const size_t n(_base2bool.at(dim).size()) ; + _base2bool.at(dim).push_back(cell_index) ; + _bool2base.at(dim)[cell_index] = n ; + } + + // for the cells with dim>0 + for (int q = 1; q <= _dim; ++q) { + + for (size_t i = 0; i < _P[_dim]; ++i) { + + if (dimension(bindex_to_cell(i)) == q) { + std::vector boundaries = compute_boundaries(i); + bool all_boundaries_present = true; + for (const auto& boundary_cell : boundaries) { + if (!_cells[boundary_cell]) { + all_boundaries_present = false; + break; + } + } + if (all_boundaries_present) { + _cells.at(i)= true ; + // Add the cell in _base2bool and _bool2base + const int dim(dimension(i)) ; + const size_t n(_base2bool.at(dim).size()) ; + _base2bool.at(dim).push_back(i) ; + _bool2base.at(dim)[i] = n ; + } + } + } + } + } + else { + throw std::invalid_argument("Invalid Cubical_complex_primal_dual"); + } +} + + +// insert_cell implementation +template +void Cubical_chain_complex::insert_cell(size_t cell) { + CGAL_precondition(is_valid_cell(cell)); + + // verify if the cell has already been visited + if (_visited_cells.at(cell)) { + return; // cell has already been visited + } + + std::vector cell_coords(bindex_to_cell(cell)); + int dim = dimension(cell_coords); + + _cells[cell] = true; + size_t cell_base_index = _base2bool[dim].size(); + _base2bool[dim].push_back(cell); + _bool2base[dim][cell] = cell_base_index; + + std::vector boundaries(compute_boundaries(cell)); + for (const auto& boundary : boundaries) { + if (!_cells[boundary]) { + insert_cell(boundary); + } + } + + // mark cell as visited + _visited_cells.at(cell) = true ; +} + + +// compute_d implementation +template +void Cubical_chain_complex::compute_d(int dim) { + size_t nb_lignes = (dim == 0) ? 0 : number_of_cells(dim - 1); + + _d[dim] = Column_matrix(nb_lignes, number_of_cells(dim)); + + // Iterate through the cells of dimension dim + for (size_t i = 0; i < number_of_cells(dim); ++i) { + // Boundary of the i-th cell of dimension dim + Column_chain boundary = boundary_cell(i, dim); + + // Insert the chain into the corresponding column of the boundary matrix + OSM::set_column(_d[dim], i, boundary); + } +} + +// dimension implementation +template +int Cubical_chain_complex::dimension(const std::vector& cell) const { + CGAL_precondition(is_valid_cell(cell)); + + int dimension = 0; + for (size_t index : cell) { + if (index % 2 == 1) { // Un index impair indique une dimension plus Ć©levĆ©e + dimension++; + } + } + return dimension; +} + +// compute_boundaries implementation +template +std::vector Cubical_chain_complex::compute_boundaries(size_t idcell) const { + std::vector boundaries; + std::vector c = bindex_to_cell(idcell); + + for (size_t i = 0; i < _dim; ++i) { + if (c[i] % 2 == 1) { + // compute the coefficient based on the number of odd entries in c from 0 to i-1 + size_t cell1 = idcell + _P[i]; + if (is_valid_cell(cell1)) + boundaries.push_back(cell1) ; + + size_t cell2 = idcell - _P[i]; + if (is_valid_cell(cell2)) + boundaries.push_back(cell2) ; + } + } + return boundaries; +} + +/* \brief Export chain of dimension q to vtk. + * -> Only cells of the chain are exported + * -> If a cell Id is provided scalars are exported (0 for the given cellId / 2 for other cells) + */ + +template +void Cubical_chain_complex::chain_to_vtk(const Cubical_chain_complex &K, const std::string &filename, const typename Cubical_chain_complex::template Sparse_chain_type& chain, int q, size_t cellId) { + bool with_scalars = (cellId < K.number_of_cells(q)) ; + + // Load out file... + std::ofstream out ( filename, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "SimpComplex_chain_to_vtk. Fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + // Header + out << "# vtk DataFile Version 2.0" << std::endl ; + out << "generators" << std::endl ; + out << "ASCII" << std::endl ; + out << "DATASET UNSTRUCTURED_GRID" << std::endl ; + + // Points + size_t nnodes = K.number_of_cells(0) ; + out << "POINTS " << nnodes << " double" << std::endl ; + for (size_t n = 0; n < nnodes; ++n) { + Point3 p(Traits::to_point3(K.point(n))) ; + out << p ; + for (int i = Traits::Dimension::value; i<3; ++i) + out << " 0"; + out << std::endl ; + } + + size_t ncells_tot = 0, size_cells_tot = 0 ; + std::vector types ; + std::vector scalars ; + std::vector ids ; + + // output only cells of the chain (dimension q) + { + // 1 - Compute the number of cells / size of encoding + { + const size_t size_cell = 1< khal(K.bindex_to_cell(K._base2bool.at(q).at(id))) ; + std::vector verts(K.khal_to_verts(K.bindex_to_cell(K._base2bool.at(q).at(id)))) ; + out << size_cell << " " ; + for (size_t i : verts) + out << i << " " ; + out << std::endl ; + types.push_back(Cubical_chain_complex::VTK_cubtypes.at(q)) ; + + if (with_scalars) { + ids.push_back(id) ; + if (id != cellId) + scalars.push_back(2) ; + else + scalars.push_back(0) ; + } + } + } + } + // CELL_TYPES + out << "CELL_TYPES " << ncells_tot << std::endl ; + for (int t : types) + out << t << " " ; + out << std::endl ; + } + + if (with_scalars) { + // CELL_TYPES + out << "CELL_DATA " << ncells_tot << std::endl ; + out << "SCALARS Label " << "int" << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (int s : scalars) + out << s << " " ; + out << std::endl ; + // CELL_IDs + out << "SCALARS CellId " << "int" << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (size_t i : ids) + out << i << " " ; + out << std::endl ; + } + out.close() ; +} + +template +std::ostream& operator<<(std::ostream& out, const Cubical_chain_complex& complex) { + return complex.print_complex(out); +} + +} /* end namespace Homological_discrete_vector_field */ + +namespace IO { +/* + * \brief Exports a column chain `chain` to %VTK files. + * + * Exports cells of `chain` with a non null coefficient to a %VTK file, with a distinguished cell (for instance the underlying critical cell to a generator). Two properties are exported: + * - the `Label` property (2 for the distinguished cell, 0 for other cells) + * - the `CellId` property containing the "basis" index of the cell. + * + * \param K Underlying cubical complex. + * \param filename Output file name. + * \param chain Column chain exported to %VTK. + * \param q Dimension of the chain. + * \param cellId Index of the distinguished cell. + * + * \exception File_not_found If the file `filename` cannot be created and opened, throw a `%std::runtime_error` exception. + */ +template > +void write_VTK(const CGAL::Homological_discrete_vector_field::Cubical_chain_complex &K, const std::string &filename, const typename Homological_discrete_vector_field::Cubical_chain_complex:: template Sparse_chain_type& chain, int q, size_t cellId) { + CGAL::Homological_discrete_vector_field::Cubical_chain_complex::chain_to_vtk(K, filename, chain, q, cellId); +} + +/* + * \brief Exports a cubical chain complex (plus, optionally, labels) to a %VTK file. + * + * The method generates legacy text %VTK files. Labels are exported as such in a %VTK property, together with CellID property, containing the index of each cell. + * + * \tparam CoefficientRing Coefficient ring of the underlying complex `K`. + * \tparam Traits Traits class of the underlying complex `K`. + * \tparam LabelType Type of labels provided (default: int). + * + * \param K Cubical chain complex exported. + * \param filename Output file root (output filenames will be built from this root). + * \param labels Pointer to a vector of labels in each dimension. (*labels).at(q) is the set of integer labels of cells of dimension q. If labels is NULL, only CellID property is exported. + * \param label_type_name Typename used in %VTK export (e.g. "int" or "unsigned_long", see VTK manual ). + * + * \exception File_not_found If the file `filename` cannot be created and opened, throw a `%std::runtime_error` exception. + */ +template , typename LabelType = int> +void write_VTK(const CGAL::Homological_discrete_vector_field::Cubical_chain_complex &K, const std::string &filename, const std::vector > *labels=NULL, std::string label_type_name = "int") { + CGAL::Homological_discrete_vector_field::Cubical_chain_complexchain_complex_to_vtk(K, filename, labels, label_type_name); +} + +} /* end namespace IO */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_CUBICAL_CHAIN_COMPLEX_H diff --git a/HDVF/include/CGAL/HDVF/Filtration_core.h b/HDVF/include/CGAL/HDVF/Filtration_core.h new file mode 100644 index 000000000000..67ce9298f5dd --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Filtration_core.h @@ -0,0 +1,370 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_FILTRATION_CORE_H +#define CGAL_HDVF_FILTRATION_CORE_H + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + + +/*! \brief Type for indexing uniquely a cell. + * - First element of the pair: index of the cell. + * - Second element of the pair: dimension of the cell. + */ +typedef std::pair Cell ; + + + +/*! + \ingroup PkgHDVFFiltrationsClasses + + The class `Filtration_core` implements data structures and methods required by the `Filtration` concept. + It provides elementary constructors and methods used in derived filtrations. + + \cgalModels{Filtration} + + \tparam ChainComplex a model of the `AbstractChainComplex` concept (type of the underlying chain complex). + \tparam Degree the scalar type of degrees (a model of the `RealEmbeddable` concept). + */ + + +template +class Filtration_core +{ +public: + /*! \brief Type of the underlying complex. */ + typedef ChainComplex Chain_complex; + + /*! \brief Type of coefficients used to compute homology. */ + typedef typename ChainComplex::Coefficient_ring Coefficient_ring; + + /*! \brief Type of value returned by the iterator. + */ + typedef struct { + Cell cell_dim ; + Degree degree ; + size_t time; + } Filtration_iter_value ; + +protected: + /* \brief Constant reference to the underlying chain complex. */ + const ChainComplex& _K ; + /* \brief Vector of cells of the filtration (full ordering of cells). */ + std::vector _filtration ; + /* \brief Vector of degrees of cells along the filtration. */ + std::vector _deg ; + + /* \brief Map from cells to their index in the filtration. */ + std::map _cell_to_t ; + + /* \brief Type of sparse matrix structure used to compute homology. */ + typedef typename ChainComplex::Sparse_matrix_structure Sparse_matrix_structure; + + /* \brief Template type of underlying sparse chains. */ + template + using Sparse_chain_base = typename Sparse_matrix_structure::template Sparse_chain_type; + + /* \brief Template type of underlying sparse matrices. */ + template + using Sparse_matrix_base = typename Sparse_matrix_structure::template Sparse_matrix_type; + /* + Type of column-major chains + */ + typedef Sparse_chain_base Column_chain; + + /* + Type of row-major chains + */ + typedef Sparse_chain_base Row_chain; + + /* + Type of column-major sparse matrices + */ + typedef Sparse_matrix_base Column_matrix; + + /* + Type of row-major sparse matrices + */ + typedef Sparse_matrix_base Row_matrix; +public: + /*! \brief Constructor of an empty filtration with an underlying chain complex. + * + *\param K A chain complex (a model of `AbstractChainComplex`), the underlying chain complex of the filtration. + */ + Filtration_core(const ChainComplex& K) : _K(K) + {} + + /*! \brief Constructor from a vector of cells (ordering of cells) and an associated vector of degrees. + * + * The constructor check that the filtration is valid (a cell is introduced in the filtration after its faces and the degree vector is increasing) and throw an exception if not. + * \param K A chain complex (a model of `AbstractChainComplex`), the underlying chain complex of the filtration. + * \param filtration An ordering of the cells of `K` encoded as a vector of its cells. + * \param deg The (increasing) vector of cell degrees. + */ + Filtration_core(const ChainComplex& K, const std::vector& filtration, const std::vector& deg) : _K(K), _filtration(filtration), _deg(deg) + { + if (!is_valid()) + throw ("Invalid filtration, Filtration_core constructor failed"); + } + + /* + * \brief Copy constructor. + */ + Filtration_core(const Filtration_core& f) = default; + + /* \brief Iterator over a filtration. + * + * Iterate the cells of the filtration by increasing index (and hence increasing degree). + */ + struct iterator + { + // Iterator tags + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Filtration_iter_value; + + /* \brief Iterator constructor + * + * \param f Constant reference on the `Filtration_core` iterated. + * \param i The initial index. + */ + iterator(const Filtration_core& f, std::size_t i=0) : _i(i), _f(f) {} + + /* \brief Iterator dereference + * + * \returns A `Filtration_iter_value` structure containing the information of the current cell and its degree. + */ + value_type operator*() const + { + Filtration_iter_value res ; + res.cell_dim = _f._filtration.at(_i) ; + res.degree = _f._deg.at(_i) ; + res.time = _i; + return res ; + } + + /* + * \brief Prefix incrementation. Moves to next cell in the filtration. + * + * \returns The reference to the current iterator. + */ + iterator& operator++() + { + ++_i; + return *this; + } + + /* + * \brief Postfix incrementation. Moves to next cell in the filtration. + * \returns The pre-incremented iterator. + */ + iterator operator++(int) { iterator tmp = *this; ++(*this); return tmp; } + + /* + * \brief Equality check. + * \returns `true` if the indices are equal. + */ + friend bool operator== (const iterator& a, const iterator& b) { return a._i == b._i; }; + + /* + * \brief Inequality check. + * \returns `true` if the indices are different. + */ + friend bool operator!= (const iterator& a, const iterator& b) { return a._i != b._i; }; + + private: + std::size_t _i ; // Index along _persist + const Filtration_core& _f ; // Filtration_core iterated + }; + + /* + * \brief Iterator to the beginning of the filtration. + * + * \return The iterator to the beginning of the filtration. + */ + iterator begin() { return iterator(*this, 0) ; } + + /* + * \brief Iterator past-the-end of the filtration. + * + * \return The iterator past-the-end of the filtration. + */ + iterator end() { return iterator(*this, _filtration.size()) ; } + + // getters + /* \brief Gets the filtration size. + */ + std::size_t size () const { return _filtration.size(); } + + /* \brief Gets the cell (that is cell index and dimension) at the index `i` of the filtration. + */ + Cell cell (std::size_t i) const { return _filtration.at(i); } + + /* \brief Gets the degree of the `i`th element of the filtration. + */ + Degree degree (std::size_t i) const { return _deg.at(i); } + + // Output filtration + /* \brief Overload of the `<<`operator for filtrations. + */ + friend std::ostream & operator<<(std::ostream & out, const Filtration_core &f) { + const std::size_t N(f._filtration.size()) ; + out << N << std::endl; + for (std::size_t i=0; i (" << f._filtration.at(i).first << "- dim " << f._filtration.at(i).second << " , " << f._deg.at(i) << ") " << std::endl ; + } + return out ; + } + + /* + \brief Extracts a filtration from a stream. + */ + friend std::istream & operator>>(std::istream & in, Filtration_core &f) { + // Read the size of the filtration + size_t N, tmp ; + char tmps[20]; + in >> N; + for (int i=0; i> tmp; + assert (i == tmp); + in.read(tmps,5); // Discard " -> (" + // Read cell index + in >> c.first; + in.read(tmps,6); // Discart "- dim " + // Read cell dimension + in >> c.second; + in.read(tmps,3); // Discart " , " + // Read degree + in >> d ; + in.read(tmps,3); // Discart ")" + + // Insert in _filtration + size_t n(f._filtration.size()); + f._filtration.push_back(c); + // Insert correponding degree + f._deg.push_back(d); + // Insert in _cell_to_t + f._cell_to_t[c] = i; + } + return in; + } + + /* Comparison operator */ + bool operator==(const Filtration_core& f) const { + return (_filtration == f._filtration) && (_deg == f._deg); + } + + /* + * \brief Exports the filtration time indices. + * + * The method exports the time index of every cells in each dimension. + * + * \returns A vector containing, for each dimension, the vector of labels by cell index. + */ + std::vector > export_filtration () const + { + std::vector > labels(_K.dimension()+1) ; + for (int q=0; q<=this->_K.dimension(); ++q) + { + for (std::size_t i = 0; i_K.number_of_cells(q); ++i) + { + const Cell cell(i,q) ; + const std::size_t t(_cell_to_t.at(cell)); + labels.at(q).push_back(t) ; + } + } + return labels ; + } + + /* \brief Checks if a filtration is valid. + * Checks that cells are ordered in increasing degrees and all cells have indices larger than their faces. + * \returns `true` if the filtration is valid, `false` otherwise + */ + bool is_valid() const ; + +protected: + /* \brief Sets _cell_to_t from the initial vector o cells. */ + void build_filtration_structure() ; + + /* Friend class: Hdvf_persistence. */ + template + friend class Hdvf_persistence ; +}; + +template +void Filtration_core::build_filtration_structure() +{ + for (std::size_t i = 0; i<_filtration.size(); ++i) + { + const Cell c(_filtration.at(i)) ; + // c : filtration index i, index in the basis reordered by filtration : j + _cell_to_t[c] = i ; + } +} + +template +bool Filtration_core::is_valid() const +{ + bool valid = true ; + for (std::size_t i=0; i<_filtration.size() && valid; ++i) + { + if (i>0) + valid = valid & (_deg.at(i) >= _deg.at(i-1)) ; + Cell c(_filtration.at(i)) ; + std::cout << i << " -> " << c.first << " dim "<< c.second << std::endl ; + const int q = c.second ; + if (q>0) + { + Column_chain dc = _K.d(c.first, q) ; + std::cout << "bnd : " << dc << std::endl ; + for (typename Column_chain::iterator it = dc.begin(); it != dc.end() && valid; ++it) + { + // Faces of c + const Cell face(it->first,q-1) ; + // Check if the face c belongs to the filtration + auto it_face(_cell_to_t.find(face)) ; + valid = valid & (it_face != _cell_to_t.end()) ; + if (!valid) + std::cout << "face not found" << std::endl ; + if (it_face != _cell_to_t.end()) + valid = valid & (_cell_to_t.at(face) < i) ; + if (!valid) + std::cout << "face " << it->first << " at time : " << _cell_to_t.at(face) << " with i : " << i << std::endl ; + } + } + if (!valid) + std::cout << "check failed : " << i << std::endl ; + } + return valid ; +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_FILTRATION_CORE_H diff --git a/HDVF/include/CGAL/HDVF/Filtration_lower_star.h b/HDVF/include/CGAL/HDVF/Filtration_lower_star.h new file mode 100644 index 000000000000..2448c78775a8 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Filtration_lower_star.h @@ -0,0 +1,229 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_FILTRATION_LOWER_STAR_H +#define CGAL_HDVF_FILTRATION_LOWER_STAR_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +//template +//std::function sample_f = [](const P& X) +//{ +// // Filtration : (x,y,z) -> degree x+y +// return X[0]+X[1]; +//} +// +//template +//std::function sample_degree_function (const ChainComplex& complex, const std::function& f) +//{ +// std::function deg_fun_f = [&complex, &f](size_t i) +// { +// const P& Xi(complex.point(i)) ; +// return f(Xi) ; +// } ; +// return deg_fun_f ; +//} + +/** + *\ingroup PkgHDVFFiltrationsClasses + * + * \brief Degree function providing a filtration value for all vertices of the complex. + * + * Given a function mapping a point's coordinates to a degree, `degree_function` returns a function mapping the index of a vertex to the degree associated to its coordinates. + * + * \param complex The underlying complex of the filtration. + * \param f A function mapping a point to a degree value. Standard argument functions are `Kernel::Compute_x_3()`, `Kernel::Compute_y_3()`, `Kernel::Compute_z_3()`. + */ + +template +std::function degree_function (const ChainComplex& complex, const std::function& f) +{ + std::function deg_fun_f = [&complex, &f](size_t i) + { + const P& Xi(complex.point(i)) ; + return f(Xi) ; + } ; + return deg_fun_f ; +} + +/*! + \ingroup PkgHDVFFiltrationsClasses + + The class `Filtration_lower_star` implements the lower star filtration on a given complex implementing the concept `AbstractChainComplex`. + + A filtration associated to a chain complex `K` associates to each cell of `K` a scalar value (called degree) such that the degree of a cell is larger than the degrees of its faces. + + Let `Degree` be a scalar type. The lower star filtration is a filtration obtained from a map \f$\mathrm{deg}\,:\, K_0 \to \mathrm{Degree}\f$ (where \f$K_0\f$ denotes the set of vertices of \f$K\f$) associating a degree to each vertex of the complex \f$K\f$. + + The map is extended to cells of any dimension by setting, for a cell \f$\sigma\f$: + \f[\mathrm{deg}(\sigma) = \max_{\substack{v\in K_0\\v\text{ face of }\sigma}} \mathrm{deg}(v) + \f] + + For geometric complexes, standard lower star filtrations are obtained by taking as a degree function the \f$x\f$, \f$y\f$ or \f$z\f$ coordinate of vertices. The image below illustrates such a filtration ((left) lower star filtration with a \f$z\f$ degree map on vertices, (right) lower star filtration with a \f$y\f$ degree map on vertices). + + + + + The `Filtration_lower_star` class provides constructors taking as input: + - either the vector of vertex degrees + - or a function mapping each vertex to its degree. + + \cgalModels{Filtration} + + \tparam ChainComplex a model of the `AbstractChainComplex` concept (type of the underlying chain complex). + \tparam Degree the scalar type of degrees, a model of `RealEmbeddable`. + */ + +template +class Filtration_lower_star : public Filtration_core +{ +private: + /* \brief Type of coefficients used to compute homology. */ + typedef typename ChainComplex::Coefficient_ring Coefficient_ring; + /*! Type of parent filtration instance. */ + typedef Filtration_core Filtration_parent; + /*! Type of value returned by the iterator. */ + typedef typename Filtration_parent::Filtration_iter_value Filtration_iter_value; +public: + /* \brief chain complex type. */ + typedef ChainComplex Chain_complex; + /* + * \brief Copy constructor. + */ + Filtration_lower_star(const Filtration_lower_star& f) : Filtration_parent(f) {} + + /*! \brief Constructor from vertex degrees. + * + * The constructor computes all cell degrees as the minimum of the degrees of their vertices and sorts all the cells of the complex to fulfill the filtration ordering constraints. + * + * \param K Constant reference to the underlying complex. + * \param deg Vector of vertex degrees. + */ + Filtration_lower_star(const Chain_complex& K, const std::vector& deg) : Filtration_parent(K) + { + star_filtration(deg); + } + + /*! \brief Constructor from a function mapping vertices to degrees. + * + * The constructor computes all cell degrees as the minimum of the degrees of their vertices (obtained through `deg`) and sorts all the cells of the complex to fulfill the filtration ordering constraints. + * + * \param K Constant reference to the underlying complex. + * \param deg Function mapping vertices of `K` to their degree. + */ + Filtration_lower_star(const Chain_complex& K, std::function& deg) : Filtration_parent(K) + { + star_filtration(deg); + } + + +protected: + // Build lower-star filtration + /* \brief Function building the filtration from the vector of vertex degrees. + */ + void star_filtration(const std::vector& deg) ; + + /* \brief Function building the filtration from a function mapping vertices to their degree. + */ + void star_filtration(std::function& degree) ; +}; + + + +template +void Filtration_lower_star::star_filtration(const std::vector °) +{ + if (deg.size() != this->_K.number_of_cells(0)) + throw "Star filtration error : deg should provide one value by vertex" ; + + // Create filtration and degrees for all cells according to deg + // -> lower star: maximum degree of vertices + // -> upper star: minimum degree of vertices + std::vector tmp_filtration ; + std::vector tmp_deg ; + std::vector tmp_perm ; + for (size_t i=0; i_K.dimension(); ++q) + { + for (size_t i=0; i_K.number_of_cells(q); ++i) + { + tmp_filtration.push_back(Cell(i,q)) ; + + // Compute corresponding degree + // Vertices of the cell + std::vector verts(this->_K.bottom_faces(i,q)) ; + // Compute the degree of the cell + Degree d = deg.at(verts.at(0)) ; + for (size_t j=1; j d) + d = tmp_d ; + // If upper star filtration (for cohomology) + // if (tmp_d < d) + // d = tmp_d ; + + } + tmp_deg.push_back(d) ; + tmp_perm.push_back(tmp_perm.size()) ; + } + } + // Sort filtration + + // Create sorting function : lexicographic order over (deg, dim) + // Test if cell i < cell j + auto f_sort = [&tmp_filtration, &tmp_deg] (size_t i, size_t j) + { + // degree of i is lower than degree of j or (they are equal and the dimension of i is lower than the dimension of j) + return ((tmp_deg[i] < tmp_deg[j]) || ((tmp_deg[i] == tmp_deg[j]) && (tmp_filtration[i].second < tmp_filtration[j].second))) ; + } ; + // Sort -> create the right permutation + std::sort(tmp_perm.begin(), tmp_perm.end(), f_sort) ; + // Insert cells in the filtration and degrees accordingly + for (size_t i=0; i_filtration.push_back(tmp_filtration.at(tmp_perm.at(i))) ; + this->_deg.push_back(tmp_deg.at(tmp_perm.at(i))) ; + this->_cell_to_t[tmp_filtration.at(tmp_perm.at(i))] = i ; + } +} + +template +void Filtration_lower_star::star_filtration(std::function& degree) +{ + std::vector deg ; + for (size_t i=0; i_K.number_of_cells(0); ++i) + { + deg.push_back(degree(i)) ; + } + star_filtration(deg) ; +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_FILTRATION_LOWER_STAR_H diff --git a/HDVF/include/CGAL/HDVF/Geometric_chain_complex_tools.h b/HDVF/include/CGAL/HDVF/Geometric_chain_complex_tools.h new file mode 100644 index 000000000000..a9d1bbc4bf58 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Geometric_chain_complex_tools.h @@ -0,0 +1,695 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_GEOMETRIC_CHAIN_COMPLEX_TOOLS_H +#define CGAL_HDVF_GEOMETRIC_CHAIN_COMPLEX_TOOLS_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * \class SimpComplexTools + * \brief Provides tools for SimpComplexes ((co)homology, duality, persistent homology). + * \brief Friend class of SimpComplex + */ + +namespace CGAL { +namespace IO { + +// GeometricChainComplex IO + +// Hdvf vtk export + +/*! + *\ingroup PkgHDVFIOClasses + * + * \brief Exports all the `Hdvf` information of a geometric chain complex to %VTK files. + * + * Export PSC labels and homology/cohomology generators (depending on HDVF options) associated to each critical cell to %VTK files. + * + * \param hdvf Reference to the HDVF exported. + * \param complex Underlying geometric chain complex. + * \param filename Prefix of all generated files. + * \param co_faces Export the cohomology generator or its co-faces (sometimes more convenient for visualisation). + * + * Below, a sample mesh with, (left) homology generators, (right) two examples of cohomology generators (corresponding generators/co-generators bear similar colours): + * + * + * + * + * + * The same generators displayed through their co-faces: + * + * + * + * + * All homology / cohomology generators: + * + * + */ + +template +void write_VTK (const Homological_discrete_vector_field::Hdvf_core& hdvf, const ChainComplex &complex, std::string filename = "test", bool co_faces = false) { + typedef typename ChainComplex::Coefficient_ring Coefficient_ring; + // Export PSC labelling in any dimension (whatever the dimension restriction) + std::string outfile(filename+"_PSC.vtk") ; + std::vector > labels = hdvf.psc_labels(-1) ; + ChainComplex::chain_complex_to_vtk(complex, outfile, &labels) ; + + // Range of computed dimensions + int min_dim, max_dim; + if (hdvf.dimension_restriction() == -1) { + min_dim = 0; + max_dim = complex.dimension(); + } + else + min_dim = max_dim = hdvf.dimension_restriction(); + + if (hdvf.hdvf_opts() != Homological_discrete_vector_field::OPT_BND) { + // Export generators of all critical cells in the computed range + // [min_dim, max_dim] + std::vector > criticals(hdvf.psc_flag(Homological_discrete_vector_field::CRITICAL)) ; + for (int q = min_dim; q <= max_dim; ++q) { + for (size_t c : criticals.at(q)) { + // Homology generators + if (hdvf.hdvf_opts() & (Homological_discrete_vector_field::OPT_FULL | Homological_discrete_vector_field::OPT_G)) { + std::string outfile_g(filename+"_hom_"+std::to_string(c)+"_dim_"+std::to_string(q)+".vtk") ; + // std::vector > labels = hdvf.export_label(G,c,q) ; + OSM::Sparse_chain chain(hdvf.homology_chain(c,q)) ; + ChainComplex::chain_to_vtk(complex, outfile_g, chain, q, c) ; + } + // Cohomology generators + if (hdvf.hdvf_opts() & (Homological_discrete_vector_field::OPT_FULL | Homological_discrete_vector_field::OPT_F)) { + std::string outfile_f(filename+"_cohom_"+std::to_string(c)+"_dim_"+std::to_string(q)+".vtk") ; + OSM::Sparse_chain chain(hdvf.cohomology_chain(c, q)) ; + if (!co_faces) { + ChainComplex::chain_to_vtk(complex, outfile_f, chain, q, c) ; + } + else { + if (q < complex.dimension()) { + ChainComplex::chain_to_vtk(complex, outfile_f, complex.cofaces_chain(chain, q), q+1, c) ; + } + } + } + } + } + } +} + +// Hdvf_persistence vtk export + +/** + * \ingroup PkgHDVFIOClasses + * + * \brief Exports all the `HDVF_persistence` information of a geometric chain complex to %VTK files. + * + * Export PSC labels and homology/cohomology generators (depending on HDVF options) associated to each persistent intervals to %VTK files. + * + * \param per_hdvf Reference to the persistent HDVF exported. + * \param complex Underlying geometric chain complex. + * \param filename Prefix of all generated files. + * \param co_faces Export the cohomology generator or its co-faces (sometimes more convenient for visualisation). + */ + +template +void write_VTK (const Homological_discrete_vector_field::Hdvf_persistence &per_hdvf, const ChainComplex &complex, std::string filename = "per", bool co_faces = false) { + typedef typename ChainComplex::Coefficient_ring Coefficient_ring; + if (!per_hdvf.with_export()) + throw("Cannot export persistent generators to vtk: with_export is off!") ; + + using HDVF_type = Homological_discrete_vector_field::Hdvf_persistence; + using Persistence_interval = typename HDVF_type::Persistence_interval; + + // Export the filtration + std::string out_file_filtration = filename+"_filtration.vtk" ; + std::vector > filtr_labels = per_hdvf.filtration().export_filtration(); + ChainComplex::template chain_complex_to_vtk(complex, out_file_filtration, &filtr_labels, "unsigned_long") ; + + // Iterate over persistence diagram (iterator over non zero intervals) + // Batch informations are stored in file filename_infos.txt + std::ofstream info_file(filename+"_infos.txt") ; + size_t i = 0 ; + for (typename HDVF_type::iterator it = per_hdvf.begin(); it != per_hdvf.end(); ++it) { + typename HDVF_type::Persistent_hole hole_data(*it) ; + const Persistence_interval hole(hole_data.hole) ; + // Export informations of this hole + /* V1 */ + info_file << i << " -> " << " --- duration : " << hole.duration() << " -- " ; + hole.insert(info_file); + info_file << std::endl ; + /* V2 */ + // info_file << i << " -> " << " --- duration : " << hole.duration() << " -- " << hole << std::endl ; + + if (hole.duration()>0) { // Export vtk of "finite" holes + // Build name associated to the ith hole : filename_i + std::string out_file = filename+"_"+std::to_string(i) ; + // Export PSC labels to vtk + ChainComplex::chain_complex_to_vtk(complex, out_file+"_PSC.vtk", &hole_data.labelsPSC) ; + + // Export homology generators (g) + if (per_hdvf.hdvf_opts() & (Homological_discrete_vector_field::OPT_FULL | Homological_discrete_vector_field::OPT_G)) { + // First generator : filename_i_g_sigma_q.vtk + { + const size_t id(hole_data.hole.cell_birth.first) ; + const int dim(hole_data.hole.cell_birth.second) ; + std::string tmp(out_file+"_hom_"+std::to_string(id)+"_"+std::to_string(dim)+".vtk") ; + ChainComplex::chain_to_vtk(complex, tmp, hole_data.homology_chain_birth, dim); + } + // Second generator : filename_i_g_tau_q+1.vtk + { + const size_t id(hole_data.hole.cell_death.first) ; + const int dim(hole_data.hole.cell_death.second) ; + std::string tmp(out_file+"_hom_"+std::to_string(id)+"_"+std::to_string(dim)+".vtk") ; + ChainComplex::chain_to_vtk(complex, tmp, hole_data.homology_chain_death, dim); + } + } + // Export cohomology generators (fstar) + if ((per_hdvf.hdvf_opts() == Homological_discrete_vector_field::OPT_FULL) || (per_hdvf.hdvf_opts() == Homological_discrete_vector_field::OPT_F)) { + // First generator : filename_i_fstar_sigma_q.vtk + { + const size_t id(hole_data.hole.cell_birth.first) ; + const int dim(hole_data.hole.cell_birth.second) ; + std::string tmp(out_file+"_cohom_"+std::to_string(id)+"_"+std::to_string(dim)+".vtk") ; + if (co_faces) { + ChainComplex::chain_to_vtk(complex, tmp, complex.cofaces_chain(hole_data.cohomology_chain_birth, dim), dim+1); + } + else + ChainComplex::chain_to_vtk(complex, tmp, hole_data.cohomology_chain_birth, dim); + } + // Second generator : filename_i_fstar_tau_q+1.vtk + { + const size_t id(hole_data.hole.cell_death.first) ; + const int dim(hole_data.hole.cell_death.second) ; + std::string tmp(out_file+"_cohom_"+std::to_string(id)+"_"+std::to_string(dim)+".vtk") ; + if (co_faces) { + ChainComplex::chain_to_vtk(complex, tmp, complex.cofaces_chain(hole_data.cohomology_chain_death, dim), dim+1); + } + else + ChainComplex::chain_to_vtk(complex, tmp, hole_data.cohomology_chain_death, dim); + } + } + } + ++i ; + } + info_file.close() ; + // Export infinite holes by calling Hdvf_core write_VTK + const Homological_discrete_vector_field::Hdvf_core& tmp(per_hdvf); + CGAL::IO::write_VTK(tmp, complex, filename+"_inf", co_faces); +} + +// Hdvf_duality vtk export + +/** + * \ingroup PkgHDVFIOClasses + * + * \brief Exports all the `HDVF_duality` information of a geometric chain complex to %VTK files. + * + * Export PSC labels and homology/cohomology generators (depending on HDVF options) associated to each persistent intervals to %VTK files. + * + * \param hdvf Reference to the HDVF exported. + * \param complex Underlying geometric chain complex. + * \param filename Prefix of all generated files. + * \param co_faces Export the cohomology generator or its co-faces (sometimes more convenient for visualisation). + */ + +template +void write_VTK (const Homological_discrete_vector_field::Hdvf_duality &hdvf, const ChainComplex &complex, std::string filename = "test", bool co_faces = false) { + typedef typename ChainComplex::Coefficient_ring Coefficient_ring; + typedef Homological_discrete_vector_field::Hdvf_duality HDVF_parent; + // Export PSC labelling + std::string outfile(filename+"_PSC.vtk") ; + std::vector > labels = hdvf.psc_labels() ; + ChainComplex::chain_complex_to_vtk(complex, outfile, &labels) ; + + if (hdvf.hdvf_opts() != Homological_discrete_vector_field::OPT_BND) { + // Export generators of all critical cells + std::vector > criticals(hdvf.psc_flag(Homological_discrete_vector_field::CRITICAL)) ; + for (int q = 0; q <= complex.dimension(); ++q) { + for (size_t c : criticals.at(q)) { + // Homology generators + if (hdvf.hdvf_opts() & (Homological_discrete_vector_field::OPT_FULL | Homological_discrete_vector_field::OPT_G)) { + std::string outfile_g(filename+"_hom_"+std::to_string(c)+"_dim_"+std::to_string(q)+".vtk") ; + // std::vector > labels = hdvf.export_label(G,c,q) ; + OSM::Sparse_chain chain(hdvf.homology_chain(c,q)) ; + ChainComplex::chain_to_vtk(complex, outfile_g, chain, q, c) ; + } + // Cohomology generators + if (hdvf.hdvf_opts() & (Homological_discrete_vector_field::OPT_FULL | Homological_discrete_vector_field::OPT_F)) { + std::string outfile_f(filename+"_cohom_"+std::to_string(c)+"_dim_"+std::to_string(q)+".vtk") ; + OSM::Sparse_chain chain(hdvf.cohomology_chain(c, q)) ; + if (!co_faces) { + ChainComplex::chain_to_vtk(complex, outfile_f, chain, q, c) ; + } + else { + // Compute co-faces + if (q < complex.dimension()) { + // Restrict the cofaces of the cohomology generator to the current sub chain complex + OSM::Sparse_chain cofaces_chain(complex.cofaces_chain(chain, q)) ; + Homological_discrete_vector_field::Sub_chain_complex_mask sub(hdvf.get_current_mask()); + sub.screen_chain(cofaces_chain, q+1); + // Display + ChainComplex::chain_to_vtk(complex, outfile_f, cofaces_chain, q+1, c) ; + } + } + } + } + } + } +} + +/** + *\ingroup PkgHDVFIOClasses + * + * \brief Exports a model of `GeometricChainComplex` (plus, optionally, labels) to a VTK file. + * + * The method generates legacy text VTK files. Labels are exported as such in a VTK property, together with CellID property, containing the index of each cell. + * + * \tparam LabelType Type of labels provided (default: int). + * + * \param K Model of `GeometricChainComplex` exported. + * \param filename Output file root (output filenames will be built from this root). + * \param labels Pointer to a vector of labels in each dimension. (*labels).at(q) is the set of integer labels of cells of dimension q. If labels is NULL, only CellID property is exported. + * \param label_type_name Typename used in vtk export (e.g. "int" or "unsigned_long", see VTK manual ). + */ +template +static void write_VTK(const Chain_complex &K, const std::string &filename, const std::vector > *labels=NULL, std::string label_type_name = "int") { + Chain_complex::chain_complex_to_vtk(K, filename, labels, label_type_name); +} + +/** + *\ingroup PkgHDVFIOClasses + * + * \brief Exports a chain over a model of `GeometricChainComplex` to a VTK file. + * + * The method generates legacy text VTK files. All the cells of the chain with non zero coefficient are exported. If a cellId is provided, labels are exported in a VTK property (2 for all cells, 0 for cell of index cellId). The index of each cell is exported in a CellID property. + * + * \param K Model of `GeometricChainComplex` exported. + * \param filename Output file root (output filenames will be built from this root). + * \param chain Sparse_chain exported (all the cells with non-zero coefficients in the chain are exported to vtk). + * \param q Dimension of the cells of the chain. + * \param cellId If different from MAX_SIZE_T, labels are exported to distinguish cells of the chain (label 2) from cellId cell (label 0). + */ +template +void write_VTK(const Chain_complex &K, const std::string &filename, const OSM::Sparse_chain& chain, int q, size_t cellId = -1) { + Chain_complex::chain_to_vtk(K, filename, chain, q, cellId); +} + +} /* end namespace IO */ + +namespace Homological_discrete_vector_field { + +// Duality tools + + +/** + *\ingroup PkgHDVFDualityClasses + * \brief Type returned by `dualize_complex()`. + * + * \tparam ChainComplex Type of the underlying chain complex. + * + * The structure contains a pair: + * - `L_complex`: a pointer over simplicial chain complex (homeomorphic to \f$\mathbb B^3\f$). + * - `K_complex`: a sub chain complex mask (encoding the initial mesh inside `*L_complex`) + */ +template +class Complex_duality_data_t { +public: + /** \brief Type of the underlying chain complex. */ + typedef ChainComplex Chain_complex; + /** \brief Type of sub chain complex mask encoding the sub complex `*K_complex`. */ + typedef Sub_chain_complex_mask Sub_chain_complex ; + /** \brief Pointer over a complex homeomorphic to \f$\mathbb B^3\f$. */ + std::shared_ptr L_complex ; + /** \brief Pointer over a sub chain complex mask encoding a sub complex of `*L_complex`. */ + std::shared_ptr K_complex ; + + /** \brief %Default constructor*/ + Complex_duality_data_t() : L_complex(nullptr), K_complex(nullptr) {} + + /** \brief Constructor from a pointer over a chain complex and a sub chain complex mask. + */ + Complex_duality_data_t(std::shared_ptr L, std::shared_ptr K) : L_complex(L), K_complex(K) {} +} ; + + +// =========== Simplicial chain complex tools +// Build K sub-chain complex of L (homeomorphic to B^n) + +/*! + \ingroup PkgHDVFDualityClasses + + The class `Duality_simplicial_complex_tools` is dedicated to Alexander duality for 3D surface meshes. Starting from a simplicial chain complex (encoding a 3D surface mesh), it provides methods to embed the complex into a larger icosphere and generate a 3D constrained Delaunay triangulation. + + Technically, starting from a Simplicial_chain_complex `K_init`, the methods `dualize_complex()` builds a `Simplicial_chain_complex` `L` and a `Sub_chain_complex_mask` `K`. + - L : complex built out of K_init together with a closing icosphere + - K (Sub_chain_complex_mask) : Sub_chain_complex_mask identifying K_init inside L + + Two `dualize_complex()` methods are provided, based repsectively on tetgen and `CGAL::Conforming_constrained_Delaunay_triangulation_3`. + + \tparam CoefficientRing a model of the `IntegralDomainWithoutDivision` concept providing the ring used to compute homology. + \tparam Traits a geometric traits class model of the `HDVFTraits` concept. + \tparam SparseMatrixStruct a specialization of `OSM::Sub_sparse_matrix_structure` corresponding to that of the dualized complex. + */ + +template > +class Duality_simplicial_complex_tools { +public: + typedef typename Traits::Point Point ; + /** \brief Type of simplicial chain complex encoding `L`. */ + typedef Simplicial_chain_complex Chain_complex ; + /** \brief Type of sub chain complex mask encoding the sub complex `K`. */ + typedef Sub_chain_complex_mask Sub_chain_complex ; + /** \brief Type of 3D surface meshes. */ + typedef CGAL::Surface_mesh Triangle_mesh; + /** \brief Type returned by `dualize_complex()`. */ + typedef Complex_duality_data_t Complex_duality_data; + /** \brief %Default constructor. */ + Duality_simplicial_complex_tools() {} + +private: + static std::shared_ptr compute_sub_chain_complex(const Chain_complex& _K, const Chain_complex &L){ + // Build the Sub_chain_complex_mask encoding K_init inside L + std::shared_ptr K(std::make_shared(L, false)); + // Visit all cells of K_init and activate the corresponding bit in K + for (int q=0; q<=_K.dimension(); ++q) { + for (size_t i=0; i<_K.number_of_cells(q); ++i) { + const Simplex& simplex(_K.index_to_cell(i,q)) ; + const size_t id(L.cell_to_index(simplex)); + K->set_bit_on(q, id) ; + } + } + return K; + } + +public: + /** \brief Generates a subcomplex \f$K\f$K and a complex \f$L\f$ with \f$K\subseteq L\f$ from a set of simplices `mesh_io` (based on tetgen). + * + * On the one hand, a `Simplicial_chain_complex` is built from `mesh_io` (let us call if `_K`); on the other hand, this complex is embedded into a larger icosphere and a 3D constrained Delaunay tetraedrization is generated. + * Then \f$K\f$, \f$L\f$ and vertex coordinates are extracted and stored in a `Complex_duality_data` structure. + * + * After the function, `mesh_object_io` has been extended with the simplices of the closing icosphere. + * + * The following figures shows the resulting complex with an initial `Twirl` mesh (right - sectional view): + * + * + * + * \param mesh_io `Mesh_object_io` containing the initial set of simplicies (working mesh). + * \param BB_ratio Ratio of the "closing" icosphere diameter with respect to the diameter of the object's bounding box. + * \param out_file_prefix Prefix of tetgen intermediate files (default: "file_K_closed.off"). + * \param subdiv Subdivision level of the bounding icosphere. + * + * \warning Replace the path to tetgen by an environment variable... + */ + static Complex_duality_data dualize_complex (Mesh_object_io& mesh_io, double BB_ratio=1.5, const std::string& out_file_prefix = "file_K_closed.off", unsigned int subdiv = 2) { + + std::cerr << "-- Starting dualize_complex" << std::endl; + std::cerr << "Imported set of simplices" << std::endl; + std::cout << mesh_io; + + // Create a temporary associated complex (to identify simplices of mesh_io) + Chain_complex* _K = new Chain_complex(mesh_io); + + // Closing mesh_object_io by adding the icosphere + // Compute a bounding icosphere + Point center = mesh_io.centroid() ; + double r = mesh_io.radius(center) ; + Icosphere_object_io ico(subdiv,center, BB_ratio*r) ; + std::cerr << "Icosphere generated" << std::endl; + ico.print_infos() ; + + // Add it to the mesh + mesh_io.push_back(ico) ; + std::cerr << "Mesh concatenation" << std::endl; + mesh_io.print_infos() ; + + // Write this mesh to an off file for Tetgen + mesh_io.write_off(out_file_prefix) ; + // const std::string tetgen_path(CMAKE_TETGEN_PATH) ; + // WARNING: use CGAL 3D triangulation to get rid of tetgen... + const std::string tetgen_path("/Users/umenohana/Dropbox/G-Mod/TopAlg/code/tetgen1.6.0/build/") ; + std::string tetgen_command = tetgen_path+"tetgen -pqkcYfe "+out_file_prefix+".off" ; + system(tetgen_command.c_str()) ; + + // Read the mesh built by tetgen for L + Tet_object_io tetL(out_file_prefix) ; + + // Build the output data structure + Complex_duality_data dualized_complex; + // Build the associated SimpComplex + dualized_complex.L_complex = std::make_shared(tetL) ; + std::shared_ptr L(dualized_complex.L_complex); + std::cout << "------ L:" << *L; + + // Build the Sub_chain_complex_mask encoding K_init inside L + dualized_complex.K_complex = compute_sub_chain_complex(*_K, *L); + // Remove the temporary complex + delete _K; + + return dualized_complex; + } + + /** \brief Generates a subcomplex \f$K\f$K and a complex \f$L\f$ with \f$K\subseteq L\f$ from a `Surface_mesh` (based on `CGAL::Conforming_constrained_Delaunay_triangulation_3`). + * + * On the one hand, a `Simplicial_chain_complex` is built from the `Surface_mesh` (let us call it `_K`); on the other hand, this complex is embedded into a larger icosphere and a 3D constrained Delaunay tetraedrization is generated. + * Then \f$K\f$, \f$L\f$ and vertex coordinates are built and extracted (from both previous computation) and stored in a `Complex_duality_data` structure. + * + * After the function, `mesh_object_io` has been extended with the simplices of the closing icosphere. + * + * The following figures shows the resulting complex with an initial `Twirl` mesh (right - sectional view): + * + * + * + * \param mesh `Surface_mesh` containing the initial mesh. + * \param BB_ratio Ratio of the "closing" icosphere diameter with respect to the diameter of the object's bounding box. + * \param subdiv Subdivision level of the bounding icosphere. + */ + static Complex_duality_data dualize_complex (Triangle_mesh& mesh, double BB_ratio=1.5, unsigned int subdiv = 2) { + typedef CGAL::Triangulation_data_structure_3, CGAL::Conforming_constrained_Delaunay_triangulation_cell_base_3> TDS; + typedef CGAL::Triangulation_3 Triangulation; + typedef CGAL::Conforming_constrained_Delaunay_triangulation_3 CCDT3; + std::cerr << "-- Starting dualize_complex" << std::endl; + std::cerr << "Imported mesh" << std::endl; + + // Create a temporary Surfaces_mesh_io (to load the mesh) + Surface_mesh_io K_init_mesh_io(mesh); + // Create a temporary associated complex (to identify simplices of mesh_io) + Chain_complex* _K = new Chain_complex(K_init_mesh_io); + std::cout << "------ _K:" << *_K; + + // Closing mesh_object_io by adding the icosphere + // Compute a bounding icosphere + Point center = K_init_mesh_io.centroid() ; + double r = K_init_mesh_io.radius(center) ; + Triangle_mesh ico; + CGAL::make_icosahedron(ico, center, r*BB_ratio); + CGAL::Subdivision_method_3::Loop_subdivision( + ico, CGAL::parameters::number_of_iterations(subdiv) + ); + + std::cerr << "Icosphere generated" << std::endl; + + // Add it to the mesh + mesh += ico ; + std::cerr << "Mesh concatenation" << std::endl; + std::string off_file("tmp/test_bounded.off"); + std::ofstream out (off_file , std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "Cannot output dualized mesh to:\n " << off_file << " - not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + out << mesh ; + out.close(); + + // Generate plc_facet_map + auto plc_facet_map = get(CGAL::face_patch_id_t(), mesh); + int cpt(0); + for (typename Triangle_mesh::Face_iterator it = mesh.faces_begin(); it!= mesh.faces_end(); ++it){ + plc_facet_map[*it] = cpt++; + } + + // Constrained Delaunay Tetraedrisation preserving plc_facet_map + auto ccdt = CGAL::make_conforming_constrained_Delaunay_triangulation_3(mesh, CGAL::parameters::plc_face_id(plc_facet_map)); + + // Detect refined constrained faces + std::vector > faces_constr(cpt); + for (typename Triangulation::Facet f : ccdt.constrained_facets()) { + if (ccdt.is_facet_constrained(f)) { + const int i(ccdt.face_constraint_index(f)); + faces_constr.at(i).push_back(f); + } + } + for (int i=0; i1) + std::cout << i << ": " << faces_constr.at(i).size() << std::endl; + } + + Triangulation tri_L = std::move(ccdt).triangulation(); + + // Build the output object + Complex_duality_data dualized_complex; + // Build the associated Triangulation_3_io + Triangulation_3_io L_tri_io(tri_L); + // Build the associated SimpComplex + dualized_complex.L_complex = std::make_shared(L_tri_io) ; + std::shared_ptr L(dualized_complex.L_complex); + std::cout << "------ L:" << *L; + + std::vector > indices(L->dimension()+1); + for (int q=0; q<=L->dimension(); ++q){ + for (int i=0; inumber_of_cells(q); ++i){ + indices.at(q).push_back(i); + } + } + std::string vtk_file("tmp/test_CDT3.vtk"); + Chain_complex::chain_complex_to_vtk(*L, vtk_file, &indices, "int"); + + // Build the Sub_chain_complex_mask encoding K_init inside L + dualized_complex.K_complex = compute_sub_chain_complex(*_K, *L); + // Remove the temporary complex + delete _K; + + return dualized_complex; + } + + + /** \brief Exports a chain complex to a Mesh_object_io */ + static Mesh_object_io& export_mesh_object(const Chain_complex& _CC) { + std::vector vcells ; + for (int q = 0; q <= _CC.dimension(); ++q) + for (size_t i = 0; i<_CC.number_of_cells(q); ++i) { + const Simplex& s(_CC.index_to_cell(i,q)) ; + vcells.push_back(s.vertices()) ; + } + + std::vector coords; + for (auto it = _CC.points().begin(); it != _CC.points().end(); ++it) + coords.push_back(Point(*it)); + Mesh_object_io &m = *(new Mesh_object_io(-3, coords, vcells)) ; + return m ; + } +} ; + +// =========== Cubical chain complex tools +// Build K sub-chain complex of L (homeomorphic to B^n) +// Adjust L size + +/*! + *\ingroup PkgHDVFDualityClasses + + The class `Duality_cubical_complex_tools` is dedicated to Alexander duality for 3D binary volumes. + + Starting from a Cubical_chain_complex `K_init`, the method `dualize_complex()` builds a `Cubical_chain_complex` `L` and `Sub_chain_complex_mask` `K`. + - L : complex built of the "full" bounding box of `K_init` + - K (Sub_chain_complex_mask) : Sub_chain_complex_mask identifying `K_init` inside `L` + + Use the `frame()` method from the `Cub_object_io` class to enlarge the bounding box (via a 1 pixel dilatation) if necessary. + + \tparam CoefficientRing a model of the `IntegralDomainWithoutDivision` concept providing the ring used to compute homology. + + \tparam Traits a geometric traits class model of the `HDVFTraits` concept. + */ + +template> +class Duality_cubical_complex_tools { +public: + /** \brief Type of cubical complexes used for the initial complex and \f$L\f$. */ + typedef Cubical_chain_complex Chain_complex ; + /** \brief Type of sub chain complex mask used to encode the sub-complex \f$K\f$. */ + typedef Sub_chain_complex_mask Sub_chain_complex ; + /** \brief Type returned by `dualize_complex()`. */ + typedef Complex_duality_data_t Complex_duality_data; + + // Constructor + Duality_cubical_complex_tools() {} + + + /** \brief Generates a subcomplex \f$K\f$K and a complex \f$L\f$ with \f$K\subseteq L\f$ from a cubical complex `K_init`. + * + * `L` is the bounding box of `K_init` (homeomorphic to a ball) and \f$K\f$ is a sub chain complex mask encoding `K_init`. + * + * The following figures shows the resulting complex with an initial simple cubical complex (right - sectional view): + * + * + * + * \param K_init Initial cubical chain complex (working mesh). + */ + static Complex_duality_data dualize_complex (const Chain_complex& K_init) { + Cub_object_io tmp_L ; + tmp_L.init(K_init.dimension(), K_init.size_bb()) ; + // Visit all boolean indices in the BB of _CC and insert corresponding cells in the Cub_object_io of L + for (size_t i=0; i tmpkhal(K_init.bindex_to_cell(i)) ; + tmp_L.add_cub(tmpkhal); + } + + // Build the output object + Complex_duality_data dualized_complex; + dualized_complex.L_complex = std::make_shared(tmp_L, Chain_complex::PRIMAL); + std::shared_ptr L(dualized_complex.L_complex) ; + + // Build the Sub_chain_complex_mask corresponding to _CC + dualized_complex.K_complex = std::make_shared (Sub_chain_complex(*L, false)) ; + std::shared_ptr K(dualized_complex.K_complex) ; + // Visit all cells of _CC and activate the corresponding bit in K + for (int q=0; q<=K_init.dimension(); ++q) { + for (size_t i=0; i khal(K_init.index_to_cell(i, q)) ; + const size_t j = L->cell_to_index(khal); + K->set_bit_on(q,j) ; + } + } + return dualized_complex; + } + + /** \brief Generates a subcomplex \f$K\f$K and a complex \f$L\f$ with \f$K\subseteq L\f$ from a `Cub_object_io` `K_init`. + * + * `L` is the bounding box of `K_init` (homeomorphic to a ball) and \f$K\f$ is a sub chain complex mask encoding `K_init`. + * + * The following figures shows the resulting complex with an initial simple cubical complex (right - sectional view): + * + * + * + * \param K_init Initial `Cub_object_io` (cubical object). + * \param primal_dual TBD + */ + static Complex_duality_data dualize_complex (const Cub_object_io& K_init, typename Chain_complex::Cubical_complex_primal_dual primal_dual) { + Chain_complex* _K = new Chain_complex(K_init, primal_dual); + Complex_duality_data res(dualize_complex(*_K)); + delete _K; + return res; + } +} ; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_GEOMETRIC_CHAIN_COMPLEX_TOOLS_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf.h b/HDVF/include/CGAL/HDVF/Hdvf.h new file mode 100644 index 000000000000..18078e0af0e4 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf.h @@ -0,0 +1,1739 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_HDVF_H +#define CGAL_HDVF_HDVF_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFHDVFClasses + + The class `Hdvf` implements homology and cohomology computation via homological discrete vector fields (%HDVF for short). It derives from `Hdvf_core` and shares all its data and methods. + + Perfect HDVFs provide various topological results: + - Betti numbers: are well known topological descriptors (providing the number of holes in each dimension). For a perfect HDVF, Betti numbers equal the number of critical cells (that can be obtained through `psc_flag()` with the `CRITICAL``PSC_flag` as argument). + - Homology/cohomology generators are actually algebraic objects, namely chains. Methods `homology_chain()` and `cohomology_chain()` return the homology and cohomology generator chain associated to a given critical cell. VTK export functions output all the cells of such chains with non zero coefficients. Figures here above illustrate such homology and co-homology generators. + - Homology/cohomology annotation: use `get_annotation()` or `get_coannotation()` to get the annotation/co-annotation of a cycle/co-cycle in the homology/cohomology basis (as a chain of critical cells). + - Homology/cohomology comparison: use `are_same_cycles()` or `are_same_cocycles()` to check if two cycles (or co-cycles) belong to the same homology / cohomology class. + + \cgalFigureBegin{HDVFannotation,HDVF_annotations.png} + Illustration of cycles annotation and comparison. Left: Generator \f$g(\sigma)\f$ associated to the critical cell \f$\sigma\f$; Middle: A cycle \f$\alpha\f$ ; Right: A second cycle \f$\beta\f$. + + For \f$\mathbb Z_2\f$ homology, the annotation of \f$\alpha\f$ is \f$\sigma\f$ (\f$\alpha\f$ equals \f$g(\sigma)\f$ up to a boundary), while the annotation of \f$\beta\f$ is \f$\sigma+\tau+\gamma\f$ (\f$\beta\f$ equals \f$g(\sigma)+g(\tau)+g(\gamma)\f$ up to a boundary). + + Therefore, `are_same_cycles` will return `true` for \f$\alpha\f$ and \f$g(\sigma)\f$, but `false` for \f$\beta\f$ and \f$g(\sigma)\f$. + \cgalFigureEnd + + Besides construction operations and methods (using the `A()` operation), the `Hdvf` class implements four other HDVF operations: R, M, W and MW together with appropriate `find_pair_xxx()` functions (see \ref secHDVFcomput "user documentation" for more details). + + \cgalModels{HDVF} + + \tparam ChainComplex a model of the `AbstractChainComplex` concept, providing the type of abstract chain complex used. + */ + +//These operations change the %HDVF (that is change homology / cohomology generators) and thus provide a convenient tool to move inside the "space of homology/cohomology computations". +// +//- `R()` operation is the "dual" of the A pairing operation (it cancels the pairing and turns back a `PRIMARY`/`SECONDARY` pair into a pair of `CRITICAL` cells) +//- `M()` operation exchanges a `PRIMARY` \f$\pi\f$ and a `CRITICAL` cell \f$\gamma\f$ (under conditions) and modifies the homology generator associated to \f$\gamma\f$ (while preserving is associated cohomology generator) +//- `W()` operation exchanges a `SECONDARY` \f$\sigma\f$ and a `CRITICAL` cell \f$\gamma\f$ (under conditions) and modifies the cohomology generator associated to \f$\gamma\f$ (while preserving is associated homology generator) +//- `MW()` operation exchanges a `PRIMARY` \f$\pi\f$ and a `SECONDARY` cell \f$\sigma\f$ (under conditions). See the introduction to HDVF for more details on this operation. + +template +class Hdvf : public Hdvf_core { +public: + /*! \brief Chain complex type */ + typedef ChainComplex Chain_complex; + + /*! \brief Type of coefficients used to compute homology. */ + typedef typename Chain_complex::Coefficient_ring Coefficient_ring; + + /*! + Type of parent Hdvf_core class. + */ + typedef Hdvf_core Base ; + + // Inherited types + using Column_chain = typename Base::Column_chain; + using Row_chain = typename Base::Row_chain; + using Column_matrix = typename Base::Column_matrix; + using Row_matrix = typename Base::Row_matrix; + + /** + * Builds an "empty" HDVF associated to K (with all cells critical). By default, the HDVF option is set to OPT_FULL (full reduction computed). + * + * \param K A chain complex (a model of `AbstractChainComplex`) + * \param hdvf_opt Option for HDVF computation (`OPT_BND`, `OPT_F`, `OPT_G` or `OPT_FULL`) + * \param dimension_restriction Determines if perfect HDVFs are computed along any dimensions (if `dimension_restriction` is -1) or a single dimension (specified by `dimension_restrictions`) + * + * \exception If the complex `K` is empty, raises a `%std::runtime_error`. + */ + Hdvf(const Chain_complex& K, int hdvf_opt = OPT_FULL, int dimension_restriction = -1) ; + + /* + * \brief Copy constructor. + */ + Hdvf(const Hdvf& hdvf) : Hdvf_core(hdvf) { } + + /** + * \brief Affectation operator. + * + * \exception Invalid_underlying_complex If `hdvf` does not share the same underlying complex, raises a `%std::runtime_error`. + */ + Hdvf& operator=(const Hdvf& hdvf) { + Base::operator=(hdvf); + return *this; + } + + /** \brief Constructor from the PRIMARY/SECONDARY/CRITICAL labels. + * + * If `with_build_reduction` is `false` check the combinatorial coherence of labels. If `with_build_reduction` is `true` checks that labels describe a valid HDVF (ie. \f$\partial(S)\_P\f$ is invertible). + * + * \param K A chain complex (a model of `AbstractChainComplex`) + * \param flags Vector of PSC labels along each dimensions. + * \param with_build_reduction If `false`, loads labels without building the reduction, if `true` builds the reduction (\f$\mathscr O(n^3)\f$). + * \param hdvf_opt Option for HDVF computation (`OPT_BND`, `OPT_F`, `OPT_G` or `OPT_FULL`) + * \param dimension_restriction Determines if perfect HDVFs are computed along any dimensions (if `dimension_restriction` is -1) or a single dimension (specified by `dimension_restrictions`) + * + * \exception Incoherent_flags If the flags provided are incoherent (or do not define an HDVF), raises a `%std::invalid_argument`. + */ + Hdvf(const ChainComplex& K, const std::vector >& flags, bool with_build_reduction = false, int hdvf_opt = OPT_FULL, int dimension_restriction = -1) + : Hdvf_core(K, flags, with_build_reduction, hdvf_opt, dimension_restriction) {} + + /* + * \brief Destructor. */ + ~Hdvf() { } + + // findPair functions for M + + /** \brief Checks if the pair of cells \f$(\pi, \gamma)\f$, of dimension q, is valid for M. + * + * The pair is valid if \f$\langle \mathrm f(\pi),\gamma\rangle\f$ is invertible. + * + * \param pi Index of the first cell (dimension `q`). + * \param gamma Index of the second cell (dimension `q+1`). + * \param q Dimension of cells. + */ + + bool is_valid_pair_for_M(size_t pi, size_t gamma, int q) { + Coefficient_ring coef(OSM::get_coefficient(this->_F_row.at(q),gamma, pi)); + return is_invertible(coef); + } + + /** + * \brief Finds a valid Cell_pair of dimension q for M. + * + * The function searches a pair of cells \f$(\pi, \gamma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\gamma\f$ `CRITICAL`, valid for M (ie.\ such that \f$\langle f(\pi), \gamma \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + Cell_pair find_pair_M(int q, bool &found) const; + + /** + * \brief Finds a valid Cell_pair of dimension q for M cointaining `tau`. + * + * The function searches a pair of cells \f$(\pi, \gamma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\gamma\f$ `CRITICAL` (one of them is `tau`), valid for M (ie.\ such that \f$\langle f(\pi), \gamma \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + * \param tau Cell of dimension `q` to pair. + */ + Cell_pair find_pair_M(int q, bool &found, size_t tau) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q for M. + * + * The function searches all pairs of cells \f$(\pi, \gamma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\gamma\f$ `CRITICAL`, valid for M (ie.\ such that \f$\langle f(\pi), \gamma \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + std::vector find_pairs_M(int q, bool &found) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q for M cointaining `tau`. + * + * The function searches all pairs of cells \f$(\pi, \gamma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\gamma\f$ `CRITICAL` (one of them is `tau`), valid for M (ie.\ such that \f$\langle f(\pi), \gamma \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + * \param tau Cell of dimension `q` to pair. + */ + std::vector find_pairs_M(int q, bool &found, size_t tau) const; + + // findPair functions for W + + /** \brief Checks if the pair of cells \f$(\sigma, \gamma)\f$, of dimension q, is valid for W. + * + * The pair is valid if \f$\langle \mathrm g(\gamma),\sigma\rangle\f$ is invertible. + * + * \param sigma Index of the first cell. + * \param gamma Index of the second cell. + * \param q Dimension of cells. + */ + + bool is_valid_pair_for_W(size_t sigma, size_t gamma, int q) { + Coefficient_ring coef(OSM::get_coefficient(this->_G_col.at(q),sigma, gamma)); + return is_invertible(coef); + } + + /** + * \brief Finds a valid Cell_pair of dimension q for W. + * + * The function searches a pair of cells \f$(\sigma, \gamma)\f$ with \f$\sigma\f$ `SECONDARY` and \f$\gamma\f$ `CRITICAL`, valid for W (ie.\ such that \f$\langle g(\gamma), \sigma \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + Cell_pair find_pair_W(int q, bool &found) const; + + /** + * \brief Finds a valid Cell_pair of dimension q for W cointaining `tau`. + * + * The function searches a pair of cells \f$(\sigma, \gamma)\f$ with \f$\sigma\f$ `SECONDARY` and \f$\gamma\f$ `CRITICAL` (one of them is `tau`), valid for W (ie.\ such that \f$\langle g(\gamma), \sigma \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + * \param tau Cell of dimension `q` to pair. + */ + Cell_pair find_pair_W(int q, bool &found, size_t tau) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q for W. + * + * The function searches all pairs of cells \f$(\sigma, \gamma)\f$ with \f$\sigma\f$ `SECONDARY` and \f$\gamma\f$ `CRITICAL`, valid for W (ie.\ such that \f$\langle g(\gamma), \sigma \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + std::vector find_pairs_W(int q, bool &found) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q for W cointaining `tau`. + * + * The function searches all pairs of cells \f$(\sigma, \gamma)\f$ with \f$\sigma\f$ `SECONDARY` and \f$\gamma\f$ `CRITICAL` (one of them is `tau`), valid for W (ie.\ such that \f$\langle g(\gamma), \sigma \rangle\f$ invertible). It returns the first valid pair found by iterators. + * It returns a vector of such pairs. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + * \param tau Cell of dimension `q` to pair. + */ + std::vector find_pairs_W(int q, bool &found, size_t tau) const; + + + + // findPair functions for MW + + /** \brief Checks if the pair of cells \f$(\pi, \sigma)\f$, of dimension q, is valid for MW. + * + * The pair is valid if \f$\langle \parital h(\pi),\sigma\rangle \text{ and } \langle h\partial(\sigma),\pi\rangle\f$ is invertible. + * + * \param pi Index of the first cell. + * \param sigma Index of the second cell. + * \param q Dimension of cells. + */ + + bool is_valid_pair_for_MW(size_t pi, size_t sigma, int q) { + Coefficient_ring coef; + bool res; + + // Compute (coz) + Row_chain row_sigma_D(this->_K.cod(sigma,q)); + row_sigma_D = this->projection(row_sigma_D, SECONDARY, q+1); + const Column_chain& col_pi_H(OSM::cget_column(this->_H_col.at(q), pi)); + coef = row_sigma_D * col_pi_H; + res = is_invertible(coef); + + // Compute (z) + Column_chain col_sigma_D(this->_K.d(pi,q)); + col_sigma_D = this->projection(col_sigma_D, PRIMARY, q-1); + Row_chain row_pi_H(OSM::get_row(this->_H_col.at(q-1), sigma)); + coef = row_pi_H * col_sigma_D; + res = res && is_invertible(coef); + return res; + } + + /** + * \brief Finds a valid Cell_pair of dimension q for MW. + * + * The function searches a pair of cells \f$(\pi, \sigma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY`, valid for MW (ie.\ such that \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ invertible and \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + Cell_pair find_pair_MW(int q, bool &found) const; + + /** + * \brief Finds a valid Cell_pair of dimension q for MW cointaining `tau`. + * + * The function searches a pair of cells \f$(\pi, \sigma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY` (one of them is `tau`), valid for MW (ie.\ such that \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ invertible and \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + * \param tau Cell of dimension `q` to pair. + */ + Cell_pair find_pair_MW(int q, bool &found, size_t tau) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q for MW. + * + * The function searches all pairs of cells \f$(\pi, \sigma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY`, valid for MW (ie.\ such that \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ invertible and \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + std::vector find_pairs_MW(int q, bool &found) const; + + /** + * \brief Finds *all* valid Cell_pair of dimension q for W cointaining `tau`. + * + * The function searches all pairs of cells \f$(\pi, \sigma)\f$ with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY` (one of them is `tau`), valid for MW (ie.\ such that \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ invertible and \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Dimension of the pair searched. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + * \param tau Cell of dimension `q` to pair. + */ + std::vector find_pairs_MW(int q, bool &found, size_t tau) const; + + /** \brief Checks if the pair of cells \f$(\pi, \sigma)\f$, of dimensions q / q+1, is valid for R. + * + * The pair is valid if \f$\langle h(\pi),\sigma\rangle\f$ is invertible. + * + * \param pi Index of the first cell. + * \param sigma Index of the second cell. + * \param q Lower dimension of cells. + */ + + bool is_valid_pair_for_R(size_t pi, size_t sigma, int q) { + Coefficient_ring coef(OSM::get_coefficient(this->_H_col.at(q),sigma, pi)); + return is_invertible(coef); + } + + // Hdvf methods + + /** + * \brief R operation (cancels a A operation). + * + * A pair of cells \f$(\pi, \sigma)\f$ of respective dimension q and q+1, with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY`, is valid for R if \f$\langle h(\pi), \sigma \rangle\f$ is invertible. After the R operation, \f$\pi\f$ and \f$\sigma\f$ become `CRITICAL`. The R method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param pi First cell of the pair (dimension `q`) + * \param sigma Second cell of the pair (dimension `q+1`) + * \param q Dimension of the pair + * + * \exception Invalid_arguments If \f$\langle h(\pi), \sigma \rangle\f$ is not invertible, raises a `%std::invalid_argument` exception. + * + * \exception Incorrect_hdvf_options Operations can be computed only under HDVF_FULL options, raises a `%std::runtime_error` exception otherwise. + */ + void R(size_t pi, size_t sigma, int q); + + /** + * \brief R operation (cancels a A operation). + * + * A pair of cells \f$(\pi, \sigma)\f$ of respective dimension q and q+1, with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY`, is valid for R if \f$\langle h(\pi), \sigma \rangle\f$ is invertible. After the R operation, \f$\pi\f$ and \f$\sigma\f$ become `CRITICAL`. The R method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param p Argument `Cell_pair` containing \f$\pi\f$, \f$\sigma\f$ and the dimension of the pair. + */ + void R(const Cell_pair& p) { R(p.sigma, p.tau, p.dim); }; + + /** + * \brief M operation. + * + * A pair of cells \f$(\pi, \gamma)\f$ of dimension q, with \f$\pi\f$ `PRIMARY` and \f$\gamma\f$ `CRITICAL`, is valid for M if \f$\langle f(\pi), \gamma \rangle\f$ is invertible. After the M operation, \f$\pi\f$ becomes `CRITICAL` and \f$\gamma\f$ become `PRIMARY`. The M method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param pi First cell of the pair (dimension `q`) + * \param gamma Second cell of the pair (dimension `q`) + * \param q Dimension of the pair + * + * \exception Invalid_operation If `q` is the dimension of the complex, `M`operation is not valid and the method raises a `%std::runtime_error` exception. + * + * \exception Invalid_arguments If \f$\langle f(\pi), \gamma \rangle\f$ is not invertible, raises a `%std::invalid_argument` exception. + * + * \exception Incorrect_hdvf_options Operations can be computed only under HDVF_FULL options, raises a `%std::runtime_error` exception otherwise. + */ + void M(size_t pi, size_t gamma, int q); + + /** + * \brief M operation. + * + * A pair of cells \f$(\pi, \gamma)\f$ of dimension q, with \f$\pi\f$ `PRIMARY` and \f$\gamma\f$ `CRITICAL`, is valid for M if \f$\langle f(\pi), \gamma \rangle\f$ is invertible. After the M operation, \f$\pi\f$ becomes `CRITICAL` and \f$\gamma\f$ become `PRIMARY`. The M method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param p Argument `Cell_pair` containing \f$\pi\f$, \f$\gamma\f$ and the dimension of the pair. + */ + void M(const Cell_pair& p) { M(p.sigma, p.tau, p.dim); } + + /** + * \brief W operation. + * + * A pair of cells \f$(\sigma, \gamma)\f$ of dimension q, with \f$\sigma\f$ `SECONDARY` and \f$\gamma\f$ `CRITICAL`, is valid for W if \f$\langle g(\gamma), \sigma \rangle\f$ is invertible. After the W operation, \f$\sigma\f$ becomes `CRITICAL` and \f$\gamma\f$ become `SECONDARY`. The W method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param sigma First cell of the pair (dimension `q`) + * \param gamma Second cell of the pair (dimension `q`) + * \param q Dimension of the pair + * + * \exception Invalid_operation If `q` is 0, `W`operation is not valid and the method raises a `%std::runtime_error` exception. + * + * \exception Invalid_arguments If \f$\langle g(\gamma), \sigma \rangle\f$ is not invertible, raises a `%std::invalid_argument` exception. + * + * \exception Incorrect_hdvf_options Operations can be computed only under HDVF_FULL options, raises a `%std::runtime_error` exception otherwise. + */ + void W(size_t sigma, size_t gamma, int q); + + /** + * \brief W operation. + * + * A pair of cells \f$(\sigma, \gamma)\f$ of dimension q, with \f$\sigma\f$ `SECONDARY` and \f$\gamma\f$ `CRITICAL`, is valid for W if \f$\langle g(\gamma), \sigma \rangle\f$ is invertible. After the W operation, \f$\sigma\f$ becomes `CRITICAL` and \f$\gamma\f$ become `SECONDARY`. The W method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param p Argument `Cell_pair` containing \f$\sigma\f$, \f$\gamma\f$ and the dimension of the pair. + */ + void W(const Cell_pair& p) { W(p.sigma, p.tau, p.dim); }; + + /** + * \brief MW operation. + * + * A pair of cells \f$(\pi, \sigma)\f$ of dimension q, with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY`, is valid for MW if \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ is invertible and \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ is invertible. After the MW operation, \f$\pi\f$ becomes `SECONDARY` and \f$\sigma\f$ become `PRIMARY`. The MW method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param pi First cell of the pair (dimension `q`) + * \param sigma Second cell of the pair (dimension `q`) + * \param q Dimension of the pair + * + * \exception Invalid_operation If `q` is 0 or the dimension of the complex, `MW`operation is not valid and the method raises a `%std::runtime_error` exception. + * + * \exception Invalid_arguments If \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ or \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ are not invertible, raises a `%std::invalid argument` exception. + * + * \exception Incorrect_hdvf_options Operations can be computed only under HDVF_FULL options, raises a `%std::runtime_error` exception otherwise. + */ + void MW(size_t pi, size_t sigma, int q); + + /** + * \brief MW operation. + * + * A pair of cells \f$(\pi, \sigma)\f$ of dimension q, with \f$\pi\f$ `PRIMARY` and \f$\sigma\f$ `SECONDARY`, is valid for MW if \f$\langle h_{q-1}\partial_q(\pi), \sigma \rangle\f$ is invertible and \f$\langle \partial_{q+1} h_q(\sigma), \pi \rangle\f$ is invertible. After the MW operation, \f$\pi\f$ becomes `SECONDARY` and \f$\sigma\f$ become `PRIMARY`. The MW method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param p Argument `Cell_pair` containing \f$\pi\f$, \f$\sigma\f$ and the dimension of the pair. + */ + void MW(const Cell_pair& p) { MW(p.sigma, p.tau, p.dim); }; + + /** \brief Compute \f$z_q\f$ function on a cell of dimension `q`. + * + * This function was first defined in tri-partitions; formally \f$z_q = \mathrm{Id} - h_{q-1}\circ \partial_q\f$. + * + * When the HDVF is perfect, \f$z_q(\sigma)\f$ is the canonical cycle associated to \f$\sigma\f$. Intuitively: + * - if \f$\sigma\f$ is PRIMARY, \f$z_q(\sigma)\f$ is a chain of SECONDARY cells ; if the HDVF is perfect \f$z_q(\sigma)\f$ is a cycle (the unique cycle containing \f$\sigma\f$ and SECONDARY cells). + * - if \f$\sigma\f$ is SECONDARY, this cycle is null + * - if \f$\sigma\f$ is CRITICAL, \f$z_q(\sigma)\f$ matches \f$g(\sigma)\f$ ; if the HDVF is perfect \f$z_q(\sigma)\f$ hence provides the homology generator associated to \f$\sigma\f$). + * + * Moreover, in `HDVF_persistent`, `z` function provides persistent homology generators. + * + * \param sigma Cell index. + * \param q Cell dimension. + */ + + Column_chain z(size_t sigma, int q) { + CGAL_precondition(this->_K.is_valid_cell(sigma, q)); + Column_chain res(this->_K.number_of_cells(q)); + + if (this->psc_flag(sigma,q) == SECONDARY) + // 0 + return res; + else if (this->psc_flag(sigma,q) == CRITICAL) { + // sigma + G * sigma + res.set_coefficient(sigma,1); + res += OSM::cget_column(this->_G_col.at(q), sigma); + return res; + } + else { // PRIMARY + // sigma - hd(sigma) + res.set_coefficient(sigma,1); + Column_chain col_sigma_D(this->_K.d(sigma,q)); + res -= this->_H_col.at(q-1) * col_sigma_D; + return res; + } + } + + /** \brief Compute \f$z^q\f$ function on a cell of dimension `q`. + * + * This function was first defined in tri-partitions; formally \f$z^q = \mathrm{Id} - h_{q}^*\circ \partial_{q+1}^*\f$. + * + * When the HDVF is perfect, \f$z_q(\sigma)\f$ is the canonical cocycle associated to \f$\sigma\f$. Intuitively: + * - if \f$\sigma\f$ is SECONDARY, \f$z^q(\sigma)\f$ is a cochain of PRIMARY cells ; if the HDVF is perfect \f$z^q(\sigma)\f$ is a cocycle (the unique cocycle containing \f$\sigma\f$ and PRIMARY cells). + * - if \f$\sigma\f$ is PRIMARY, this cocycle is null + * - if \f$\sigma\f$ is CRITICAL, \f$z^q(\sigma)\f$ matches \f$f^*(\sigma)\f$ ; if the HDVF is perfect \f$z^q(\sigma)\f$ hence provides the cohomology generator associated to \f$\sigma\f$). + * + * Moreover, in `HDVF_persistent`, `co_z` function provides persistent cohomology generators. + * + * \param sigma Cell index. + * \param q Cell dimension. + */ + + Column_chain co_z(size_t sigma, int q) { + CGAL_precondition(this->_K.is_valid_cell(sigma, q)); + Row_chain res(this->_K.number_of_cells(q)); + + if (this->psc_flag(sigma,q) == PRIMARY) + // 0 + return res.transpose(); + else if (this->psc_flag(sigma,q) == CRITICAL) { + // sigma + F* * sigma + res.set_coefficient(sigma,1); + res += OSM::cget_row(this->_F_row.at(q), sigma); + return res.transpose(); + } + else { // SECONDARY + // (sigma - dh(sigma))^* + res.set_coefficient(sigma,1); + Row_chain row_sigma_D(this->_K.cod(sigma,q)); + res -= row_sigma_D * this->_H_col.at(q); + return res.transpose(); + } + } + +public: + // Compute dh(sigma), hd(sigma) and and + // dh and hd matrices are not stored + // So we provide functions to get these values + + /** \brief Computes \f$h_{q-1}\circ \partial_q(\sigma)\f$. + * + * The \f$h_{q-1}\circ \partial_q\f$ function is used both in MW operation and in tri-partitions. As the matrix is not stored in the reduction, we provide methods to compute if efficiently. + * + * \param sigma Index of the cell + * \param q Dimension of the cell + */ + Column_chain hd(size_t sigma, int q) { + PSC_flag flag(this->psc_flag(sigma,q)); + if (flag == PRIMARY) { + Column_chain dsigma(this->_K.d(sigma,q)); + dsigma = this->projection(dsigma,PRIMARY,q-1); + return this->_H_col.at(q-1) * dsigma; + } + else if (flag == SECONDARY) { + Column_chain res(this->_K.number_of_cells(q)); + res.set_coefficient(sigma, 1); + return res; + } + else { // CRITICAL + return OSM::cget_column(this->_G_col.at(q), sigma); + } + } + + /** \brief Computes \f$\langle h_{q-1}\circ \partial_q(\sigma), \tau\rangle\f$. + * + * The \f$h_{q-1}\circ \partial_q\f$ function is used both in MW operation and in tri-partitions. As the matrix is not stored in the reduction, we provide methods to compute if efficiently. + * + * \param sigma Index of the first cell + * \param tau Index of the second cell + * \param q Dimension of the cell + */ + Coefficient_ring hd(size_t sigma, size_t tau, int q) { + PSC_flag flag(this->psc_flag(sigma,q)); + PSC_flag flag_tau(this->psc_flag(tau, q)); + if ((flag_tau == PRIMARY) || (flag_tau == CRITICAL)) // tau PRIMARY, CRITICAL + return Column_chain(this->_K.number_of_cells(q)); + else { // tau SECONDARY + // Check the flag of sigma + if (flag == PRIMARY) { + // PRIMARY + Row_chain H_tau(OSM::get_row(this->_H_col.at(q-1))); + Column_chain dsigma(this->_K.d(sigma,q)); + dsigma = this->projection(dsigma,PRIMARY,q-1); + return H_tau * dsigma; + } + else if (flag == SECONDARY) { + // SECONDARY + Column_chain res(this->_K.number_of_cells(q)); + if (sigma == tau) + res.set_coefficient(sigma, 1); + return res; + } + else // CRITICAL + return OSM::get_coefficient(this->_G_col.at(q), tau, sigma); + } + } + + /** \brief Computes \f$\partial_{q+1}\circ h_{q}(\sigma)\f$. + * + * The \f$\partial_{q+1}\circ h_{q}(\sigma)\f$ function is used both in MW operation and in tri-partitions. As the matrix is not stored in the reduction, we provide methods to compute if efficiently. + * + * \param sigma Index of the cell + * \param q Dimension of the cell + */ + Column_chain dh(size_t sigma, int q) { + PSC_flag flag(this->psc_flag(sigma,q)); + if (flag == PRIMARY) { + const Column_chain& hsigma(CGAL::OSM::cget_column(this->_H_col.at(q), sigma)); + return this->_K.boundary_matrix(q+1) * hsigma; + } + else { + Column_chain res(this->_K.number_of_cells(q)); + return res; + } + } + + /** \brief Computes \f$\langle \partial_{q+1}\circ h_{q}(\sigma), \tau\rangle\f$. + * + * The \f$\partial_{q+1}\circ h_{q}\f$ function is used both in MW operation and in tri-partitions. As the matrix is not stored in the reduction, we provide methods to compute if efficiently. + * + * \param sigma Index of the first cell + * \param tau Index of the second cell + * \param q Dimension of the cell + */ + Coefficient_ring dh(size_t sigma, size_t tau, int q) { + PSC_flag flag(this->psc_flag(sigma,q)); + if (flag == PRIMARY) { + Row_chain res(this->_K.cod(sigma,q)); + const Column_chain& res2(OSM::cget_column(this->_H_col.at(q)), tau); + return res*res2; + } + else + return 0; + } + + + /** + * \brief Gets the annotation of a cycle in the homology basis. + * + * The method returns the image of a cycle by the morphism \f$f\f$, that is, a linear combination of critical cells (corresponding to the decomposition of the cycle in the homology basis). + * If the annotation has a single non zero coefficient for a given critical cell \f$\sigma\f$, then the cycle belongs to the class of the homology generator \f$g(\sigma)\f$. + * + * \warning Will raise an error is the chain provided is not a cycle. + * \pre The `HDVF` must be perfect. + * + * \param chain The cycle to annotate in the homology basis. + * \param dim Dimension of the cycle. + * + * \exception Invalid_argument If the `chain` provided is not a cycle, raises a `%std::invalid_argument` exception. + */ + Column_chain get_annotation(Column_chain chain, int dim) const + { + // Check that the chain is a cycle (must belong to the kernel of the boundary operator) + Column_chain bnd(this->_DD_col.at(dim) * chain) ; + if (!bnd.is_null()) + throw(std::invalid_argument("get_annotation: the chain provided is not a cycle")); + + // Compute the annotation + return (this->_F_row.at(dim) * chain + this->projection(chain, CRITICAL, 1)) ; + } + + /** + * \brief Gets the co-annotation of a co-cycle in the cohomology basis. + * + * The method returns the image of a co-cycle by the morphism \f$g^*\f$, that is, a linear combination of critical cells (corresponding to the decomposition of the co-cycle in the cohomology basis). + * If the annotation has a single non zero coefficient for a given critical cell \f$\sigma\f$, then the co-cycle belongs to the class of the cohomology generator \f$f^*(\sigma)\f$. + * + * \warning Will raise an error is the chain provided is not a co-cycle. + * \warning The HDVF must be perfect. + * + * \param chain The co-cycle to annotate in the homology basis. + * \param dim Dimension of the co-cycle. + * + * \exception Invalid_argument If the `chain` provided is not a co-cycle, raises a `%std::invalid_argument` exception. + */ + Row_chain get_coannotation(Row_chain chain, int dim) const + { + // Check that the chain is a co-cycle (must belong to the kernel of the boundary operator) + Row_chain bnd(chain * this->_DD_col.at(dim+1)) ; + if (!bnd.is_null()) + throw(std::invalid_argument("get_coannotation: the chain provided is not a co-cycle")); + + // Compute the co-annotation + return (chain * this->_G_col.at(dim) + this->projection(chain, CRITICAL, 1)) ; + } + + /** + * \brief Checks if two cycles belong to the same homology class. + * + * \warning Will raise an error is chains provided are not cycles. + * \warning The HDVF must be perfect. + * + * \param chain1 First cycle. + * \param chain2 Second cycle. + * \param dim Dimension of both cycles. + * + * \exception Invalid_argument If `chain1` or `chain2` are not cycles, raises a `%std::invalid_argument` exception. + */ + bool are_same_cycles (Column_chain chain1, Column_chain chain2, int dim) + { + // Check if both chains are cycles (must belong to the kernel of the boundary operator) + Column_chain bnd1(this->_DD_col.at(dim) * chain1), bnd2(this->_DD_col.at(dim) * chain2) ; + if (!bnd1.is_null()) + throw(std::invalid_argument("get_annotation: chain1 is not a cycle")); + if (!bnd2.is_null()) + throw(std::invalid_argument("get_annotation: chain2 is not a cycle")); + + Column_chain annot1(get_annotation(chain1, dim)), annot2(get_annotation(chain2, dim)) ; + return annot1 == annot2 ; + } + + /** + * \brief Checks if two co-cycles belong to the same cohomology class. + * + * \warning Will raise an error is chains provided are not co-cycles. + * \warning The HDVF must be perfect. + * + * \param chain1 First co-cycle. + * \param chain2 Second co-cycle. + * \param dim Dimension of both co-cycles. + * + * \exception Invalid_argument If `chain1` or `chain2` are not co-cycle, raises a `%std::invalid_argument` exception. + */ + bool are_same_cocycles (Row_chain chain1, Row_chain chain2, int dim) + { + // Check if both chains are cocycles (must belong to the kernel of the boundary^* operator) + Row_chain cobnd1(chain1 * this->_DD_col.at(dim)), cobnd2(chain2 * this->_DD_col.at(dim)) ; + if (!cobnd1.is_null()) + throw(std::invalid_argument("get_coannotation: chain1 is not a co-cycle")); + if (!cobnd2.is_null()) + throw(std::invalid_argument("get_coannotation: chain2 is not a co-cycle")); + + Row_chain coannot1(get_coannotation(chain1, dim)), coannot2(get_coannotation(chain2, dim)) ; + return coannot1 == coannot2 ; + } +}; + +// Constructor for the Hdvf class +template +Hdvf::Hdvf(const ChainComplex& K, int hdvf_opt, int dimension_restriction) : Hdvf_core(K, hdvf_opt, dimension_restriction) { + // Check if the complex is non empty + size_t acc(K.number_of_cells(0)); + for (int q=1; q = +- 1 with cell1 primary, cell2 critical +// First version: returns a pair of dimensions q +// Second version: returns all the pairs containing sigma + +// \brief find a valid Cell_pair for M in dimension q + +template +Cell_pair Hdvf::find_pair_M(int q, bool &found) const { + found = false; + Cell_pair p; + + if (this->_hdvf_opt & (OPT_F | OPT_FULL)) { + // Search for +-1 in _F - iterate over rows + for (OSM::Bitboard::iterator it_row = this->_F_row[q].begin(); (it_row != this->_F_row[q].end() && !found); ++it_row) { + const Row_chain &row(OSM::cget_row(this->_F_row[q],*it_row)); + + // Iterate through the entries of the row + for (typename Row_chain::const_iterator it = row.begin(); (it != row.end() && !found); ++it) { + if (is_invertible(it->second)) { + // If an entry with coefficient 1 or -1 is found, set the pair and mark as found + p.sigma = it->first; // primary cell + p.tau = *it_row; // critical cell + p.dim = q; + found = true; + } + } + } + } + return p; +} + +// find a valid Cell_pair containing tau for M in dimension q + +template +Cell_pair Hdvf::find_pair_M(int q, bool &found, size_t tau) const { + found = false; + Cell_pair p; + if (this->_flag.at(q).at(tau) == SECONDARY) + return p ; // Empty / found false + + if (this->_hdvf_opt & (OPT_F | OPT_FULL)) { + // If tau is primary, search for gamma such that =+-1 + if (this->_flag.at(q).at(tau) == PRIMARY) { + // Get the column of tau + const Column_chain& col(OSM::get_column(this->_F_row.at(q), tau)) ; + for (typename Column_chain::const_iterator it = col.cbegin(); (it != col.cend() && !found); ++it) { + if (is_invertible(it->second)) { + p.sigma = tau ; + p.tau = it->first ; + p.dim = q ; + found = true ; + } + } + } + // If tau is critical, search for pi such that =+-1 + if (this->_flag.at(q).at(tau) == CRITICAL) { + // Search along the row of tau + const Row_chain& row(OSM::cget_row(this->_F_row.at(q), tau)) ; + for (typename Row_chain::const_iterator it = row.cbegin(); (it != row.cend() && !found); ++it) { + if (is_invertible(it->second)) { + p.sigma = it->first ; // primary cell + p.tau = tau ; // critical cell + p.dim = q ; + found = true; + } + } + } + } + return p; +} + +// find all the valid Cell_pair for M in dimension q +template +std::vector Hdvf::find_pairs_M(int q, bool &found) const { + found = false; + std::vector pairs; + if (this->_hdvf_opt & (OPT_F | OPT_FULL)) { + // Search for +-1 in _F - iterate over rows + for (OSM::Bitboard::iterator it_row = this->_F_row[q].begin(); it_row != this->_F_row[q].end() ; ++it_row) { + const Row_chain &row(OSM::cget_row(this->_F_row[q],*it_row)); + + // Iterate through the entries of the row + for (typename Row_chain::const_iterator it = row.begin(); it != row.end(); ++it) { + if (is_invertible(it->second)) { + // If an entry with coefficient 1 or -1 is found, set the pair and add it + Cell_pair p ; + p.sigma = it->first; // primary cell + p.tau = *it_row; // critical cell + p.dim = q; + pairs.push_back(p) ; + } + } + } + } + found = !pairs.empty() ; + return pairs; +} + +// find all the valid Cell_pair containing tau for M in dimension q +template +std::vector Hdvf::find_pairs_M(int q, bool &found, size_t tau) const { + found = false; + std::vector pairs; + if (this->_flag.at(q).at(tau) == SECONDARY) + return pairs ; // Empty / found false + + if (this->_hdvf_opt & (OPT_F | OPT_FULL)) { + // If tau is primary, search for gamma such that =+-1 + if (this->_flag.at(q).at(tau) == PRIMARY) { + // Get the column of tau + const Column_chain& col(OSM::get_column(this->_F_row.at(q), tau)) ; + for (typename Column_chain::const_iterator it = col.cbegin(); it != col.cend(); ++it) { + if (is_invertible(it->second)) { + Cell_pair p ; + p.sigma = tau ; + p.tau = it->first ; + p.dim = q ; + found = true ; + pairs.push_back(p); + } + } + } + // If tau is critical, search for pi such that =+-1 + if (this->_flag.at(q).at(tau) == CRITICAL) { + const Row_chain& row(OSM::get_row(this->_F_row.at(q), tau)) ; + for (typename Row_chain::const_iterator it = row.cbegin(); it != row.cend(); ++it) { + if (is_invertible(it->second)) { + Cell_pair p ; + p.sigma = it->first ; // primary cell + p.tau = tau ; // critical cell + p.dim = q ; + pairs.push_back(p) ; + } + } + } + } + found = !pairs.empty() ; + return pairs; +} + +// Methods to find a pair of cells for W in dimension q +// -> = +- 1 with cell1 secondary, cell2 critical +// First version: returns a pair of dimensions q +// Second version: returns all the pairs containing sigma + +// find a valid Cell_pair for W in dimension q +template +Cell_pair Hdvf::find_pair_W(int q, bool &found) const { + found = false; + Cell_pair p; + + if (this->_hdvf_opt & (OPT_G | OPT_FULL)) { + // Search for +-1 in _G - iterate over cols + for (OSM::Bitboard::iterator it_col = this->_G_col[q].begin(); (it_col != this->_F_row[q].end() && !found); ++it_col) { + const Column_chain &col(this->_G_col[q][*it_col]); + + // Iterate through the entries of the col + for (typename Column_chain::const_iterator it = col.begin(); (it != col.end() && !found); ++it) { + if (is_invertible(it->second)) { + // If an entry with coefficient 1 or -1 is found, set the pair and mark as found + p.sigma = it->first; // secondary cell + p.tau = *it_col; // critical cell + p.dim = q; + found = true; + } + } + } + } + return p; +} + +// find a valid Cell_pair containing tau for W in dimension q +template +Cell_pair Hdvf::find_pair_W(int q, bool &found, size_t tau) const { + found = false; + Cell_pair p; + if (this->_flag.at(q).at(tau) == PRIMARY) + return p ; // Empty / found false + + if (this->_hdvf_opt & (OPT_G | OPT_FULL)) { + // If tau is primary, search for gamma such that =+-1 + if (this->_flag.at(q).at(tau) == SECONDARY) { + for (OSM::Bitboard::iterator it_col = this->_G_col.at(q).begin(); (it_col != this->_G_col.at(q).end() && !found); ++it_col) { + if (is_invertible(OSM::get_coefficient(this->_G_col.at(q), tau, *it_col))) { + p.sigma = tau ; // secondary cell + p.tau = *it_col ; // critical cell + p.dim = q ; + found = true; + } + } + } + // If tau is critical, search for sigma such that =+-1 + if (this->_flag.at(q).at(tau) == CRITICAL) { + Column_chain col(OSM::get_column(this->_G_col.at(q), tau)) ; + for (typename Column_chain::const_iterator it = col.cbegin(); (it != col.cend() && !found); ++it) { + if (is_invertible(it->second)) { + p.sigma = it->first ; // secondary cell + p.tau = tau ; // critical cell + p.dim = q ; + found = true; + } + } + } + } + return p; +} + +// find all the valid Cell_pair for W in dimension q +template +std::vector Hdvf::find_pairs_W(int q, bool &found) const { + found = false; + std::vector pairs; + + if (this->_hdvf_opt & (OPT_G | OPT_FULL)) { + // Search for +-1 in _G - iterate over cols + for (OSM::Bitboard::iterator it_col = this->_G_col[q].begin(); it_col != this->_F_row[q].end(); ++it_col) { + const Column_chain &col = this->_G_col[q][*it_col]; + + // Iterate through the entries of the col + for (typename Column_chain::const_iterator it = col.begin(); it != col.end(); ++it) { + if (is_invertible(it->second)) { + // If an entry with coefficient 1 or -1 is found, set the pair and mark as found + Cell_pair p ; + p.sigma = it->first; // secondary cell + p.tau = *it_col; // critical cell + p.dim = q; + pairs.push_back(p) ; + } + } + } + } + found = !pairs.empty() ; + return pairs; +} + +// find all the valid Cell_pair containing tau for W in dimension q +template +std::vector Hdvf::find_pairs_W(int q, bool &found, size_t tau) const { + found = false; + std::vector pairs; + if (this->_flag.at(q).at(tau) == PRIMARY) + return pairs ; // Empty / found false + + if (this->_hdvf_opt & (OPT_G | OPT_FULL)) { + // If tau is primary, search for gamma such that =+-1 + if (this->_flag.at(q).at(tau) == SECONDARY) { + for (OSM::Bitboard::iterator it_col = this->_G_col.at(q).begin(); it_col != this->_G_col.at(q).end(); ++it_col) { + if (is_invertible(get_coefficient(this->_G_col.at(q), tau, *it_col))) { + Cell_pair p ; + p.sigma = tau ; // secondary cell + p.tau = *it_col ; // critical cell + p.dim = q ; + pairs.push_back(p) ; + } + } + } + // If tau is critical, search for sigma such that =+-1 + if (this->_flag.at(q).at(tau) == CRITICAL) { + Column_chain col(OSM::get_column(this->_G_col.at(q), tau)) ; + for (typename Column_chain::const_iterator it = col.cbegin(); it != col.cend(); ++it) { + if (is_invertible(it->second)) { + Cell_pair p ; + p.sigma = it->first ; // secondary cell + p.tau = tau ; // critical cell + p.dim = q ; + pairs.push_back(p) ; + } + } + } + } + found = !pairs.empty() ; + return pairs; +} + +// Method to find a pair of cells for MW in dimension q +// -> = +- 1 and = +- 1 with cell1 primary, cell2 secondary +// First version: returns a pair of dimensions q +// Second version: returns all the pairs containing sigma + +// find a valid Cell_pair for MW in dimension q +template +Cell_pair Hdvf::find_pair_MW(int q, bool &found) const { + found = false; + Cell_pair p; + + if (this->_hdvf_opt & OPT_FULL) { + // pi and sigma must at least satisfy that col pi of H_q and row sigma of H_q-1 are non zero + // iterate on H_q and H_q-1 accordingly + for (OSM::Bitboard::iterator it_pi = this->_H_col[q].begin(); (it_pi != this->_H_col[q].end() && !found); ++it_pi) { + const size_t pi = *it_pi ; + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + for (size_t sigma = 0; (sigma < this->_H_col.at(q-1).dimensions().first && !found); ++sigma) { + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + if (!H11q1.is_null()) { + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + + // Compute xi and xi' to test the validity of MW + + const Coefficient_ring xi = projS_cod_sigma * H11 ; + const Coefficient_ring xip = H11q1 * projP_d_pi ; + found = (is_invertible(xi) && is_invertible(xip)) ; + if (found) { + p.sigma = pi ; // primary cell + p.tau = sigma ; // secondary cell + p.dim = q ; + } + } + } + } + } + return p; +} + +// find a valid Cell_pair containing tau for MW in dimension q +template +Cell_pair Hdvf::find_pair_MW(int q, bool &found, size_t tau) const { + found = false; + Cell_pair p; + + if (this->_flag.at(q).at(tau) == CRITICAL) + return p ; // Empty / found false + + if (this->_hdvf_opt & OPT_FULL) { + // If tau is primary (rename pi), search for a valid sigma + if (this->_flag.at(q).at(tau) == PRIMARY) { + const size_t pi(tau) ; + // Col pi of H_q and proj_P(d(pi)) must at least be non empty + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + + if (H11.is_null() || projP_d_pi.is_null()) + return p ; + + // Search for sigma with col_sigma(H_q-1) non empty + for (size_t sigma = 0; (sigma < this->_H_col.at(q-1).dimensions().first && !found); ++sigma) { + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + if (!H11q1.is_null()) { + // and proj_S(cod(sigma)) non empty + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + if (!projS_cod_sigma.is_null()) { + // test xi and xip + const Coefficient_ring xi = projS_cod_sigma * H11 ; + const Coefficient_ring xip = H11q1 * projP_d_pi ; + found = (is_invertible(xi) && is_invertible(xip)) ; + if (found) { + p.sigma = pi ; // primary cell + p.tau = sigma ; // critical cell + p.dim = q ; + } + } + } + } + } + else { // cell is secondary + // cout << "secondary" << endl ; + const size_t sigma(tau) ; + // Row sigma of H_q-1 and proj_S(cod(sigma)) must at least be non empty + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + if (H11q1.is_null() || projS_cod_sigma.is_null()) + return p ; + + // Search for pi with col pi of H_q non empty + for (OSM::Bitboard::iterator it_pi = this->_H_col[q].begin(); (it_pi != this->_H_col[q].end() && !found); ++it_pi) { + const size_t pi(*it_pi) ; + + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + // proj_P of d(pi) must also be non empty + if (!projP_d_pi.is_null()) { + // test xi and xip + const Coefficient_ring xi = projS_cod_sigma * H11 ; + const Coefficient_ring xip = H11q1 * projP_d_pi ; + found = (is_invertible(xi) && is_invertible(xip)) ; + if (found) { + p.sigma = pi ; // primary cell + p.tau = sigma ; // secondary cell + p.dim = q ; + } + } + } + } + } + return p; +} + +// find all the valid Cell_pair for MW in dimension q +template +std::vector Hdvf::find_pairs_MW(int q, bool &found) const { + found = false; + std::vector pairs; + + if (this->_hdvf_opt & OPT_FULL) { + // pi and sigma must at least satisfy that col pi of H_q and row sigma of H_q-1 are non zero + // iterate on H_q and H_q-1 accordingly + for (OSM::Bitboard::iterator it_pi = this->_H_col[q].begin(); it_pi != this->_H_col[q].end(); ++it_pi) { + const size_t pi = *it_pi ; + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + for (size_t sigma = 0; sigma < this->_H_col.at(q-1).dimensions().first; ++sigma) { + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + if (!H11q1.is_null()) { + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + + // Compute xi and xi' to test the validity of MW + + const Coefficient_ring xi = projS_cod_sigma * H11 ; + const Coefficient_ring xip = H11q1 * projP_d_pi ; + found = (is_invertible(xi) && is_invertible(xip)) ; + if (found) { + Cell_pair p; + p.sigma = pi ; // primary cell + p.tau = sigma ; // secondary cell + p.dim = q ; + pairs.push_back(p) ; + } + } + } + } + } + found = !pairs.empty() ; + return pairs; +} + +// find all the valid Cell_pair containing tau for MW in dimension q +template +std::vector Hdvf::find_pairs_MW(int q, bool &found, size_t tau) const { + found = false; + std::vector pairs; + if (this->_flag.at(q).at(tau) == CRITICAL) + return pairs ; // Empty / found false + + if (this->_hdvf_opt & OPT_FULL) { + // If tau is primary (rename pi), search for a valid sigma + if (this->_flag.at(q).at(tau) == PRIMARY) { + const size_t pi(tau) ; + // Col pi of H_q and proj_P(d(pi)) must at least be non empty + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + + if (H11.is_null() || projP_d_pi.is_null()) + return pairs ; + + // Search for sigma with col_sigma(H_q-1) non empty + for (size_t sigma = 0; sigma < this->_H_col.at(q-1).dimensions().first; ++sigma) { + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + if (!H11q1.is_null()) { + // and proj_S(cod(sigma)) non empty + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + if (!projS_cod_sigma.is_null()) { + // test xi and xip + const Coefficient_ring xi = projS_cod_sigma * H11 ; + const Coefficient_ring xip = H11q1 * projP_d_pi ; + found = (is_invertible(xi) && is_invertible(xip)) ; + if (found) { + Cell_pair p ; + p.sigma = pi ; // primary cell + p.tau = sigma ; // critical cell + p.dim = q ; + pairs.push_back(p) ; + } + } + } + } + } + else { // cell is secondary + // cout << "secondary" << endl ; + const size_t sigma(tau) ; + // Row sigma of H_q-1 and proj_S(cod(sigma)) must at least be non empty + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + if (H11q1.is_null() || projS_cod_sigma.is_null()) + return pairs ; + + // Search for pi with col pi of H_q non empty + for (OSM::Bitboard::iterator it_pi = this->_H_col[q].begin(); it_pi != this->_H_col[q].end(); ++it_pi) { + const size_t pi(*it_pi) ; + + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + // proj_P of d(pi) must also be non empty + if (!projP_d_pi.is_null()) { + // test xi and xip + const Coefficient_ring xi = projS_cod_sigma * H11 ; + const Coefficient_ring xip = H11q1 * projP_d_pi ; + found = (is_invertible(xi) && is_invertible(xip)) ; + if (found) { + Cell_pair p ; + p.sigma = pi ; // primary cell + p.tau = sigma ; // secondary cell + p.dim = q ; + pairs.push_back(p) ; + } + } + } + } + } + found = !pairs.empty() ; + return pairs; +} + + +// Method to perform operation R +// pi is in dimension q, sigma is in dimension q+1 +template +void Hdvf::R(size_t pi, size_t sigma, int q) { + if (this->_hdvf_opt & OPT_FULL) { + CGAL_precondition(this->_K.is_valid_cell(pi, q)); + CGAL_precondition(this->_K.is_valid_cell(sigma, q+1)); + // Output operation details to the console + std::cout << "R of " << pi << "(dim " << q << ") / " << sigma << "(dim " << q + 1 << ")" << std::endl; + + //----------------------------------------------- Submatrices of H ---------------------------------------------------- + // Extract the relevant row and column chains from this->_H_col + Row_chain H12 = OSM::get_row(this->_H_col[q], sigma); // H12 is the row chain from this->_H_col[q] at index sigma + Column_chain H21 = OSM::get_column(this->_H_col[q], pi); // H21 is the column chain from this->_H_col[q] at index pi + + // Get the coefficient at the intersection of H12 and H21 + Coefficient_ring H11(H12.get_coefficient(pi)); // H11 is the coefficient at row sigma and column pi + + // Assert that H11 is invertible + if (! is_invertible(H11)) + throw(std::invalid_argument("Invalid arguments for R - cells do not meet the required condition")); + Coefficient_ring H11_inv = inverse(H11); // Inverse of H11 + + // Remove the contributions of pi from H12 and sigma from H21 + H12 /= std::vector({pi}); // Remove column pi from H12 + H21 /= std::vector({sigma}); // Remove row sigma from H21 + + // Remove the corresponding row and column from this->_H_col + remove_row(this->_H_col[q], sigma); // Remove row sigma from this->_H_col[q] + remove_column(this->_H_col[q], pi); // Remove column pi from this->_H_col[q] + + //---------------------------------------------- Submatrices of F ----------------------------------------------------- + + // Extract the relevant column chain from this->_F_row + Column_chain F11 = OSM::get_column(this->_F_row[q], pi); // F11 is the column chain from this->_F_row[q] at index pi + + // Remove the column pi from this->_F_row + remove_column(this->_F_row[q], pi); + + //--------------------------------------------- Submatrices of G ------------------------------------------------------ + + // Extract the relevant row chain from this->_G_col + Row_chain G11 = OSM::get_row(this->_G_col[q + 1], sigma); // G11 is the row chain from this->_G_col[q+1] at index sigma + + // Remove the row sigma from this->_G_col + remove_row(this->_G_col[q + 1], sigma); + + //--------------------------------------------- Update matrices ------------------------------------------------------- + + // Update this->_F_row[q] + // Subtract the product of (H12 * F11) and H11 from this->_F_row[q] + // Note: % operator returns a row matrix, so be careful with operations + this->_F_row[q] -= (F11 % H12) * H11_inv; + + // Set the row pi of this->_F_row[q] to (H12 * (-H11_inv)) + OSM::set_row(this->_F_row[q], pi, H12 * (-H11_inv)); + + // Update this->_G_col[q + 1] + // Subtract the product of (H21 * G11) and H11_inv from this->_G_col[q + 1] + this->_G_col[q + 1] -= (H21 * G11) * H11_inv; + + // Set the column sigma of this->_G_col[q + 1] to (H21 * (-H11_inv)) + OSM::set_column(this->_G_col[q + 1], sigma, H21 * (-H11_inv)); + + // Update this->_DD_col + // Compute the temporary matrix product F11 * G11 + this->_DD_col[q + 1] += (F11 * G11) * H11_inv; + + // Set the row pi in this->_DD_col[q + 1] to (G11 * H11_inv) + OSM::set_row(this->_DD_col[q + 1], pi, G11 * H11_inv); + + // Set the column sigma in this->_DD_col[q + 1] to (F11 * H11_inv) + OSM::set_column(this->_DD_col[q + 1], sigma, F11 * H11_inv); + + // Set the coefficient at (pi, sigma) in this->_DD_col to H11_inv + OSM::set_coefficient(this->_DD_col[q + 1], pi, sigma, H11_inv); + + // Update this->_H_col[q] + // Subtract the product of (H21 * H12) and H11_inv from this->_H_col[q] + this->_H_col[q] -= (H21 * H12) * H11_inv; + + // Perform additional updates + + // Extract boundary and coboundary chains + Column_chain bnd_pi(this->_K.d(pi, q)); // Boundary of pi in dimension q + Row_chain cobnd_sigma(this->_K.cod(sigma, q + 1)); // Coboundary of sigma in dimension q+1 + + // Project the boundary and coboundary chains onto PRIMARY and SECONDARY flags + Column_chain proj_P_pi(this->projection(bnd_pi, PRIMARY, q)); + Row_chain proj_S_sigma(this->projection(cobnd_sigma, SECONDARY, q + 1)); + + if (q > 0) { + // Update this->_DD_col[q] with projections and this->_F_row[q-1] + Column_chain c1(this->_F_row[q - 1] * proj_P_pi + this->projection(bnd_pi, CRITICAL, q)); + OSM::set_column(this->_DD_col[q], pi, c1); + OSM::set_column(this->_G_col[q], pi, this->_H_col[q - 1] * proj_P_pi); + } + + // Update this->_F_row[q+1] with this->projection and this->_H_col[q+1] + OSM::set_row(this->_F_row[q + 1], sigma, proj_S_sigma * this->_H_col[q + 1]); + + if (q + 2 <= this->_K.dimension()) { + // Update this->_DD_col[q+2] with projections + Row_chain c4(this->projection(cobnd_sigma, CRITICAL, q + 1) + proj_S_sigma * this->_G_col[q + 2]); + OSM::set_row(this->_DD_col[q + 2], sigma, c4); + } + + // Update flags + this->_flag[q][pi] = CRITICAL; // Set the PSC_flag of pi in dimension q to CRITICAL + ++this->_nb_C.at(q) ; + --this->_nb_P.at(q) ; + this->_flag[q + 1][sigma] = CRITICAL; // Set the PSC_flag of sigma in dimension q+1 to CRITICAL + ++this->_nb_C.at(q+1) ; + --this->_nb_S.at(q+1) ; + } + else { + std::cerr << "R operation available only with HDVF_FULL option" << std::endl; + throw(std::runtime_error("R operation available only with HDVF_FULL option")); + } +} + +// Method to perform operation M +// pi is in dimension q, gamma is in dimension q +template +void Hdvf::M(size_t pi, size_t gamma, int q) { + if (this->_hdvf_opt & OPT_FULL) { + CGAL_precondition(this->_K.is_valid_cell(pi, q)); + CGAL_precondition(this->_K.is_valid_cell(gamma, q)); + + // Output the operation details to the console + std::cout << "M of " << q << "(" << pi << "," << gamma << ")" << std::endl; + + if (q == this->_K.dimension()) + throw(std::runtime_error("Operation M invalid in maximum dimension")) ; + + //----------------------------------------------- Submatrices of F ---------------------------------------------------- + // Extract row and column chains from this->_F_row + Row_chain F12(OSM::get_row(this->_F_row[q], gamma)); // F12 is the row chain from this->_F_row[q] at index gamma + Column_chain F21(OSM::get_column(this->_F_row[q], pi)); // F21 is the column chain from this->_F_row[q] at index pi + + // Get the coefficient at the intersection of F12 and F21 + const Coefficient_ring F11(F12.get_coefficient(pi)); // F11 is the coefficient at row gamma and column pi + + // Assert that F11 is invertible + if (! is_invertible(F11)) + throw(std::invalid_argument("Invalid arguments for M - cells do not meet the required condition")); + Coefficient_ring F11_inv = inverse(F11); // Inverse of F11 + + // Remove the contributions of pi from F12 and gamma from F21 + F12 /= std::vector({pi}); // Remove column pi from F12 + F21 /= std::vector({gamma}); // Remove row gamma from F21 + + // Remove the corresponding row and column from this->_F_row + remove_row(this->_F_row[q], gamma); // Remove row gamma from this->_F_row[q] + remove_column(this->_F_row[q], pi); // Remove column pi from this->_F_row[q] + + //--------------------------------------------- Submatrices of G ------------------------------------------------------ + + // Extract the relevant column chain from this->_G_col + remove_column(this->_G_col[q], gamma); // Remove column gamma from this->_G_col[q] + + //---------------------------------------------- Submatrices of H ----------------------------------------------------- + + // Extract the relevant column chain from this->_H_col + Column_chain H11(OSM::get_column(this->_H_col[q], pi)); // H11 is the column chain from this->_H_col[q] at index pi + + // Remove the column pi from this->_H_col + remove_column(this->_H_col[q], pi); + + //--------------------------------------------- Submatrices of DD_q+1 ------------------------------------------------------ + + // For DD_q+1 and DD_q: + // Extract the relevant row chains from this->_DD_col + Row_chain D11(OSM::get_row(this->_DD_col[q+1], gamma)); // D11 is the row chain from this->_DD_col[q+1] at index gamma + remove_row(this->_DD_col[q + 1], gamma); // Remove row gamma from this->_DD_col[q + 1] + + //--------------------------------------------- Submatrices of DD ------------------------------------------------------ + + // DD_q (corresponds to the column matrix of this->_DD_col) + if (q > 0) + remove_column(this->_DD_col[q], gamma); // Remove column gamma from this->_DD_col[q] + + //--------------------------------------------- Update matrices ------------------------------------------------------- + + // Update this->_H_col + // Subtract the product of (H11 * F11_inv) and F12 from this->_H_col[q] + // Note: * operator is used for matrix column results + this->_H_col[q] -= (H11 * F11_inv) * F12; + + // Set the column gamma of this->_H_col[q] to (-1 * H11) * F11_inv + OSM::set_column(this->_H_col[q], gamma, (-1 * H11) * (F11_inv)); + + // Update this->_F_row + // Add the product of (F21 * F11_inv) and F12 to this->_F_row[q] + this->_F_row[q] -= (F21 * F11_inv) * F12; + + // Set the row pi of this->_F_row[q] to F11_inv * F12 + OSM::set_row(this->_F_row[q], pi, F11_inv * F12); + + // Set the column gamma of this->_F_row[q] to F21 * (-F11_inv) + OSM::set_column(this->_F_row[q], gamma, F21 * (-F11_inv)); + + // Set the coefficient at (pi, gamma) in this->_F_row[q] to F11_inv + set_coefficient(this->_F_row[q], pi, gamma, F11_inv); + + // Update this->_G_col + // Add the product of (H11 * F11_inv) and D11 to this->_G_col[q+1] + this->_G_col[q + 1] += (H11 * F11_inv) * D11; + + // Update this->_DD_col[q + 1] + this->_DD_col[q + 1] -= (F21 * F11_inv) * D11; // Subtract the product from this->_DD_col[q + 1] + OSM::set_row(this->_DD_col[q + 1], pi, D11 * F11_inv); // Set the row pi in this->_DD_col[q + 1] + + // Update this->_G_col (for dimension q) and this->_DD_col (for dimension q) + if (q>0) { + // Extract boundary chain and project it + Column_chain c = this->_K.d(pi, q); // Boundary of pi in dimension q + Column_chain projection_p(this->projection(c, PRIMARY, q-1)); // Project boundary chain to PRIMARY + Column_chain projection_c = this->projection(c, CRITICAL, q-1); // Project boundary chain to CRITICAL + + // Set the column pi of this->_G_col[q] to (-1 * this->_H_col[q-1]) * projection_p + OSM::set_column(this->_G_col[q], pi, (Coefficient_ring(-1) * this->_H_col[q - 1]) * projection_p); + + // Update this->_DD_col + // Extract projections and perform updates + Column_chain tmp(this->_F_row[q - 1] * projection_p + projection_c); // Compute the product of this->_F_row[q - 1] and projection_p + // Set the column pi of this->_DD_col[q] to cc + OSM::set_column(this->_DD_col[q], pi, tmp); + } + // else: if the dimension is 0, no change for this->_G_col[q] and this->_DD_col[q] + + // Update flags + this->_flag[q][pi] = CRITICAL; // Set the PSC_flag of pi in dimension q to PRIMARY + this->_flag[q][gamma] = PRIMARY; // Set the PSC_flag of gamma in dimension q to CRITICAL + } + else { + std::cerr << "M operation available only with HDVF_FULL option" << std::endl; + throw(std::runtime_error("M operation available only with HDVF_FULL option")); + } +} + +// Method to perform operation W +// gamma is in dimension q, sigma is in dimension q +template +void Hdvf::W(size_t sigma, size_t gamma, int q) { + if (this->_hdvf_opt & OPT_FULL) { + CGAL_precondition(this->_K.is_valid_cell(sigma, q)); + CGAL_precondition(this->_K.is_valid_cell(gamma, q)); + + // Output the operation details to the console + std::cout << "W of " << q << "(" << sigma << "," << gamma << ")" << std::endl; + + if (q == 0) + throw(std::runtime_error("W operation in dimension 0")) ; + + //----------------------------------------------- Submatrices of G ---------------------------------------------------- + // Extract row and column chains from this->_G_col + Row_chain G12(OSM::get_row(this->_G_col[q], sigma)); // G12 is the row chain from this->_G_col[q] at index sigma + Column_chain G21(OSM::get_column(this->_G_col[q], gamma)); // G21 is the column chain from this->_G_col[q] at index gamma + + // Get the coefficient at the intersection of G12 and G21 + Coefficient_ring G11(G12.get_coefficient(gamma)); // G11 is the coefficient at row sigma and column gamma + + // Assert that G11 is invertible + if (! is_invertible(G11)) + throw(std::invalid_argument("Invalid arguments for W - cells do not meet the required condition")); + Coefficient_ring G11_inv = inverse(G11); // Inverse of G11 + + // Remove the contributions of gamma from G12 and sigma from G21 + G12 /= std::vector({gamma}); // Remove column gamma from G12 + G21 /= std::vector({sigma}); // Remove row sigma from G21 + + // Remove the corresponding row and column from this->_G_col + remove_row(this->_G_col[q], sigma); // Remove row sigma from this->_G_col[q] + remove_column(this->_G_col[q], gamma); // Remove column gamma from this->_G_col[q] + + //---------------------------------------------- Submatrices of F ----------------------------------------------------- + + // Extract the row chain from this->_F_row + + // Remove the row gamma from this->_F_row + remove_row(this->_F_row[q], gamma); + + //--------------------------------------------- Submatrices of H ------------------------------------------------------ + + // Extract the row chain from this->_H_col + Row_chain H11(OSM::get_row(this->_H_col[q-1], sigma)); // H11 is the row chain from this->_H_col[q] at index sigma + + // Remove the row sigma from this->_H_col + remove_row(this->_H_col[q-1], sigma); + + //--------------------------------------------- Submatrices of DD_q+1 ------------------------------------------------------ + + // Extract the row chain from this->_DD_col[q+1] + + // Remove the row gamma from this->_DD_col + if (q < this->_K.dimension()) + remove_row(this->_DD_col[q + 1], gamma); + + //--------------------------------------------- Submatrices of DD_q ------------------------------------------------------ + + // Extract the column chain from this->_DD_col[q] + Column_chain D11_q(OSM::get_column(this->_DD_col[q], gamma)); // D11_q is the column chain from this->_DD_col[q] at index gamma + + // Remove the column gamma from this->_DD_col + remove_column(this->_DD_col[q], gamma); + + //--------------------------------------------- Update matrices ------------------------------------------------------- + + // Update this->_H_col + // Subtract the product of (G21 * G11_inv) and H11 from this->_H_col[q] + // Note: % operator is used for matrix row results + this->_H_col[q-1] -= (G21 * G11_inv) * H11; + + // Set the row gamma of this->_H_col[q] to (-1 * H11) * G11_inv + OSM::set_row(this->_H_col[q-1], gamma, (-1 * H11) * (G11_inv)); + + // Update this->_G_col + // Subtract the product of G11_inv and (G21 * G12) from this->_G_col[q] + this->_G_col[q] -= (G21 * G11_inv) * G12; + + // Set the row gamma of this->_G_col[q] to G11_inv * G12 + OSM::set_row(this->_G_col[q], gamma, -G11_inv * G12); + + // Set the column sigma of this->_G_col[q] to G11_inv * G21 + OSM::set_column(this->_G_col[q], sigma, G21 * G11_inv); + + // Set the coefficient at (gamma, sigma) in this->_G_col[q] to G11_inv + set_coefficient(this->_G_col[q], gamma, sigma, G11_inv); + + // Update this->_F_row (for dimension q-1) + // Add the product of (D11_q * G11_inv) and H11 to this->_F_row[q - 1] + this->_F_row[q - 1] += (D11_q * G11_inv) * H11; + + // Update this->_DD_col (for dimension q) + this->_DD_col[q] -= (D11_q * G11_inv) * G12;// Subtract the product of (D11_q * G11_inv) and G12 + OSM::set_column(this->_DD_col[q], sigma, D11_q * G11_inv) ; + + // Update this->_F_row (for dimension q) and this->_DD_col (for dimension q+1) + if (q < this->_K.dimension()) { + // Extract boundary chain and project it + Row_chain c = this->_K.cod(sigma, q); // Boundary of sigma in dimension q + Row_chain projection_s(this->projection(c, SECONDARY, q+1)); // Project boundary chain to SECONDARY + Row_chain projection_c(this->projection(c, CRITICAL, q+1)); // Project boundary chain to SECONDARY + + // Set the row sigma of this->_F_row[q] to (-1 * projection_s * this->_H_col[q]) + OSM::set_row(this->_F_row[q], sigma, (-1 * projection_s) * this->_H_col[q]); + + // Set the row sigma of this->_DD_col[q + 1] to projection_s * this->_G_col[q + 1] + projection_c + + Row_chain tmp(projection_s * this->_G_col[q + 1] + projection_c) ; + OSM::set_row(this->_DD_col[q + 1], sigma, tmp); + } + // else : if the dimension is maximal, no update of this->_DD_col[q+1] and this->_F_row[q] + + // Update flags + this->_flag[q][gamma] = SECONDARY; // Set the PSC_flag of gamma in dimension q to SECONDARY + this->_flag[q][sigma] = CRITICAL; // Set the PSC_flag of sigma in dimension q to CRITICAL + } + else { + std::cerr << "W operation available only with HDVF_FULL option" << std::endl; + throw(std::runtime_error("W operation available only with HDVF_FULL option")); + } +} + +// Method to perform operation MW +// gamma is in dimension q, sigma is in dimension q +template +void Hdvf::MW(size_t pi, size_t sigma, int q) { + if (this->_hdvf_opt & OPT_FULL) { + CGAL_precondition(this->_K.is_valid_cell(pi, q)); + CGAL_precondition(this->_K.is_valid_cell(sigma, q)); + // Output the operation details to the console + std::cout << "MW of " << q << "(" << pi << "," << sigma << ")" << std::endl; + + if (q <= 0) + throw(std::runtime_error("MW operation in dimension 0")) ; + if (q >= this->_K.dimension()) + throw(std::runtime_error("MW operation in maximal dimension")) ; + + //----------------------------------------------- Submatrices ---------------------------------------------------- + // In order to compute xi and xi', extract sub-matrices of H_q, H_q-1 and compute d(pi) and cod(sigma) + + // H_q extractions + + Column_chain H11(OSM::get_column(this->_H_col.at(q), pi)) ; + // H21 -> delete H11 + this->_H_col.at(q) /= std::vector({pi}) ; + + // H_q-1 extractions + + Row_chain H11q1(OSM::get_row(this->_H_col.at(q-1), sigma)) ; + // H21_q-1 -> delete H11q1 + remove_row(this->_H_col.at(q-1), sigma) ; + + // d(pi) + + Column_chain d_pi = this->_K.d(pi, q) ; + Column_chain projP_d_pi = this->projection(d_pi, PRIMARY, q-1) ; + Column_chain projC_d_pi = this->projection(d_pi, CRITICAL, q-1) ; + + // cod(sigma) + + Row_chain cod_sigma = this->_K.cod(sigma, q) ; + Row_chain projS_cod_sigma = this->projection(cod_sigma, SECONDARY, q+1) ; + Row_chain projC_cod_sigma = this->projection(cod_sigma, CRITICAL, q+1) ; + + // Compute xi and xi' to test the validity of MW + + Coefficient_ring xi = projS_cod_sigma * H11 ; + Coefficient_ring xip = H11q1 * projP_d_pi ; + + if (! is_invertible(xi)) + throw "MW impossible, xi non invertible" ; + if (! is_invertible(xip)) + throw "MW impossible, xi' non invertible" ; + + Coefficient_ring xi_inv = inverse(xi), xip_inv = inverse(xip); + + // F_q extraction + + Column_chain F11(OSM::get_column(this->_F_row.at(q), pi)) ; + // F12 -> delete col F11 + remove_column(this->_F_row.at(q), pi) ; + + // G_q extractions + + Row_chain G11(OSM::get_row(this->_G_col.at(q), sigma)) ; + // G21 -> dele row G11 + remove_row(this->_G_col.at(q), sigma) ; + + // ----------- Update of the reduction + + // H_q + + Row_chain tmp1 = projS_cod_sigma * this->_H_col.at(q) ; + + this->_H_col.at(q) -= (H11 * xi_inv) * tmp1 ; + OSM::set_column(this->_H_col.at(q), sigma, H11 * xi_inv) ; + + // F_q + + this->_F_row.at(q) -= (F11 * xi_inv) * tmp1 ; + OSM::set_column(this->_F_row.at(q), sigma, F11 * xi_inv) ; + + // G_q+1 // note: G_q+1 is not be modified if the Hdvf is perfect + + Row_chain tmp2(projS_cod_sigma * this->_G_col.at(q+1)) ; + tmp2 += projC_cod_sigma ; + this->_G_col.at(q+1) -= (H11 * xi_inv) * tmp2 ; + + // DD_col_q+1 / DD_row_q + + this->_DD_col.at(q+1) -= (F11 * xi_inv) * tmp2 ; + + // H_q-1 + + Column_chain tmp3(this->_H_col.at(q-1) * projP_d_pi) ; + + this->_H_col.at(q-1) -= (tmp3 * xip_inv) * H11q1 ; + OSM::set_row(this->_H_col.at(q-1), pi, xip_inv * H11q1) ; + + // G_q + + this->_G_col.at(q) -= (tmp3 * xip_inv) * G11 ; + OSM::set_row(this->_G_col.at(q), pi, xip_inv * G11) ; + + // F_q-1 // note: F_q-1 is not be modified if the Hdvf is perfect + + Column_chain tmp4(this->_F_row.at(q-1) * projP_d_pi) ; + tmp4 += projC_d_pi ; + this->_F_row.at(q-1) -= (tmp4 * xip_inv) * H11q1 ; + + // DD_col_q + + this->_DD_col.at(q) += (tmp4 * xip_inv) * G11 ; + + // Update flags + this->_flag[q][pi] = SECONDARY; // Set the PSC_flag of gamma in dimension q to SECONDARY + this->_flag[q][sigma] = PRIMARY; // Set the PSC_flag of sigma in dimension q to PRIMARY + } + else { + std::cerr << "W operation available only with HDVF_FULL option" << std::endl; + throw(std::runtime_error("W operation available only with HDVF_FULL option")); + } +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_HDVF_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_core.h b/HDVF/include/CGAL/HDVF/Hdvf_core.h new file mode 100644 index 000000000000..85197c6a92aa --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_core.h @@ -0,0 +1,1862 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_HDVF_CORE_H +#define CGAL_HDVF_HDVF_CORE_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/* \brief HDVF Enum for the label of cells. */ +enum PSC_flag { + PRIMARY, + SECONDARY, + CRITICAL, + NONE // For duality and persistence +}; + +/* \brief HDVF option (compute only reduced boundary). */ +const int OPT_BND = 0b0001; +/* \brief HDVF option (compute only reduced boundary and f). */ +const int OPT_F = 0b0010; +/* \brief HDVF option (compute only reduced boundary and g). */ +const int OPT_G = 0b0100; +/* \brief HDVF option (compute full reduction). */ +const int OPT_FULL = 0b1000; + + +/* \brief Structure to represent data for HDVF operations (pairs of cells). + * + * Cells are always sorted so that the dimension of `sigma` is lesser than the dimension of `tau`. + */ +struct Cell_pair { + // Index of the first cell + size_t sigma; + // Index of the second cell + size_t tau; + // Dimension of cells: `dim`/`dim`+1 for A and R, `dim`/`dim` for other operations + int dim; + + // Comparison operator + inline bool operator==(const Cell_pair& pair2) const { + return ((sigma==pair2.sigma) && (tau==pair2.tau) && (dim==pair2.dim)); + } +}; + + +/* \brief Overload of operator<< for Cell_pair type. */ +inline std::ostream& operator<<(std::ostream &out, const std::vector& pairs) { + for (const auto& pair : pairs) { + out << "Sigma: " << pair.sigma << ", Tau: " << pair.tau << ", Dim: " << pair.dim << std::endl; + } + return out ; +} + +/*! + \ingroup PkgHDVFHDVFClasses + + The class `Hdvf_core` is the core implementation of homological discrete vector fields (HDVF for short). The ring of coefficients for homology computation must be a model of `IntegralDomainWithoutDivision`. + + An enumeration `PSC_flag` is defined in the `Homological_discrete_vector_field` namespace and the `Hdvf_core` class maps each cell to one of the flags (namely `PRIMARY`, `SECONDARY`, `CRITICAL`). The NONE `PSC_flag` is used in child classes (such as `Hdvf_duality`) when computing relative homology on a sub-complex. + The `PSC_flag` of each cell is stored in an appropriate structure and getters are provided to access to this information. + + The `Hdvf_core` class stores the associated reduction in sparse matrices: row-major for \f$f\f$, and column-major for \f$g\f$, \f$h\f$ and \f$\partial'\f$. Getters are provided to access this information. However, according to the chosen HDVF computation option (`OPT_BND`, `OPT_F`, `OPT_G`, `OPT_FULL`) the reduction can be computed only partially (and thus faster). + + The class provides perfect HDVF construction operations: `compute_perfect_hdvf()` and `compute_rand_perfect_hdvf()`, which build perfect HDVFs by pairing iteratively critical cells through the `A()` operation. + + If the user wishes to build an HDVF using other criteria, several `find_pair_A()` functions are provided (searching for valid pairs of cells for `A`respecting various constraints). The `A` operation can be applied to any pair returned by these functions. + + Homology/cohomology generators are actually algebraic objects, namely chains. Methods `homology_chain()` and `cohomology_chain()` return the homology and cohomology generator chain associated to a given critical cell. VTK export functions output all the cells of such chains with non zero coefficients. + + + \cgalModels{HDVF} + + \tparam ChainComplex a model of the `AbstractChainComplex` concept, providing the type of abstract chain complex used. + */ + + +template +class Hdvf_core { +public: + /*! \brief Type of the underlying chain complex. */ + typedef ChainComplex Chain_complex; + + /*! \brief Type of coefficients used to compute homology. */ + typedef typename ChainComplex::Coefficient_ring Coefficient_ring; + + /*! \brief Type of sparse matrix structure used to compute homology. */ + typedef typename ChainComplex::Sparse_matrix_structure Sparse_matrix_structure; + +/** \brief Template type of underlying sparse chains. */ + template + using Sparse_chain_base = typename Sparse_matrix_structure::template Sparse_chain_type; + + /** \brief Template type of underlying sparse matrices. */ + template + using Sparse_matrix_base = typename Sparse_matrix_structure::template Sparse_matrix_type; + + /*! + Type of column-major chains + */ + typedef Sparse_chain_base Column_chain; + + /*! + Type of row-major chains + */ + typedef Sparse_chain_base Row_chain; + + /*! + Type of column-major sparse matrices + */ + typedef Sparse_matrix_base Column_matrix; + + /*! + Type of row-major sparse matrices + */ + typedef Sparse_matrix_base Row_matrix; + +protected: + /* \brief Flags of the cells. + * _flag.at(q) contains the flags of cells of dimension q + */ + std::vector> _flag; + /* \brief Number of `PRIMARY` cells. */ + std::vector _nb_P; + /* \brief Number of `SECONDARY` cells. */ + std::vector _nb_S; + /* \brief Number of `CRITICAL` cells. */ + std::vector _nb_C; + /* \brief Row matrices for f. */ + std::vector _F_row; + /* \brief Column matrices for g. */ + std::vector _G_col; + /* \brief Column matrices for h. */ + std::vector _H_col; + /* \brief Column matrices for reduced boundary. */ + std::vector _DD_col; + + /* \brief Reference to the underlying complex. */ + const ChainComplex& _K; + + /* \brief Hdvf_core options for computation (computation of partial reduction). */ + int _hdvf_opt; + + /* \brief Restriction of HDVF computation to a single dimension. */ + int _dimension_restriction; + int _min_dimension; + int _max_dimension; + +public: + /* \brief Returns the dimension of Hdvf computation. + * + * If HDVF computation is full, returns -1; otherwise, returns the dimension along which the HDVF is computed. + */ + inline int dimension_restriction() const { return _dimension_restriction; } + + /* \brief Changes the dimension of Hdvf computation. + * + * Set -1 for full computation, and a positive value lower than the complex dimension for a computation restricted to dimension `dimension`. + * + * \param dimension Dimension along which the computation is restricted (-1 for full computation). + */ + inline void dimension_restriction(int dimension) { + if ((dimension>=-1) && (dimension <= _K.dimension())) { + // Update dimension restriction + _dimension_restriction = dimension; + // Update _min_dimension and _max_dimension accordingly + if (_dimension_restriction == -1) { + _min_dimension = 0; + _max_dimension = _K.dimension(); + } + else + _min_dimension = _max_dimension = _dimension_restriction; + // Get necessary boundary matrices + int _min_dim, _max_dim; + if (_dimension_restriction==-1) { + _min_dim = 0; + _max_dim = _K.dimension(); + } + else { + if (_min_dimension > 0) + _min_dim = _min_dimension-1; + else + _min_dim = 0; + + if (_max_dimension < _K.dimension()) + _max_dim = _max_dimension+1; + else + _max_dim = _K.dimension(); + }; + + for (int q = _min_dim; q <= _max_dim; ++q) { + if (_DD_col.at(q).is_empty()) + _DD_col.at(q) = _K.boundary_matrix(q); + } + } + else { + std::cerr << "dimension_restriction () error, provided incoherent argument " << dimension << std::endl; + } + } + +public: + /** + * \brief Constructor from a chain complex. + * + * Builds an "empty" HDVF_core associated to K (with all cells critical). By default, the HDVF option is set to OPT_FULL (full reduction computed). + * + * \param K A chain complex (a model of `AbstractChainComplex`) + * \param hdvf_opt Option for HDVF computation (`OPT_BND`, `OPT_F`, `OPT_G` or `OPT_FULL`) + * \param dimension_restriction Determines if perfect HDVFs are computed along any dimensions (if `dimension_restriction` is -1) or a single dimension (specified by `dimension_restrictions`) + * + * \exception Empty_complex If the complex `K` is empty, raises a `%std::runtime_error`. + */ + Hdvf_core(const ChainComplex& K, int hdvf_opt = OPT_FULL, int dimension_restriction = -1) ; + + /** + * \brief Copy constructor. + */ + Hdvf_core(const Hdvf_core& hdvf) : _flag(hdvf._flag), _nb_P(hdvf._nb_P), _nb_S(hdvf._nb_S), _nb_C(hdvf._nb_C), _F_row(hdvf._F_row), _G_col(hdvf._G_col), _H_col(hdvf._H_col), _DD_col(hdvf._DD_col), _K(hdvf._K), _hdvf_opt(hdvf._hdvf_opt), _dimension_restriction(hdvf._dimension_restriction), _min_dimension(hdvf._min_dimension), _max_dimension(hdvf._max_dimension) { } + + /** + * \brief Affectation operator. + * + * \exception Invalid_underlying_complex If `hdvf` does not share the same underlying complex, raises a `%std::runtime_error`. + */ + Hdvf_core& operator=(const Hdvf_core& hdvf) { + if (_K.id() != hdvf._K.id()) { + throw std::runtime_error("Affectation of Hdvf_core only over the same complex"); + } + _flag = hdvf._flag; + _nb_P = hdvf._nb_P; + _nb_S = hdvf._nb_S; + _nb_C = hdvf._nb_C; + _F_row = hdvf._F_row; + _G_col = hdvf._G_col; + _H_col = hdvf._H_col; + _DD_col = hdvf._DD_col; + _hdvf_opt = hdvf._hdvf_opt; + _dimension_restriction = hdvf._dimension_restriction; + _min_dimension = hdvf._min_dimension; + _max_dimension = hdvf._max_dimension; + return *this; + } + + /** \brief Constructor from the PRIMARY/SECONDARY/CRITICAL labels. + * + * If `with_build_reduction` is `false` check the combinatorial coherence of labels. If `with_build_reduction` is `true` checks that labels describe a valid HDVF (ie. \f$\partial(S)\_P\f$ is invertible). + * + * \param K A chain complex (a model of `AbstractChainComplex`) + * \param flags Vector of PSC labels along each dimensions. + * \param with_build_reduction If `false`, loads labels without building the reduction, if `true` builds the reduction (\f$\mathscr O(n^3)\f$). + * \param hdvf_opt Option for HDVF computation (`OPT_BND`, `OPT_F`, `OPT_G` or `OPT_FULL`) + * \param dimension_restriction Determines if perfect HDVFs are computed along any dimensions (if `dimension_restriction` is -1) or a single dimension (specified by `dimension_restrictions`) + * + * \exception If the flags provided are incoherent (or do not define an HDVF), raises a `%std::invalid_argument`. + */ + Hdvf_core(const ChainComplex& K, const std::vector >& flags, bool with_build_reduction = false, int hdvf_opt = OPT_FULL, int dimension_restriction = -1) : _flag(flags), _K(K), _hdvf_opt(hdvf_opt), _dimension_restriction(dimension_restriction) { + _nb_P.resize(_K.dimension()+1); + _nb_S.resize(_K.dimension()+1); + _nb_C.resize(_K.dimension()+1); + // Compute the number of cells of each flag in any dimension + for (int q=0; q<=_K.dimension(); ++q) { + for (PSC_flag flag : _flag.at(q)) { + if (flag == PRIMARY) ++_nb_P.at(q); + else if (flag == SECONDARY) ++_nb_S.at(q); + else ++_nb_C.at(q); + } + } + // Check the combinatorial coherence of flags + if (!combinatorially_coherent()) { + std::cerr << "flags provided are not combinatorially coherent" << std::endl; + throw std::invalid_argument("flags provided are not combinatorially coherent"); + } + + // Build the reduction + if (with_build_reduction) { + build_reduction(); + } + } + + /* + * \brief Destructor. */ + ~Hdvf_core() { } + +protected: + template + struct Sub_matrix_data { + MatrixType matrix; + std::vector basis_rows, basis_cols; + std::unordered_map map_rows, map_cols; + void print() { + std::cout << "rows basis: " << std::endl; + for (size_t i=0; i " << basis_rows.at(i) << " -> " << map_rows.at(basis_rows.at(i)) << std::endl; + std::cout << "cols basis: " << std::endl; + for (size_t i=0; i " << basis_cols.at(i) << " -> " << map_cols.at(basis_cols.at(i)) << std::endl; + } + }; + + // Extract M(flag_cols)|flag_rows (dimension q_row, q_col for rows and cols respectively) + // Row version + Sub_matrix_data > extract_sub(const Sparse_matrix_base& M, int q_rows, int q_cols, PSC_flag flag_rows, PSC_flag flag_cols) { + using Matrix_type = Sparse_matrix_base; + using Chain_type = Sparse_chain_base; + using Sub_data_type = Sub_matrix_data; + // Create the sub matrix + Matrix_type res(number_of_cells_by_flag(flag_rows, q_rows), number_of_cells_by_flag(flag_cols, q_cols)); + Sub_data_type sub; + sub.basis_rows.resize(number_of_cells_by_flag(flag_rows, q_rows)); + sub.basis_cols.resize(number_of_cells_by_flag(flag_cols, q_cols)); + sub.matrix = Matrix_type(number_of_cells_by_flag(flag_rows, q_rows), number_of_cells_by_flag(flag_cols, q_cols)); + // Extract the bases + size_t i(0), j(0); + for (size_t k=0; k<_flag.at(q_rows).size(); ++k) { + if (_flag.at(q_rows).at(k) == flag_rows) { + sub.basis_rows.at(i) = k; + sub.map_rows[k]=i++; + } + } + assert(i == number_of_cells_by_flag(flag_rows, q_rows)); + for (size_t k=0; k<_flag.at(q_cols).size(); ++k) { + if (_flag.at(q_cols).at(k) == flag_cols) { + sub.basis_cols.at(j) = k; + sub.map_cols[k]=j++; + } + } + assert(j == number_of_cells_by_flag(flag_cols, q_cols)); + // extract sub matrix + for (typename OSM::Bitboard::iterator it = M.begin(); it != M.end(); ++it) { + if (_flag.at(q_rows).at(*it) == flag_rows) { // the row has the right flag + const size_t i(sub.map_rows(*it)); + // Add the sub_row (with flag flag_cols) to the sub matrix at row i + const Chain_type& row(OSM::cget_row(M, *it)); + for (typename Chain_type::const_iterator it_row = row.cbegin(); it_row != row.end(); ++it_row) { + const size_t k(it_row->first); + if (_flag.at(q_cols).at(k) == flag_cols) { // extract the element + const size_t j(sub.map_cols.at(k)); + OSM::set_coefficient(sub.matrix, i, j, it_row->second); + } + } + } + } + return sub; + } + // Column version + Sub_matrix_data > extract_sub(const Sparse_matrix_base& M, int q_rows, int q_cols, PSC_flag flag_rows, PSC_flag flag_cols) { + using Matrix_type = Sparse_matrix_base; + using Chain_type = Sparse_chain_base; + using Sub_data_type = Sub_matrix_data; + // Create the sub matrix + Matrix_type res(number_of_cells_by_flag(flag_rows, q_rows), number_of_cells_by_flag(flag_cols, q_cols)); + Sub_data_type sub; + sub.basis_rows.resize(number_of_cells_by_flag(flag_rows, q_rows)); + sub.basis_cols.resize(number_of_cells_by_flag(flag_cols, q_cols)); + sub.matrix = Matrix_type(number_of_cells_by_flag(flag_rows, q_rows), number_of_cells_by_flag(flag_cols, q_cols)); + // Extract the bases + size_t i(0), j(0); + for (size_t k=0; k<_flag.at(q_rows).size(); ++k) { + if (_flag.at(q_rows).at(k) == flag_rows) { + sub.basis_rows.at(i) = k; + sub.map_rows[k]=i++; + } + } + assert(i == number_of_cells_by_flag(flag_rows, q_rows)); + for (size_t k=0; k<_flag.at(q_cols).size(); ++k) { + if (_flag.at(q_cols).at(k) == flag_cols) { + sub.basis_cols.at(j) = k; + sub.map_cols[k]=j++; + } + } + assert(j == number_of_cells_by_flag(flag_cols, q_cols)); + // extract sub matrix + for (typename OSM::Bitboard::iterator it = M.begin(); it != M.end(); ++it) { + if (_flag.at(q_cols).at(*it) == flag_cols) { // the column has the right flag + const size_t j(sub.map_cols.at(*it)); + // Add the sub_cols (with flag flag_rows) to the sub matrix at column j + const Chain_type& col(OSM::cget_column(M, *it)); + for (typename Chain_type::const_iterator it_col = col.cbegin(); it_col != col.end(); ++it_col) { + const size_t k(it_col->first); + if (_flag.at(q_rows).at(k) == flag_rows) { // extract the element + const size_t i(sub.map_rows.at(k)); + OSM::set_coefficient(sub.matrix, i, j, it_col->second); + } + } + } + } + return sub; + } + + // Fill a submatrix of M + template + void fill_sub(MatrixType& M, const Sub_matrix_data& sub) { + // visit the sub matrix and copy corresponding coefficients (to the right indices) + // set all coefficients (to empty required coefficients) + for (size_t i=0; i + void restrict_matrix(MatrixType& M, int q_rows, int q_cols, PSC_flag flag_rows, PSC_flag flag_cols) { + using Matrix_type = MatrixType; + using Chain_type = typename Matrix_type::Matrix_chain; + PSC_flag flag1(flag_cols), flag2(flag_rows); + int q1(q_cols), q2(q_rows); + if (M.storage_format() == OSM::ROW) { + flag1 = flag_rows; + flag2 = flag_cols; + q1 = q_rows; + q2 = q_cols; + } + + // Visit first dimension, remove chains with the wrong flag and visit others + for(typename OSM::Bitboard::iterator it1 = M.begin(); it1 != M.end(); ++it1) { + if (_flag.at(q1).at(*it1) == flag1) { // Visit + const Chain_type chain(M.chain(*it1)); + for(typename Chain_type::const_iterator it2 = chain.cbegin(); it2 != chain.cend(); ++it2) { + if (_flag.at(q2).at(it2->first) != flag2) { // Delete coefficient + if (M.storage_format() == OSM::COLUMN) + OSM::remove_coefficient(M, it2->first, *it1); + else + OSM::remove_coefficient(M, *it1, it2->first); + } + } + } + else { // Delete chain + if (M.storage_format() == OSM::COLUMN) + OSM::remove_column(M, *it1); + else + OSM::remove_row(M, *it1); + } + } + } + + + void build_reduction() { + // Clean the reduction if exists + init_internals(); + // Compute H_q + for (int q=1; q<=_K.dimension(); ++q) { + Sub_matrix_data sub(extract_sub(_K.boundary_matrix(q), q-1, q, PRIMARY, SECONDARY)); + OSM::Full_lu lu(sub.matrix); + lu.compute(); + Sub_matrix_data sub2; + sub2.matrix = lu.inverse(); + sub2.basis_rows = sub.basis_cols; + sub2.basis_cols = sub.basis_rows; + sub2.map_rows = sub.map_cols; + sub2.map_cols = sub.map_rows; + fill_sub(_H_col.at(q-1), sub2); + } + // Compute F_q + for (int q=0; q<_K.dimension(); ++q) { + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + Column_matrix dSC(_K.boundary_matrix(q+1)); + restrict_matrix(dSC, q, q+1, CRITICAL, SECONDARY); + _F_row.at(q) = -dSC*_H_col.at(q); + } + } + // Compute G_q, DD_q + for (int q=1; q<=_K.dimension(); ++q) { + Column_matrix dCP(_K.boundary_matrix(q)); + restrict_matrix(dCP, q-1, q, PRIMARY, CRITICAL); + if (_hdvf_opt & (OPT_FULL | OPT_G)) + _G_col.at(q) = -_H_col.at(q-1)*dCP; + + Column_matrix dCC(_K.boundary_matrix(q)); + restrict_matrix(dCC, q-1, q, CRITICAL, CRITICAL); + _DD_col.at(q) = dCC + _F_row.at(q-1)*dCP; + } + + } + +protected: + /* \brief Check the "combinatorial" coherence of a HDVF \f$X(P,S,C)\f$. + * + * Checks if for any dimension, \f$|S_q| = |P_{q-1}|\f$. + */ + bool combinatorially_coherent() { + bool res((_nb_S.at(0) == 0) && (_nb_P.at(_K.dimension()) == 0)); + int q=1; + while (res && (q<=_K.dimension())) { + if (_nb_S.at(q) != _nb_P.at(q-1)) + res = false; + ++q; + } + return res; + } + +public: + /* \brief Checks if the pair of cells \f$(\gamma, \gamma')\f$, of dimensions q / q+1, is valid for A. + * + * The pair is valid if \f$\langle \mathrm d(\gamma),\gamma' \rangle\f$ is invertible. + * + * \param gamma Index of the first cell (dimension `q`). + * \param gamma_prime Index of the second cell (dimension `q+1`). + * \param q Lower dimension of cells. + */ + + bool is_valid_pair_for_A(size_t gamma, size_t gamma_prime, int q) { + Coefficient_ring coef(OSM::get_coefficient(_DD_col.at(q+1),gamma, gamma_prime)); + return CGAL::is_invertible(coef); + } + + /* + * \brief Finds a valid Cell_pair of dimension q / q+1 for A. + * + * The function searches a pair of critical cells \f$(\gamma_1, \gamma2)\f$ of dimension q / q+1, valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false`. + * + * \param q Lower dimension of the pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual Cell_pair find_pair_A(int q, bool &found) const; + + /* + * \brief Finds a valid Cell_pair for A containing `gamma` (a cell of dimension `q`) + * + * The function searches a cell \f$\gamma'\f$ such that one of the following conditions holds: + * - \f$\gamma'\f$ has dimension q+1 and \f$(\gamma, \gamma')\f$ is valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma'), \gamma \rangle\f$ invertible), + * - \f$\gamma'\f$ has dimension q-1 and \f$(\gamma', \gamma)\f$ is valid for A (ie.\ such that \f$\langle \partial_{q}(\gamma), \gamma' \rangle\f$ invertible). + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false`. If the cell index is incorrect (larger than the number of cells in the dimension), `found` is set to `false`. + * + * \param q Dimension of the cell `gamma`. + * \param gamma Index of a cell to pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual Cell_pair find_pair_A(int q, size_t gamma, bool &found) const; + + /* + * \brief Finds *all* valid Cell_pair of dimension q / q+1 for A. + * + * The function searches all pairs of critical cells \f$(\gamma_1, \gamma2)\f$ of dimension q / q+1, valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false` and the function returns an empty vector. + * + * \param q Lower dimension of the pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual std::vector find_pairs_A(int q, bool &found) const; + + /* + * \brief Finds *all* valid Cell_pair for A containing `gamma` (a cell of dimension `q`) + * + * The function searches all `CRITICAL` cells \f$\gamma'\f$ such that one of the following conditions holds: + * - \f$\gamma'\f$ has dimension q+1 and \f$(\gamma, \gamma')\f$ is valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma'), \gamma \rangle\f$ invertible), + * - \f$\gamma'\f$ has dimension q-1 and \f$(\gamma', \gamma)\f$ is valid for A (ie.\ such that \f$\langle \partial_{q}(\gamma), \gamma' \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * If the dimension is incorrect (negative or larger than the dimension of the underlying complex), `found` is set to `false`. If the cell index is incorrect (larger than the number of cells in the dimension), `found` is set to `false`. In both cases, the function returns an empty vector. + * + * \param q Dimension of the cell `gamma`. + * \param gamma Index of a cell to pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + virtual std::vector find_pairs_A(int q, size_t gamma, bool &found) const; + + /* + * \brief A operation: pairs critical cells. + * + * A pair of critical cells \f$(\gamma_1, \gamma_2)\f$ of respective dimension q and q+1 is valid for A if \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ is invertible. After the `A()` operation, \f$\gamma_1\f$ becomes `PRIMARY`, \f$\gamma_2\f$ becomes `SECONDARY`. The A method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param gamma1 First cell of the pair (dimension `q`) + * \param gamma2 Second cell of the pair (dimension `q+1`) + * \param q Dimension of the pair + */ + void A(size_t gamma1, size_t gamma2, int q); + + /* + * \brief A operation (pairs critical cells). + * + * A pair of critical cells \f$(\gamma_1, \gamma_2)\f$ of respective dimension q and q+1 is valid for A if \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ is invertible. After the `A()` operation, \f$\gamma_1\f$ becomes `PRIMARY`, \f$\gamma_2\f$ becomes `SECONDARY`. The A method updates the reduction accordingly (in time \f$\mathcal O(n^2)\f$). + * + * \param p Argument `Cell_pair` containing \f$\gamma_1\f$, \f$\gamma_2\f$ and the dimension of the pair. + */ + void A(const Cell_pair& p) { A(p.sigma, p.tau, p.dim); } + + /* + * \brief Computes a perfect HDVF. + * + * As long as valid pairs for A exist, the function selects the first available pair (returned by `find_pair_A`()) and applies the corresponding `A()` operation. + * If the `IntegralDomainWithoutDivision` of coefficients is a field, this operation always produces a perfect HDVF (ie.\ the reduced boundary is null and the reduction provides homology and cohomology information). + * Otherwise the operation produces a maximal HDVF with a residual boundary matrix over critical cells. + * + * If the HDVF is initially not trivial (some cells have already been paired), the function completes it into a perfect HDVF. + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \return The vector of all `Cell_pair` paired with A. + */ + std::vector compute_perfect_hdvf(bool verbose = false); + + /* + * \brief Computes a random perfect HDVF. + * + * As long as valid pairs for A exist, the function selects a random pair (among pairs returned by `find_pairs_A()`) and applies the corresponding `A()` operation. + * If the `IntegralDomainWithoutDivision` of coefficients is a field, this operation always produces a perfect HDVF (that is the reduced boundary is null and the reduction provides homology and cohomology information). + * + * If the HDVF is initially not trivial (some cells have already been paired), the function randomly completes it into a perfect HDVF. + * + * \warning This method is slower that `compute_perfect_hdvf()` (finding out all possible valid pairs requires additional time). + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \return The vector of all pairs of cells used for apply A. + */ + std::vector compute_rand_perfect_hdvf(bool verbose = false); + + /* + * \brief Tests if a HDVF is perfect. + * + * The function returns `true` if the HDVF is perfect, that is, if the reduced boundary matrix is null and `false` otherwise. The functions tests all dimensions if `dimension_restriction` is -1 (default value) and tests only dimension `dimension_restriction` otherwise. + * + *\param dimension_restriction If positive, restricts the test to a single dimension , if -1 is provided, all dimensions are tested, if -2 is provided (default) test is carried out according to the HDVF dimension restriction value. + */ + bool is_perfect_hdvf(int dimension_restriction = -2) { + bool res = true ; + + if (dimension_restriction == -2) + dimension_restriction = _dimension_restriction; + + int q = (dimension_restriction==-1)?0:_min_dimension, max_dim = (dimension_restriction==-1)?_K.dimension():_max_dimension ; + while ((q<=max_dim) && res) { + res = res && _DD_col.at(q).is_null() ; + ++q; + } + return res ; + } + + // Hdvf_core getters + + /* \brief Gets the number of cells with a given flag in dimension `q`. + * + * \param flag `PSC_flag` tested. + * \param q Dimension along which the number of cells is returned. + */ + size_t number_of_cells_by_flag(PSC_flag flag, int q) const { + if (flag == PRIMARY) return _nb_P.at(q); + else if (flag == SECONDARY) return _nb_S.at(q); + else return _nb_C.at(q); + } + + // !!! Why should it be virtual for duality????? + /* + * \brief Gets cells with a given `PSC_flag` in any dimension. + * + * The function returns a vector containing, for each dimension, the vector of cells with a given `PSC_flag`. Indices are sorted by increasing value. + * + * \param flag PSC_flag to select. + */ + virtual std::vector > psc_flag (PSC_flag flag) const ; + + /* + * \brief Gets cells with a given `PSC_flag` in dimension `q`. + * + * The function returns the vector of cells of dimension `q` with a given `PSC_flag`. Indices are sorted by increasing value. + * + * \param flag PSC_flag to select. + * \param q Dimension visited. + */ + virtual std::vector psc_flag (PSC_flag flag, int q) const ; + + /* + * \brief Gets the`PSC_flag` of all cells in dimension `q`. + * + * The function returns the vector of `PSC_flag` of cells of dimension `q`. + * + * \param q Dimension visited. + */ + virtual const std::vector& psc_flags (int q) const { return _flag.at(q); } + + /* + * \brief Gets the`PSC_flag` of all cells. + * + * The function returns a vector which qth element contains the vector of `PSC_flag` of cells of dimension `q`. + */ + virtual const std::vector >& psc_flags () const { return _flag; } + + /*! + * \brief Gets the PSC_flag of the cell `tau` in dimension `q`. + * + * \param tau Index of the cell. + * \param q Dimension of the cell. + */ + PSC_flag psc_flag (size_t tau, int q) const { return _flag.at(q).at(tau); } + + /* + * \brief Gets HDVF computation option. + */ + int hdvf_opts () const { return _hdvf_opt ; } + + /* + * \brief Gets the row-major matrix of \f$f\f$ (from the reduction associated to the HDVF). + */ + const Row_matrix& matrix_f (int q) const { return _F_row.at(q); } + + /* + * \brief Gets the column-major matrix of \f$g\f$ (from the reduction associated to the HDVF). + */ + const Column_matrix& matrix_g (int q) const { return _G_col.at(q); } + + /* + * \brief Gets the column-major matrix of \f$h\f$ (from the reduction associated to the HDVF). + */ + const Column_matrix& matrix_h (int q) const { return _H_col.at(q); } + + /* + * \brief Gets the column-major matrix of \f$\partial'\f$, reduced boundary operator (from the reduction associated to the HDVF). + */ + const Column_matrix& matrix_dd (int q) const { return _DD_col.at(q); } + + /* + \brief Gets a constant reference over the underlying chain complex. + */ + const Chain_complex& complex () const { return _K; } + + /* + * \brief Writes the PSC flags of cells along each dimension. + * + * If `dimension_restriction` is -1, writes flags in any dimensions. If `dimension_restriction` is positive, restricts output to `dimension_restriction`. If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \param out Output stream (by default, writes the complex to `std::cout`). + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs reductions in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + std::ostream& write_flags(std::ostream &out = std::cout, int dimension_restriction = -2) const { + if (dimension_restriction == -2) + dimension_restriction = _dimension_restriction; + + int min_dim = (dimension_restriction==-1)?0:_min_dimension; + int max_dim = (dimension_restriction==-1)?_K.dimension():_max_dimension; + // Print PSC + out << "----- flags of cells:" << std::endl; + for (int q = min_dim; q <= max_dim; ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < _K.number_of_cells(q); ++i) { + const int flag(_flag.at(q).at(i)) ; + if (flag == PRIMARY) + out << i << " -> P" << std::endl ; + else if (flag == SECONDARY) + out << i << " -> S" << std::endl ; + else + out << i << " -> C" << std::endl ; + } + out << std::endl; + } + return out; + } + + /* + * \brief Writes the matrices of the reduction. + * + * Writes the matrices of the reduction (that is \f$f\f$, \f$g\f$, \f$h\f$, \f$\partial'\f$ the reduced boundary). + * + * If `dimension_restriction` is -1, writes matrices in any dimensions. If `dimension_restriction` is positive, restricts output to `dimension_restriction`. If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \param out Output stream (by default, writes the complex to `std::cout`). + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs matrices in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + std::ostream& write_matrices(std::ostream &out = std::cout, int dimension_restriction = -2) const; + + /* + * \brief Writes the homology and cohomology reduction information. + * + * Writes \f$f^*\f$, \f$g\f$ \f$\partial'\f$ the reduced boundary over each critical cell. + * + * If `dimension_restriction` is -1, writes the reduction in any dimensions. If `dimension_restriction` is positive, restricts output to `dimension_restriction`. If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \param out Output stream (by default, writes the complex to `std::cout`). + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs reductions in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + std::ostream& write_reduction(std::ostream &out = std::cout, int dimension_restriction = -2) const; + + + /* + * \brief Exports primary/secondary/critical integers encoding labels (in particular for vtk export) + * + * The method exports the labels of cells. + * + * If `dimension_restriction` is -1, exports the labels of cells in any dimensions. If `dimension_restriction` is positive, exports only flags of dimension `dimension_restriction` (cells of other dimensions are labeled as 2 for`NONE` flag). If `dimension_restriction` is -2 (default value), uses the HDVF dimension restriction value. + * + * \return A vector containing, for each dimension, the vector of labels by cell index. + * + * \param dimension_restriction If positive, restricts the output to a single dimension, if -1, outputs labels in any dimensions, if -2 (default value) uses the HDVF dimension restriction value. + */ + virtual std::vector > psc_labels (int dimension_restriction = -2) const { + if (dimension_restriction == -2) + dimension_restriction = _dimension_restriction; + + std::vector > labels(_K.dimension()+1) ; + for (int q=0; q<=_K.dimension(); ++q) { + for (size_t i = 0; i<_K.number_of_cells(q); ++i) { + if ((dimension_restriction==-1) || (q==dimension_restriction)) { + if (_flag.at(q).at(i) == PRIMARY) + labels.at(q).push_back(-1) ; + else if (_flag.at(q).at(i) == SECONDARY) + labels.at(q).push_back(1) ; + else if (_flag.at(q).at(i) == CRITICAL) + labels.at(q).push_back(0) ; + else // NONE + labels.at(q).push_back(2) ; + } + else + labels.at(q).push_back(2) ; + } + } + return labels ; + } + + /* + * \brief Gets homology generators associated to `cell` (critical cell) of dimension `q` (used by vtk export). + * + * The method exports the chain \f$g(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`. + * + * \return A column-major chain. + */ + virtual Column_chain homology_chain (size_t cell_index, int q) const { + if ((q<0) || (q>_K.dimension())) + throw "Error : homology_chain with dim out of range" ; + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + Column_chain g_cell(OSM::get_column(_G_col.at(q), cell_index)) ; + // Add 1 to the cell + g_cell.set_coefficient(cell_index, 1) ; + return g_cell ; + } + else + throw "Error : trying to export g_chain without proper Hdvf_core option" ; + } + + /* + * \brief Gets cohomology generators associated to `cell_index` (critical cell) of dimension `q` (used by vtk export). + * + * The method exports the chain \f$f^\star(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`. + * + * \param cell_index Index of the (critical) cell. + * \param dim Dimension of the (critical) cell. + * + * \return A column-major chain. + */ + virtual Column_chain cohomology_chain (size_t cell_index, int dim) const { + if ((dim<0) || (dim>_K.dimension())) + throw "Error : cohomology_chain with dim out of range" ; + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + Row_chain fstar_cell(OSM::get_row(_F_row.at(dim), cell_index)) ; + // Add 1 to the cell + fstar_cell.set_coefficient(cell_index, 1) ; + + return fstar_cell.transpose() ; + + } + else + throw "Error : trying to export fstar_chain without proper Hdvf_core option" ; + } + +protected: + /* \brief Main code for saving a HDVF_core (except prefix) + */ + std::ostream& write_hdvf_reduction_main(std::ostream& out) const ; + +public: + + /* + * \brief Writes a HDVF together with the associated reduction (f, g, h, d matrices) + * + * Exports a HDVF to a stream in `hdvf` file format (a simple text file format, see for a specification). + * + * \param out Output stream. + */ + std::ostream& write_hdvf_reduction(std::ostream& out) const ; + + /* + * \brief Writes a HDVF together with the associated reduction to a file (f, g, h, d matrices). + * + * Exports a HDVF to a file in `hdvf` file format (a simple text file format, see for a specification). + * + * \param filename Output file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void write_hdvf_reduction(std::string filename) const { + std::ofstream out_file ( filename, std::ios::out | std::ios::trunc); + if ( ! out_file . good () ) { + std::cerr << "Out fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + write_hdvf_reduction(out_file) ; + + out_file.close(); + } + +protected: + /* \brief Main code for reading a HDVF_core (except prefix) + */ + std::istream& read_hdvf_reduction_main(std::istream& in_stream); + +public: + + /* + * \brief Reads a HDVF together with the associated reduction (f, g, h, d matrices) + * + * Loads a HDVF and its reduction from a stream in `hdvf` file format, a simple text file format (see for a specification). + * \warning The underlying complex is not stored in the file! + * + * \param in_stream Input stream. + * + * \exception Incompatible_prefix If the prefix of the stored HDVF is not 'HDVF_core' raises a `%std::runtime_error`. + * + * \exception Incompatible_type If the type of the stored HDVF is not 0 (HDVF and reduction storage) raises a `%std::runtime_error`. + * + * \exception Incompatible_dimension If the dimension of the HDVF stored does not match the dimension of the underlying complex, raises a `%std::runtime_error`. + * + * \exception Incoherent_number_of_cells If the number of cells loaded in a given dimension does not match that of the underlying complex, raises a `%std::runtime_error`. + */ + std::istream& read_hdvf_reduction(std::istream& in_stream) ; + + /* + * \brief Reads a HDVF together with the associated reduction from a file (f, g, h, d matrices) + * + * Loads a HDVF and its reduction from a file in `hdvf` file format, a simple text file format (see for a specification). + * \warning The underlying complex is not stored in the file! + * + * \param filename Input file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void read_hdvf_reduction(std::string filename) { + std::ifstream in_file (filename); + if ( ! in_file . good () ) { + std::cerr << "Out fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + read_hdvf_reduction(in_file); + + in_file.close(); + } + + /* \brief Compares the HDVF with another HDVF over the same underlying complex. + * + * \param other Other HDVF to compare. + * \param full_compare Turns on "in depth" HDVF comparison (reduction matrices). + */ + + virtual bool compare(const Hdvf_core& other, bool full_compare = false) const { + bool res; + int d = _flag.size(), d_other = other._flag.size(); + res = (d == d_other); + if (!res) { + std::cerr <<"HDVF compare: dimensions differ" << std::endl; + return false; + } + // Compare flags in each dimension + for (int q=0; res && (q<_K.dimension()); ++q) + res = res && (_flag.at(q) == other._flag.at(q)) ; + if (!res) { + std::cerr <<"HDVF compare: flags differ" << std::endl; + return false; + } + + // Check if full_compare is required + if (!full_compare) + return res; + + // ----- Only for full_compare + // F + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + for (int q=0; res && (q<_K.dimension()); ++q) + res = res && (_F_row.at(q) == other._F_row.at(q)); + if (!res) { + std::cerr <<"HDVF compare: F matrices differ" << std::endl; + return false; + } + } + // G + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + for (int q=0; res && (q<_K.dimension()); ++q) + res = res && (_G_col.at(q) == other._G_col.at(q)); + if (!res) { + std::cerr <<"HDVF compare: G matrices differ" << std::endl; + return false; + } + } + // H + for (int q=0; res && (q<_K.dimension()); ++q) + res = res && (_H_col.at(q) == other._H_col.at(q)); + if (!res) { + std::cerr <<"HDVF compare: H matrices differ" << std::endl; + return false; + } + // DD + for (int q=0; res && (q<_K.dimension()); ++q) + res = res && (_DD_col.at(q) == other._DD_col.at(q)); + if (!res) { + std::cerr <<"HDVF compare: DD matrices differ" << std::endl; + return false; + } + return res; + } + + /* \brief Comparison operator. + * + * Compares the HDVF with `other` HDVF (fast comparison testing only HDVF flags). + */ + bool operator==(const Hdvf_core& other) { + return compare(other, true); + } + +protected: + /* \brief Project a chain onto a given PSC_flag + * The methods cancels all the coefficients of the chain of dimension `q` that do not correspond to`PSC_flag`. This is actually an implementation of the projection operator onto the sub A-module generated by cells of PSC_flag `flag`. + * + * \param chain The chain projected. + * \param flag The PSC_flag onto which the chain is projected. + * \param q Dimension of the chain + * + * \result Returns a copy of `chain` where only coefficients of cells of PSC_flag `flag` are kept (all other coefficients are cancelled). + */ + template + Sparse_chain_base projection(const Sparse_chain_base& chain, PSC_flag flag, int q) const { + // Create a new chain to store the result + // Better to initialize 'result' directly with the correct size and iterate over it + Sparse_chain_base result(chain); + + // Iterate over each element of the chain + std::vector tmp ; + for (typename Sparse_chain_base::const_iterator it = result.cbegin(); it != result.cend(); ++it) { + size_t cell_index = it->first; + Coefficient_ring value = it->second; + + // Check the PSC_flag of the corresponding cell + if (_flag[q][cell_index] != flag) { + // Mark for cancellation + tmp.push_back(cell_index) ; + + } + } + // Cancel all coefficients of tmp + result /= tmp ; + return result; + } + + /* \brief Display a text progress bar. */ + void progress_bar(size_t i, size_t n) { + const size_t step(n>10?n/10:1) ; // TODO + if ((i%step)==0) { + const float percentage(float(i)/(n-1)) ; + const char PBSTR[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||" ; + const size_t PBWIDTH(60) ; + size_t val = (size_t) (percentage * 100); + size_t lpad = (size_t) (percentage * PBWIDTH); + size_t rpad = PBWIDTH - lpad; + printf("\r%3d%% [%.*s%*s]", val, lpad, PBSTR, rpad, ""); + fflush(stdout); + } + if (i==(n-1)) + std::cout << std::endl ; + } + +private: + void init_internals() { + int dim(_K.dimension()); + + // Compute mminimum and maximum dimensions of computation according to the value of `_dimension_restriction` + if ((_dimension_restriction < 0) || (_dimension_restriction > dim)) + _dimension_restriction = -1; + if (_dimension_restriction == -1) { + _min_dimension = 0; + _max_dimension = dim; + } + else + _min_dimension = _max_dimension = _dimension_restriction; + + // Resize the _DD_col vector to hold dim+1 elements + _DD_col.resize(dim + 1); + + // Resize the _F_row vector to hold dim+1 elements + if (_hdvf_opt & (OPT_FULL | OPT_F)) + _F_row.resize(dim + 1); + + // Resize the _G_col vector to hold dim+1 elements + if (_hdvf_opt & (OPT_FULL | OPT_G)) + _G_col.resize(dim + 1); + + // Resize the _H_col vector to hold dim+1 elements + if (_hdvf_opt & OPT_FULL) + _H_col.resize(dim + 1); + + // Initialize matrices and counters + + for (int q = 0; q <= dim; q++) { + // Initialize _F_row[q] as a row matrix with dimensions (dim(q) x dim(q)) + if (_hdvf_opt & (OPT_FULL | OPT_F)) + _F_row[q] = Row_matrix(_K.number_of_cells(q), _K.number_of_cells(q)); + + // Initialize _G_col[q] as a column matrix with dimensions (dim(q) x dim(q)) + if (_hdvf_opt & (OPT_FULL | OPT_G)) + _G_col[q] = Column_matrix(_K.number_of_cells(q), _K.number_of_cells(q)); + + // Initialize _H_col[q] as a column matrix with dimensions (dim(q+1) x dim(q)) + if (_hdvf_opt & OPT_FULL) + _H_col[q] = Column_matrix(_K.number_of_cells(q + 1), _K.number_of_cells(q)); + } + + // Populate the DD matrices + int tmp_min, tmp_max; + if (_dimension_restriction==-1) { + tmp_min = 0 ; + tmp_max = dim; + } + else { + if (_dimension_restriction > 0) + tmp_min = _dimension_restriction-1 ; + else + tmp_min = 0; + + if (_dimension_restriction +Hdvf_core::Hdvf_core(const ChainComplex& K, int hdvf_opt, int dimension_restriction) : _K(K), _hdvf_opt(hdvf_opt), _dimension_restriction(dimension_restriction) { + // Check if the complex is non empty + size_t acc(K.number_of_cells(0)); + for (int q=1; q(_K.number_of_cells(q), CRITICAL); + } + // std::cout << "------> End Hdvf_core creation" << std::endl ; +} + +// Method to print the matrices +template +std::ostream& Hdvf_core::write_matrices(std::ostream& out, int dimension_restriction) const { + if (dimension_restriction == -2) + dimension_restriction = _dimension_restriction; + + int min_dim = (dimension_restriction==-1)?0:_min_dimension; + int max_dim = (dimension_restriction==-1)?_K.dimension():_max_dimension; + // Iterate through each dimension and print the corresponding matrices + for (int q = min_dim; q <= max_dim; ++q) { + out << "------- Dimension " << q << std::endl; + + out << "Matrices _DD_col:" << std::endl; + out << _DD_col[q] << std::endl; + + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + out << "Matrices _F_row:" << std::endl; + out << _F_row[q] << std::endl; + } + + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + out << "Matrices _G_col:" << std::endl; + out << _G_col[q] << std::endl; + } + + if (_hdvf_opt & OPT_FULL) { + out << "Matrices _H_col:" << std::endl; + out << _H_col[q] << std::endl; + } + } + return out ; +} + + +// Methods to find a pair of cells for A in dimension q +// -> = +- 1 with cell1, cell2 critical +// First version: returns a pair of dimensions q / q+1 +// Second version: returns all the pairs containing sigma + +// find a valid Cell_pair for A in dimension q +template +Cell_pair Hdvf_core::find_pair_A(int q, bool &found) const { + found = false; + Cell_pair p; + + if (! _K.is_valid_cell_dimension(q)) { + std::cerr << "find_pair_A called with incoherent dimension " << q << std::endl; + return p; + } + + // Iterate through columns of _DD_col[q+1] + for (OSM::Bitboard::iterator it_col = _DD_col[q+1].begin(); (it_col != _DD_col[q+1].end() && !found); ++it_col) { + const Column_chain& col(OSM::cget_column(_DD_col[q+1], *it_col)) ; + + // Iterate through the entries of the column + for (typename Column_chain::const_iterator it = col.begin(); (it != col.end() && !found); ++it) { + if (CGAL::is_invertible(it->second)) { + // If an entry with coefficient 1 or -1 is found, set the pair and mark as found + p.sigma = it->first; + p.tau = *it_col; + p.dim = q; + found = true; + } + } + } + return p; +} + +// find a valid Cell_pair containing tau for A in dimension q +template +Cell_pair Hdvf_core::find_pair_A(int q, size_t gamma, bool &found) const { + found = false; + Cell_pair p ; + + if (! _K.is_valid_cell(gamma, q)) { + std::cerr << "find_pair_A called with incoherent cell " << std::endl; + return p; + } + + if (q>0) { + // Search for a q-1 cell tau' such that <_d(tau),tau'> invertible + const Column_chain& tmp2(OSM::cget_column(_DD_col.at(q), gamma)) ; + for (typename Column_chain::const_iterator it = tmp2.cbegin(); (it != tmp2.cend() && !found); ++it) { + if (CGAL::is_invertible(it->second)) { + found = true ; + p.sigma = it->first ; + p.tau = gamma ; + p.dim = q-1 ; + } + } + } + + if (q < _K.dimension()) { + // Search for a q+1 cell tau' such that <_d(tau'),tau> invertible, ie <_cod(tau),tau'> invertible + Row_chain tmp(OSM::get_row(_DD_col.at(q+1), gamma)) ; + for (typename Row_chain::const_iterator it = tmp.cbegin(); (it != tmp.cend() && !found); ++it) { + if (CGAL::is_invertible(it->second)) { + found = true ; + p.sigma = gamma ; + p.tau = it->first ; + p.dim = q ; + } + } + } + return p; +} + +// find all the valid PairCells for A in dimension q +template +std::vector Hdvf_core::find_pairs_A(int q, bool &found) const { + std::vector pairs; + found = false ; + + if (! _K.is_valid_cell_dimension(q)) { + std::cerr << "find_pairs_A called with incoherent dimension " << q << std::endl; + return pairs; + } + + // Iterate through columns of _DD_col[q+1] + for (OSM::Bitboard::iterator it_col = this->_DD_col[q+1].begin(); it_col != this->_DD_col[q+1].end(); ++it_col) + { + const Column_chain& col(OSM::cget_column(this->_DD_col[q+1], *it_col)) ; + + // Iterate through the entries of the column + for (typename Column_chain::const_iterator it = col.begin(); it != col.end(); ++it) { + if (CGAL::is_invertible(it->second)) { + // If an entry with an invertible coefficient is found, set the pair and mark as found + Cell_pair p; + p.sigma = it->first; + p.tau = *it_col; + p.dim = q; + pairs.push_back(p) ; + found = true; + } + } + } + return pairs; +} + +// find all the valid Cell_pair containing gamma for A in dimension q +template +std::vector Hdvf_core::find_pairs_A(int q, size_t gamma, bool &found) const { + found = false; + std::vector pairs; + + if (! _K.is_valid_cell(gamma, q)) { + std::cerr << "find_pairs_A called with incoherent cell " << std::endl; + return pairs; + } + + // Search for a q+1 cell tau' such that <_d(tau'),tau> invertible, ie <_cod(tau),tau'> invertible + if (q < _K.dimension()) { + Row_chain tmp(OSM::get_row(_DD_col.at(q+1), gamma)) ; + for (typename Row_chain::const_iterator it = tmp.cbegin(); it != tmp.cend(); ++it) { + if (CGAL::is_invertible(it->second)) { + found = true ; + Cell_pair p ; + p.sigma = gamma ; + p.tau = it->first ; + p.dim = q ; + pairs.push_back(p) ; + } + } + } + // Search for a q-1 cell tau' such that <_d(tau),tau'> invertible + if (q>0) { + const Column_chain& tmp2(OSM::cget_column(_DD_col.at(q), gamma)) ; + for (typename Column_chain::const_iterator it = tmp2.cbegin(); it != tmp2.cend(); ++it) { + // if (abs(it->second) == 1) + if (CGAL::is_invertible(it->second)) { + found = true ; + Cell_pair p ; + p.sigma = it->first ; + p.tau = gamma ; + p.dim = q-1 ; + pairs.push_back(p) ; + } + } + } + return pairs; +} + + +// Method to perform operation A +// tau1 is in dimension q, tau2 is in dimension q+1 +template +void Hdvf_core::A(size_t tau1, size_t tau2, int q) { + //----------------------------------------------- Submatrices of D ---------------------------------------------------- + + // Output operation details to the console +#ifdef DEBUG + std::cout << "A of " << tau1 << "(dim " << q << ") / " << tau2 << "(dim " << q + 1 << ")" << std::endl; +#endif + + // Extract submatrices from _DD_col + Row_chain D12(OSM::get_row(_DD_col.at(q+1),tau1)); // D12 is a row chain from _DD_col[q+1] at index tau1 + Column_chain D21(OSM::get_column(_DD_col.at(q + 1),tau2)); // D21 is a column chain from _DD_col[q+1] at index tau2 + const Coefficient_ring D11 = D12.get_coefficient(tau2); // D11 is the coefficient at the intersection of tau2 in D12 + + // Assert that D11 is either 1 or -1 (check invertibility) + if (! CGAL::is_invertible(D11)) + throw(std::invalid_argument("Invalid arguments for A - cells do not meet the required condition")); + + // Compute the inverse of D11 + Coefficient_ring D11_inv = CGAL::inverse(D11); + + // Perform operations to remove the row and column contributions + D12 /= std::vector({tau2}); // Remove tau2 column from D12 + D21 /= std::vector({tau1}); // Remove tau1 row from D21 + + // Delete rows and columns from _DD_col + remove_row(_DD_col[q + 1], tau1); // Remove row tau1 from _DD_col[q+1] + remove_column(_DD_col[q + 1], tau2); // Remove column tau2 from _DD_col[q+1] + + //---------------------------------------------- Submatrices of F ----------------------------------------------------- + + Row_chain F11 ; + Column_chain G11 ; + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + // Extract the relevant submatrix from _F_row + F11 = OSM::get_row(_F_row.at(q),tau1); // F11 is a row chain from _F_row[q] at index tau1 + + // Delete the row tau1 from _F_row + remove_row(_F_row[q], tau1); + } + + //--------------------------------------------- Submatrices of G ------------------------------------------------------ + + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + // Extract the relevant submatrix from _G_col + G11 = OSM::get_column(_G_col.at(q + 1),tau2); // G11 is a column chain from _G_col[q+1] at index tau2 + + // Delete the column tau2 from _G_col + remove_column(_G_col[q + 1], tau2); + } + + //--------------------------------------------- Update matrices ------------------------------------------------------- + + // ---- Update _F_row + + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + // Update _F_row[q] + // Subtract the product of (D21 * D11_inv) and F11 from _F_row[q] + // Note: % operator returns a row matrix, so be careful with operations + _F_row[q] -= (D21 * D11_inv) % F11; + + // Set the column tau1 of _F_row[q] to (D21 * (-D11_inv)) + OSM::set_column(_F_row[q], tau1, D21 * (-D11_inv)); + + // Remove the row tau2 from _F_row[q+1] + remove_row(_F_row[q + 1], tau2); + } + + // ---- Update _G_col + + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + // Update _G_col[q + 1] + // Subtract the product of (G11 * D11_inv) and D12 from _G_col[q+1] + _G_col[q + 1] -= (G11 * D11_inv) * D12; + + // Set the row tau2 of _G_col[q + 1] to (D12 * (-D11_inv)) + OSM::set_row(_G_col[q + 1], tau2, D12 * (-D11_inv)); + + // Remove the column tau1 from _G_col[q] + remove_column(_G_col[q], tau1); + } + + // ---- Update _H_col + + if (_hdvf_opt & OPT_FULL) { + // Update _H_col[q] + // Compute the temporary matrix product G11 * F11 + Column_matrix tmp = G11 * F11; + + // Add the product of tmp and D11_inv to _H_col[q] + _H_col[q] += (tmp) * D11_inv; + + // Set the row tau2 of _H_col[q] to (F11 * D11_inv) + OSM::set_row(_H_col[q], tau2, F11 * D11_inv); + + // Set the column tau1 of _H_col[q] to (G11 * D11_inv) + OSM::set_column(_H_col[q], tau1, G11 * D11_inv); + + // Set the coefficient at (tau2, tau1) in _H_col[q] to D11_inv + set_coefficient(_H_col[q], tau2, tau1, D11_inv); + } + + // ---- Update _DD_col + + // Update _DD_col + _DD_col[q + 1] -= (D21 * D12) * D11_inv; + + // Remove columns and rows from _DD_col as necessary + if (q > 0) { + remove_column(_DD_col[q], tau1); // Remove column tau1 from _DD_col[q] + } + if (q + 2 <= _K.dimension()) { + remove_row(_DD_col[q + 2], tau2); // Remove row tau2 from _DD_col[q+2] + } + + // Update flags + _flag[q][tau1] = PRIMARY; // Set the PSC_flag of tau1 in dimension q to PRIMARY + --_nb_C.at(q) ; + ++_nb_P.at(q) ; + _flag[q + 1][tau2] = SECONDARY; // Set the PSC_flag of tau2 in dimension q+1 to SECONDARY + --_nb_C.at(q+1) ; + ++_nb_S.at(q+1) ; + + // ----------------------------------------------------------------------------------------------------------------------- +} + + +// Method to compute a perfect Hdvf_core +template +std::vector Hdvf_core::compute_perfect_hdvf(bool verbose) { + std::vector pair_list; // Vector to store the list of pairs + bool trouve = false; // Flag to indicate whether a pair was found + int dim = _K.dimension(); // Get the dimension of the complex K + + int min_dim, max_dim; + + // Compute min and max dimensions of find_pair_A + if (_dimension_restriction == -1) { + min_dim = 0; + max_dim = dim-1; + } + else { + if (_dimension_restriction > 0) + min_dim = _dimension_restriction-1; + else + min_dim = 0; + + if (_dimension_restriction < dim-1) + max_dim = _dimension_restriction; + else + max_dim = dim-1; + } + + // Loop through decreasing dimensions + for (int q = max_dim; q >= min_dim; --q) { + std::cout << std::endl << "-> pairing cells of dimension " << q << " / " << q+1 << std::endl ; + + // Find a pair of cells in dimension q + Cell_pair pair = find_pair_A(q, trouve); + + // While a pair is found + while (trouve) { + progress_bar(_K.number_of_cells(q)-_nb_C.at(q), _K.number_of_cells(q)) ; + // Add the found pair to the list + pair_list.push_back(pair); + + // Perform operation A with the found pair + A(pair.sigma, pair.tau, q); + if (verbose) { + std::cout << "A : " << pair.sigma << " - " << pair.tau << " (dim " << pair.dim << ")" << std::endl ; + write_matrices(std::cout) ; + } + + // Find another pair of cells in dimension q + pair = find_pair_A(q, trouve); + } + std::cout << std::endl; + } + + // Return the list of pairs found + return pair_list; +} + +// Method to compute a random perfect Hdvf_core +// Returns a vector of Cell_pair objects representing the pairs found +template +std::vector Hdvf_core::compute_rand_perfect_hdvf(bool verbose) { + std::vector pair_list; // Vector to store the list of pairs + bool trouve = false; // Flag to indicate whether a pair was found + int dim = _K.dimension(); // Get the dimension of the complex K + + // Init random generator + std::random_device dev; + std::mt19937 rng(dev()); // Random + + int min_dim, max_dim; + + // Compute min and max dimensions of find_pair_A + if (_dimension_restriction == -1) { + min_dim = 0; + max_dim = dim-1; + } + else { + if (_dimension_restriction > 0) + min_dim = _dimension_restriction-1; + else + min_dim = 0; + + if (_dimension_restriction < dim-1) + max_dim = _dimension_restriction; + else + max_dim = dim-1; + } + + // Loop through decreasing dimensions + for (int q = max_dim; q >= min_dim; --q) { + std::cout << "-> pairing cells of dimension " << q << std::endl ; + // Incorrect: the number of cells is the number of cols in _DD_col ... (duality) + + std::vector pairs = find_pairs_A(q, trouve); + + Cell_pair pair ; + + // While a pair is found + while (trouve) { + // Add one of the pairs (randomly) to the list + { + // Pickup a random cell sigma + std::uniform_int_distribution rand_dist(0,pairs.size()-1); + size_t i(rand_dist(rng)) ; + pair = pairs.at(i) ; + } + pair_list.push_back(pair); + + // Perform operation A with the chosen pair + A(pair.sigma, pair.tau, pair.dim); + if (verbose) { + std::cout << "A : " << pair.sigma << " - " << pair.tau << " (dim " << pair.dim << ")" << std::endl ; + write_matrices(std::cout) ; + } + + // Compute possible pairings + pairs = find_pairs_A(q, trouve); + } + std::cout << std::endl; + } + // Return the list of pairs found + return pair_list; +} + +// Method to get cells if with a given psc_flag (P,S,C) for each dimension +template +std::vector > Hdvf_core::psc_flag (PSC_flag flag) const { + std::vector > res(_K.dimension()+1) ; + for (int q=0; q<=_K.dimension(); ++q) { + for (size_t i=0; i<_K.number_of_cells(q); ++i) { + if (_flag.at(q).at(i) == flag) + res.at(q).push_back(i) ; + } + } + return res ; +} + +// Method to get cells with a given PSC_flag (P,S,C) for a given dimension +template +std::vector Hdvf_core::psc_flag (PSC_flag flag, int q) const { + std::vector res ; + if ((q>=0) && (q<=_K.dimension())) { + for (size_t i=0; i<_K.number_of_cells(q); ++i) { + if (_flag.at(q).at(i) == flag) + res.push_back(i) ; + } + } + return res ; +} + +// Method to print the current state of the reduction +template +std::ostream& Hdvf_core::write_reduction(std::ostream& out, int dimension_restriction) const { + if (dimension_restriction == -2) + dimension_restriction = _dimension_restriction; + + int min_dim = (dimension_restriction==-1)?0:_min_dimension; + int max_dim = (dimension_restriction==-1)?_K.dimension():_max_dimension; + + // Print critical cells + out << "----- critical cells:" << std::endl; + std::vector > critical(psc_flag(CRITICAL)) ; + for (int q = min_dim; q <= max_dim; ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < critical.at(q).size(); ++i) { + out << critical.at(q).at(i) << " "; + } + out << std::endl; + } + + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + // Print matrices g + out << "----- g:" << std::endl; + for (int q = min_dim; q <= max_dim; ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < critical.at(q).size(); ++i) { + const size_t id(critical.at(q).at(i)) ; + out << "g(" << id << ") = (" << id << ")"; + // Iterate over the ith column of _G_col + Column_chain col(OSM::get_column(_G_col.at(q), id)) ; // TODO cget + for (typename Column_chain::const_iterator it_col = col.cbegin(); it_col != col.cend(); ++it_col) { + out << " + " << it_col->second << ".(" << it_col->first << ") + "; + } + out << std::endl; + } + } + } + + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + // Print matrices f* + out << "----- f*:" << std::endl; + for (int q = min_dim; q <= max_dim; ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < critical.at(q).size(); ++i) { + const size_t id(critical.at(q).at(i)) ; + out << "f*(" << id << ") = (" << id << ")"; + // Iterate over the ith row of _F_row + Row_chain row(OSM::get_row(_F_row.at(q), id)) ; // TODO cget + for (typename Row_chain::const_iterator it_row = row.cbegin(); it_row != row.cend(); ++it_row) { + out << " + " << it_row->second << ".(" << it_row->first << ") + "; + } + out << std::endl; + } + } + } + return out ; +} + +// Save HDVF and reduction: save prefix and run main code +template +std::ostream& Hdvf_core::write_hdvf_reduction(std::ostream& out) const { + // HDVF prefix + // For Hdvf_core: #HDVF_core + // For Hdvf_persistence: #HDVF_persistence + // For Hdvf_duality: #HDVF_duality + out << "#HDVF_core" << std::endl; + return write_hdvf_reduction_main(out); +} + +// Save HDVF and reduction main code +template +std::ostream& Hdvf_core::write_hdvf_reduction_main(std::ostream& out) const { + // HDVF save type + // 0: HDVF and reduction + // 1: HDVF only + out << 0 << std::endl ; + // Dimension + out << _K.dimension() << std::endl ; + // Number of cells in each dimension + for (int q=0; q<=_K.dimension(); ++q) + out << _K.number_of_cells(q) << " " ; + out << std::endl ; + // HDVF option + // + out << _hdvf_opt << std::endl; + // Flags + // P : -1 / S : 1 / C : 0 + // Each dimension written on a row + for (int q=0; q<=_K.dimension(); ++q) { + for (int i=0; i<_K.number_of_cells(q); ++i) { + if (_flag.at(q).at(i) == PRIMARY) + out << -1 << " " ; + else if (_flag.at(q).at(i) == SECONDARY) + out << 1 << " " ; + else // CRITICAL + out << 0 << " " ; + } + out << std::endl ; + } + // F + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + for (int q=0; q<=_K.dimension(); ++q) + OSM::write_matrix(_F_row.at(q), out) ; + } + // G + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + for (int q=0; q<=_K.dimension(); ++q) + OSM::write_matrix(_G_col.at(q), out) ; + } + // H + for (int q=0; q<=_K.dimension(); ++q) + OSM::write_matrix(_H_col.at(q), out) ; + // DD + for (int q=0; q<=_K.dimension(); ++q) + OSM::write_matrix(_DD_col.at(q), out) ; + return out; +} + +// Read HDVF and reduction: check prefix and call main code +template +std::istream& Hdvf_core::read_hdvf_reduction(std::istream& in_stream) { + // Load and check HDVF prefix + // For Hdvf_core: #HDVF_core + // For Hdvf_persistence: #HDVF_persistence + // For Hdvf_duality: #HDVF_duality + std::string prefix; + in_stream >> prefix; + if (prefix.compare("#HDVF_core")) { + std::cerr << "try to load a HDVF with incompatible prefix" << std::endl ; + throw (std::runtime_error("try to load a HDVF with incompatible prefix")); + } + + return read_hdvf_reduction_main(in_stream); +} + +// Read HDVF and reduction main code +template +std::istream& Hdvf_core::read_hdvf_reduction_main(std::istream& in_stream) { + // Load and check HDVF save type + int type ; + in_stream >> type ; + if (type != 0) { + std::cerr << "try to load a pure HDVF file" << std::endl ; + throw (std::runtime_error("try to load a pure HDVF file")); + } + + // Load and check dimension + int d ; + in_stream >> d ; + if (d != _K.dimension()) { + std::cerr << "dimension loaded incompatible with the dimension of the underlying complex" << std::endl ; + throw (std::runtime_error("dimension loaded incompatible with the dimension of the underlying complex")); + } + // Load and check number of cells + int nb ; + for (int q=0; q<=_K.dimension(); ++q) { + in_stream >> nb ; + if (nb != _K.number_of_cells(q)) { + std::string mess("incoherent number of cells in dimension "); + mess += std::to_string(q); + std::cerr << mess << std::endl ; + throw (std::runtime_error(mess)); + } + } + // Load HDVF opt + in_stream >> _hdvf_opt ; + // Load flags + int flag ; + for (int q=0; q<=_K.dimension(); ++q) { + for (int i=0; i<_K.number_of_cells(q); ++i) { + in_stream >> flag ; + if (flag == -1) { + _flag.at(q).at(i) = PRIMARY ; + ++_nb_P.at(q); + } + else if (flag == 1) { + _flag.at(q).at(i) = SECONDARY ; + ++_nb_S.at(q); + } + else { + _flag.at(q).at(i) = CRITICAL ; + ++_nb_C.at(q); + } + } + } + // Load reduction matrices + // F + if (_hdvf_opt & (OPT_FULL | OPT_F)) { + for (int q=0; q<=_K.dimension(); ++q) + OSM::read_matrix(_F_row.at(q), in_stream) ; + } + // G + if (_hdvf_opt & (OPT_FULL | OPT_G)) { + for (int q=0; q<=_K.dimension(); ++q) + OSM::read_matrix(_G_col.at(q), in_stream) ; + } + // H + for (int q=0; q<=_K.dimension(); ++q) { + OSM::read_matrix(_H_col.at(q), in_stream) ; + } + // DD + for (int q=0; q<=_K.dimension(); ++q) { + OSM::read_matrix(_DD_col.at(q), in_stream) ; + } + return in_stream ; +} + + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_HDVF_CORE_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_duality.h b/HDVF/include/CGAL/HDVF/Hdvf_duality.h new file mode 100644 index 000000000000..e87886b62ea2 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_duality.h @@ -0,0 +1,1001 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_HDVF_DUALITY_H +#define CGAL_HDVF_HDVF_DUALITY_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG_HDVF_DUALITY + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFHDVFClasses + + The class `Hdvf_duality` is the implementation of homological discrete vector fields (HDVF for short) for Alexander duality computation. + + \warning The ring of coefficients provided should be a **field**. + + In dimension \f$n\f$, given a complex \f$L\f$ homeomorphic to \f$\mathscr S^n\f$ and a sub-complex \f$K\subseteq L\f$, Alexander duality states that for all \f$q\leqslant n\f$: + \f\[\overset{\thicksim}{H}_q(K) \simeq \overset{\thicksim}{H}^{n-q-1}(L-K)\f\] + where \f$\overset{\thicksim}{H}_q\f$ and \f$\overset{\thicksim}{H}^q\f$ denote reduced homology and cohomology groups. + + Combinatorial Alexander duality (see \ref secHDVFAlexander_package "user manual" and [Gonzalez and al. 2025]) is actually based on the following isomorphism for the co-complex \f$L-K\f$, for all \f$q\leqslant n\f$: + \f\[\overset{\thicksim}{H}_q(K) \simeq \overset{\thicksim}{H}^{q+1}(L-K)\f\] + + HDVFs provide a fast and convenient mean to compute this isomorphism. In this context, the complex \f$L\f$ must be homeomorphic to a ball of dimension \f$n\f$ (thus \f$\mathscr S^n\f$ is actually homeomorphic to \f$L\f$ plus an infinite \f$n\f$-cell closing its boundary). + + Perfect HDVFs are first computed over \f$K\f$ and \f$L-K\f$ (providing corresponding relative homology) respectively and Alexander isomorphism gives rise to a pairing between critical cells in \f$K\f$ and \f$L-K\f$, that is a pairing between homology/cohomology generators in \f$K\f$ and \f$L-K\f$. + + In order to compute relative homology, a sub chain complex mask is used to partially screen the complex `L` and thus restrict HDVF computation. This mask is called "current mask" (and can be set over `K` or `L-K`) and the corresponding sub chain complex is called the "current sub chain complex". + + The class provides HDVF constuction operations: `compute_perfect_hdvf()` and `compute_rand_perfect_hdvf()`, which build perfect HDVFs over \f$K\f$ and \f$L-K\f$ respectively (actually, the package computes relative homology). + Then, `compute_alexander_pairing()` computes Alexander isomorphism (and provides a pairing between homology/cohomology generators in \f$K\f$ and \f$L-K\f$). + + \cgalModels{HDVF} + + \tparam ChainComplex a model of the `AbstractChainComplex` concept, providing the type of abstract chain complex used. The sparse matrix structure provided by `ChainComplex` must be an instance of the `Sub_sparse_matrix_structure` template. + + \warning The sparse matrix provided by `ChainComplex` must be an instance of the `Sub_sparse_matrix_structure` template. + + [Gonzalez and al. 2025] Gonzalez-Lorenzo, A., Bac, A. & Gazull, YS. A constructive approach of Alexander duality. J Appl. and Comput. Topology 9, 2 (2025). + */ + +// +//In [Gonzalez and al. 2025], the authors prove that, even if \f$L-K\f$ is not a sub-complex, it produces a valid chain complex (we call "co-complex" such a complementary of sub-complex). Hence, its homology/cohomology can be computed and for all \f$q\leqslant n\f$: +//\f\[\tilde H_q(K) \simeq \tilde H^{q+1}(L-K)\f\] +// +//HDVFs provide a fast and convenient mean to compute this isomorphism. In order to work with convenient finite complexes, the complex \f$L\f$ must be homeomorphic to a ball of dimension \f$n\f$ (thus \f$\mathcal S^n\f$ is actually homeomorphic to \f$L\f$ plus an infinite \f$n\f$-cell closing its boundary). +// +//Perfect HDVFs are first computed over \f$K\f$ and \f$L-K\f$ (providing corresponding relative homology) respectively and Alexander isomorphism gives rise to a pairing between critical cells in \f$K\f$ and \f$L-K\f$, that is a pairing between homology/cohomology generators in \f$K\f$ and \f$L-K\f$. +// +// +// +//Example of Alexander duality isomorphism. The twirl mesh is a subcomplex `K` of a larger complex `L` depicted in yellow, homeomorphic to the ball of dimension 3 (right - sectional view). +// +//\cgalFigureBegin{Duality_quartet,HDVF_twirl_quartet.png} +//Example of "homological quartet for the twirl model". 1: Homology generators of the twirl \f$H_1(K)\f$, 2: Cohomology generators of the twirl \f$H^1(K)\f$, 3: Homology generators of the complementary of the twirl \f$H_1(L-K)\f$, 4: Cohomology generators of the complementary of the twirl \f$H^1(L-K)\f$. Alexander isomorphism is represented through colours (paired generators have similar colours). +//\cgalFigureEnd +// +//Hence, each hole in \f$K\f$ gives rise to four generators (called its "homological quarted": its homology and cohomology generators in \f$K_q\f$ and the homology and cohomology generators paired with them in \f$(L-K)_{q+1}\f$). + +template +class Hdvf_duality : public Hdvf_core { +public: + /*! \brief Chain complex type. */ + typedef ChainComplex Chain_complex; + + /*! \brief Type of coefficients used to compute homology. */ + typedef typename Chain_complex::Coefficient_ring Coefficient_ring; + + /*! + Type of parent Hdvf_core class. + */ + typedef Hdvf_core Base ; + + // Inherited types + using Column_chain = typename Base::Column_chain; + using Row_chain = typename Base::Row_chain; + +private: + // Complex L + const ChainComplex& _L ; + // Subcomplex K + // _KCC is the Sub_chain_complex_mask describing the subcomplex K + // _subCC is the Sub_chain_complex_mask describing the current subcomplex (K, L-K or remaining critical cells for pairing) + Sub_chain_complex_mask _KCC, _subCC ; + + // Critical cells of perfect HDVFs (over K / L-K respectively) + std::vector > _critical_K, _critical_L_K ; + +public: + /** + * \brief Hdvf_duality constructor ( from a complex `L` and a sub-complex `K`) + * + * `L` is a complex of a given dimension \f$n\f$ homeomorphic to \f$\mathcal B^n\f$ and `K` is a sub-complex of `L` described by a bitboard (cells of `K` have a bit set to 1, cells of `K` have a bit set to 0). + * + * Initially, the sub chain complex mask is set to `K`. + * + * \param L A complex of a given dimension \f$n\f$ homeomorphic to \f$\mathcal B^n\f$. + * \param K A sub complex of `L` encoded through a bitboard. + * \param hdvf_opt Option for HDVF computation (`OPT_BND`, `OPT_F`, `OPT_G` or `OPT_FULL`). + * + * \exception Empty_complex If the complex `L` is empty, raises a `%std::runtime_error`. + */ + Hdvf_duality(const Chain_complex& L, Sub_chain_complex_mask& K, int hdvf_opt = OPT_FULL) ; + +protected: + /* + * \brief Hdvf_duality copy constructor + * + * \param other `Hdvf_duality` to copy. + */ + Hdvf_duality(const Hdvf_duality& other) : Base(other), _L(other._L), _KCC(other._KCC), _subCC(other._subCC), _critical_K(other._critical_K), _critical_L_K(other._critical_L_K) {} + +public: + /** + * \brief Finds a valid cell pair of dimension q / q+1 for A *in the current sub chain complex*. + * + * The function searches a pair of critical cells, *in the current sub chain complex*, \f$(\gamma_1, \gamma2)\f$ of dimension q / q+1, valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ invertible). It returns the first valid pair found by iterators. + * + * \param q Lower dimension of the pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + Cell_pair find_pair_A(int q, bool &found) const; + + /** + * \brief Finds a valid cell pair for A containing `gamma` *in the current sub chain complex* (a cell of dimension `q`) + * + * The function searches a cell \f$\gamma'\f$ *in the current sub chain complex* such that one of the following conditions holds: + * - \f$\gamma'\f$ has dimension q+1 and \f$(\gamma, \gamma')\f$ is valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma'), \gamma \rangle\f$ invertible), + * - \f$\gamma'\f$ has dimension q-1 and \f$(\gamma', \gamma)\f$ is valid for A (ie.\ such that \f$\langle \partial_{q}(\gamma), \gamma' \rangle\f$ invertible). + * + * \param q Dimension of the cell `gamma`. + * \param gamma Index of a cell to pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + Cell_pair find_pair_A(int q, size_t gamma, bool &found) const; + + /** + * \brief Finds *all* valid cell pairs of dimension q / q+1 *in the current sub chain complex* for A. + * + * The function searches all pairs of critical cells \f$(\gamma_1, \gamma2)\f$ *in the current sub chain complex* of dimension q / q+1, valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma_2), \gamma_1 \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Lower dimension of the pairs. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + std::vector find_pairs_A(int q, bool &found) const; + + /** + * \brief Finds *all* valid cell pairs for `A`containing `gamma` *in the current sub chain complex* (a cell of dimension `q`) + * + * The function searches all `CRITICAL` cells \f$\gamma'\f$ *in the current sub chain complex* such that one of the following conditions holds: + * - \f$\gamma'\f$ has dimension q+1 and \f$(\gamma, \gamma')\f$ is valid for A (ie.\ such that \f$\langle \partial_{q+1}(\gamma'), \gamma \rangle\f$ invertible), + * - \f$\gamma'\f$ has dimension q-1 and \f$(\gamma', \gamma)\f$ is valid for A (ie.\ such that \f$\langle \partial_{q}(\gamma), \gamma' \rangle\f$ invertible). + * It returns a vector of such pairs. + * + * \param q Dimension of the cell `gamma`. + * \param gamma Index of a cell to pair. + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + std::vector find_pairs_A(int q, size_t gamma, bool &found) const; + + /** \brief Sets the current sub chain complex masks over `K`. + * + * Further HDVF computations will be restricted to `K` (ie.\ computation of reduced homology). + */ + inline void set_mask_K () + { + _subCC = _KCC ; + _subCC.screen_matrices(this->_DD_col); + } + + /** \brief Sets the current sub chain complex masks over `L-K`. + * + * Further HDVF computations will be restricted to `L-K` (ie.\ computation of reduced homology). + */ + inline void set_mask_L_K () + { + _subCC = _KCC.complement() ; + _subCC.screen_matrices(this->_DD_col); + } + + /** \brief Returns the value of the current sub chain complex mask. + */ + Sub_chain_complex_mask get_current_mask() const + { + return _subCC; + } + + /** + * \brief Computes a perfect HDVF over the current sub chain complex. + * + * As long as valid pairs for `A` exist in the current sub chain complex, the function selects the first available pair (returned by `find_pair_A()`) and applies the corresponding `A()` operation. + * If the coefficient ring of coefficients is a field, this operation always produces a perfect HDVF (ie.\ the reduced boundary is null and the reduction provides homology and cohomology information). + * Otherwise the operation produces a maximal HDVF with a residual boundary matrix over critical cells. + * + * If the HDVF is initially not trivial (some cells have already been paired), the function completes it into a perfect HDVF. + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \return The vector of all `Cell_pair` paired with A. + */ + std::vector compute_perfect_hdvf(bool verbose = false); + + /** + * \brief Computes a random perfect HDVF over the current sub chain complex. + * + * As long as valid pairs for A exist in the current sub chain complex, the function selects a random pair (among pairs returned by `find_pairs_A()`) and applies the corresponding `A()` operation. + * If the coefficient ring is a field, this operation always produces a perfect HDVF (that is the reduced boundary is null and the reduction provides homology and cohomology information). + * + * If the HDVF is initially not trivial (some cells have already been paired), the function randomly completes it into a perfect HDVF. + * + * \warning This method is slower that `compute_perfect_hdvf()` (finding out all possible valid pairs requires additional time). + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \return The vector of all pairs of cells used for apply A. + */ + std::vector compute_rand_perfect_hdvf(bool verbose = false); + + /** + * \brief Tests if HDVFs over `K` and `L-K` are perfect. + * + * The function returns `true` if the HDVFs over `K` and `L-K`are perfect, that is, if the reduced boundary matrix is null and `false` otherwise. The functions tests all dimensions if `dimension_restriction` is -1 (default value) and tests only dimension `dimension_restriction` otherwise. + * + *\param dimension_restriction If positive, restricts the test to a single dimension , if -1 is provided, all dimensions are tested, if -2 is provided (default) test is carried out according to the HDVF dimension restriction value. + */ + bool is_perfect_hdvf(int dimension_restriction = -2) { + bool res = true ; + + if (dimension_restriction == -2) + dimension_restriction = this->_dimension_restriction; + + int q = (dimension_restriction==-1)?0:this->_min_dimension, max_dim = (dimension_restriction==-1)?this->_K.dimension():this->_max_dimension ; + while ((q<=max_dim) && res) { + set_mask_K(); + res = res && this->_DD_col.at(q).is_null(_subCC.get_bitboard(q-1)) ; + set_mask_L_K(); + res = res && this->_DD_col.at(q).is_null(_subCC.get_bitboard(q-1)) ; + ++q; + } + set_mask_K(); + return res ; + } + + /** + * \brief Computes a "pairing" HDVF between K and L-K. + * + * \warning Run `compute_perfect_hdvf()` first (to build perfect HDVFs over `K` and `L-K` respectively). + * + * The function computes a perfect HDVF over remaining critical cells. Each pair of cells inserted with the `A()` operation maps corresponding homology/cohomology generators in the Alexander isomorphism. + * + * \return The vector of paired critical cells (encoding Alexander isomorphism). + * + * \exception Non_perfect_hdvfs If the HDVFs over `K` and `L-K` are not perfect, raises a `%std::runtime_error`. + */ + std::vector compute_pairing_hdvf() ; + + /** + * \brief Computes a random "pairing" HDVF between K and L-K + * + * \warning Run `compute_perfect_hdvf()` first (to build perfect HDVFs over `K` and `L-K` respectively). + * + * The function computes a random perfect HDVF over remaining critical cells. Each pair of cells inserted with the `A()` operation maps corresponding homology/cohomology generators in the Alexander isomorphism. + * + * \return The vector of paired critical cells (encoding Alexander isomorphism). + * + * \exception Non_perfect_hdvfs If the HDVFs over `K` and `L-K` are not perfect, raises a `%std::runtime_error`. + */ + std::vector compute_rand_pairing_hdvf() ; + + // Hdvf_duality getters + /** + * \brief Gets cells with a given `PSC_flag` in any dimension *in the current sub chain complex*. + * + * The function returns a vector containing, for each dimension, the vector of cells of the current sub chain complex with a given `PSC_flag`. + * + * \param flag PSC_flag to select. + */ + std::vector > psc_flag (PSC_flag flag) const ; + + /** + * \brief Gets cells with a given `PSC_flag` in dimension `q` *in the current sub chain complex*. + * + * The function returns the vector of cells of dimension `q` of the current sub chain complex with a given `PSC_flag`. + * + * \param flag PSC_flag to select. + * \param q Dimension visited. + */ + std::vector psc_flag (PSC_flag flag, int q) const ; + + // Hdvf_duality I/O + + /** + * \brief Prints the homology and cohomology reduction information for `K` and `L-K`. + * + * Prints \f$f^*\f$, \f$g\f$ \f$\partial'\f$ the reduced boundary over each critical cell. + * + * By default, outputs the complex to `std::cout`. + */ + std::ostream& write_reduction(std::ostream& out = std::cout) + { + // Print K + out << "----> K" << std::endl ; + _subCC = _KCC ; + _subCC.screen_matrices(this->_DD_col); + write_reduction_sub(out) ; + + // Print L-K + out << "----> L-K" << std::endl ; + _subCC = _KCC.complement() ; + _subCC.screen_matrices(this->_DD_col); + write_reduction_sub(out) ; + + // Set back _subCC to K + _subCC = _KCC ; + + return out ; + } + + /** + * \brief Prints the homology and cohomology reduction information for the current sub chain complex. + * + * Writes \f$f^*\f$, \f$g\f$ \f$\partial'\f$ the reduced boundary over each critical cell. + * + * By default, outputs the complex to `std::cout`. + */ + std::ostream& write_reduction_sub(std::ostream& out = std::cout) // const; + { + // Print critical cells + out << "----- critical cells:" << std::endl; + for (int q = 0; q <= _L.dimension(); ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < _L.number_of_cells(q); ++i) + { + if ((this->_flag[q][i] == CRITICAL) && (_subCC.get_bit(q, i))) { + out << i << " "; + } + } + out << std::endl; + } + + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) + { + // Print matrices g + out << "----- g:" << std::endl; + for (int q = 0; q <= _L.dimension(); ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < _L.number_of_cells(q); ++i) { + if ((this->_flag[q][i] == CRITICAL) && (_subCC.get_bit(q, i))) { + out << "g(" << i << ") = (" << i << ")"; + // Iterate over the ith column of _G_col + typename Base::Column_chain col(OSM::get_column(this->_G_col.at(q), i)) ; // TODO cget + for (typename Base::Column_chain::const_iterator it_col = col.cbegin(); it_col != col.cend(); ++it_col) { + out << " + " << it_col->second << ".(" << it_col->first << ") + "; + } + out << std::endl; + } + } + } + } + + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) + { + // Print matrices f* + out << "----- f*:" << std::endl; + for (int q = 0; q <= _L.dimension(); ++q) { + out << "--- dim " << q << std::endl; + for (size_t i = 0; i < _L.number_of_cells(q); ++i) { + if ((this->_flag[q][i] == CRITICAL) && (_subCC.get_bit(q, i))) { + out << "f*(" << i << ") = (" << i << ")"; + // Iterate over the ith row of _F_row + typename Base::Row_chain row(OSM::get_row(this->_F_row.at(q), i)) ; // TODO cget + for (typename Base::Row_chain::const_iterator it_row = row.cbegin(); it_row != row.cend(); ++it_row) { + out << " + " << it_row->second << ".(" << it_row->first << ") + "; + } + out << std::endl; + } + } + } + } + return out ; + } + + /* + * \brief Writes a `Hdvf_duality` together with the associated reduction (f, g, h, d matrices) + * + * Exports a `HDVF_duality` to a stream in `hdvf` file format (a simple text file format, see for a specification). + * + * \param out Output stream. + */ + std::ostream& write_hdvf_reduction(std::ostream& out) const ; + + /* + * \brief Writes a `Hdvf_duality` together with the associated reduction to a file (f, g, h, d matrices). + * + * Exports a `Hdvf_duality` to a file in `hdvf` file format (a simple text file format, see for a specification). + * + * \param filename Output file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void write_hdvf_reduction(std::string filename) const { + std::ofstream out_file ( filename, std::ios::out | std::ios::trunc); + if ( ! out_file . good () ) { + std::cerr << "Out fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + write_hdvf_reduction(out_file) ; + + out_file.close(); + } + + /* + * \brief Reads a `Hdvf_duality` together with the associated reduction (f, g, h, d matrices) + * + * Imports a `Hdvf_duality` from a stream in `hdvf` file format (a simple text file format, see for a specification). + * + * \param in Input stream. + */ + std::istream& read_hdvf_reduction(std::istream& in) ; + + /* + * \brief Loads a `Hdvf_duality` together with the associated reduction from a file (f, g, h, d matrices) + * + * Imports a `Hdvf_duality` and its reduction from a file in `hdvf` file format, a simple text file format (see for a specification). + * \warning The underlying complex is not stored in the file! + * + * \param filename Input file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void read_hdvf_reduction(std::string filename) { + std::ifstream in_file (filename); + if ( ! in_file . good () ) { + std::cerr << "Out fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + read_hdvf_reduction(in_file); + + in_file.close(); + } + + + + /** + * \brief Prints the reduced boundary over critical cells of `K` and `L-K`. + * + * The method prints out the reduced boundary matrix in each dimension, restricted to critical cells of `K` and `L-K` (ie.\ the matrix used to compute Alexander pairing). + * + * \warning Call this method after `compute_perfect_hdvf()`. + * + * By default, outputs the complex to `std::cout`. + */ + std::ostream& print_bnd_pairing(std::ostream& out = std::cout) + { + Sub_chain_complex_mask subPair(_L, false) ; + for (int q=0; q<=_L.dimension(); ++q) + { + for (size_t i=0; i<_critical_K.at(q).size(); ++i) + subPair.set_bit_on(q, _critical_K.at(q).at(i)) ; + for (size_t i=0; i<_critical_L_K.at(q).size(); ++i) + subPair.set_bit_on(q, _critical_L_K.at(q).at(i)) ; + } + // Print corresponding submatrices _DD_col + for (int q=1; q<=_L.dimension(); ++q) + { + out << "--> dim " << q << " : q / q-1 cells" << std::endl ; + out << "id " << q << " : " ; + for (typename OSM::Bitboard::iterator it = subPair.get_bitboard(q).begin(); it != subPair.get_bitboard(q).end(); ++it) + out << *it << " " ; + out << std::endl ; + out << "id " << q-1 << " : " ; + for (typename OSM::Bitboard::iterator it = subPair.get_bitboard(q-1).begin(); it != subPair.get_bitboard(q-1).end(); ++it) + out << *it << " " ; + out << std::endl ; + for (typename OSM::Bitboard::iterator it = subPair.get_bitboard(q).begin(); it != subPair.get_bitboard(q).end(); ++it) + { + for (typename OSM::Bitboard::iterator it2 = subPair.get_bitboard(q-1).begin(); it2 != subPair.get_bitboard(q-1).end(); ++it2) + { + if (this->_DD_col.at(q).get_coefficient(*it2, *it) == 0) + out << ".\t" ; + else + out << this->_DD_col.at(q).get_coefficient(*it2, *it) << "\t" ; + } + out << std::endl ; + } + } + return out ; + } + + /** + * \brief Exports primary/secondary/critical labels *of the current sub chain complex* for vtk export. + * + * The method exports the labels of every cells in each dimension. Exports the label 2 (ie. NONE) for cells out of the current sub chain complex. + * + * \return A vector containing, for each dimension, the vector of labels by cell index. + */ + std::vector > psc_labels () const + { + std::vector > labels(this->_K.dimension()+1) ; + for (int q=0; q<=this->_K.dimension(); ++q) + { + for (size_t i = 0; i_K.number_of_cells(q); ++i) + { + if (_subCC.get_bit(q, i)) // i belongs to _subCC + { + if (this->_flag.at(q).at(i) == PRIMARY) + labels.at(q).push_back(-1) ; + else if (this->_flag.at(q).at(i) == SECONDARY) + labels.at(q).push_back(1) ; + else + labels.at(q).push_back(0) ; + } + else // i does not belongs to _subCC + labels.at(q).push_back(2) ; + } + } + return labels ; + } + + /** + * \brief Exports the homology generator *of the current sub chain complex* associated to `cell_index` (critical cell) of dimension `q`. + * + * The method exports the chain \f$g(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`. + * + * \return A column-major chain. + */ + Column_chain homology_chain (size_t cell_index, int dim) const + { + if ((dim<0) || (dim>this->_K.dimension())) + throw "Error : homology_chain with dim out of range" ; + if (!_subCC.get_bit(dim, cell_index)) + throw "Error : homology_chain for a cell out of current sub chain complex" ; + + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) + { + // Get g(cell, dim) with per indices + Column_chain g_cell(OSM::get_column(this->_G_col.at(dim), cell_index)) ; + // Add 1 to the cell + g_cell.set_coefficient(cell_index, 1) ; + // Keep cells of the chain belonging to _subCC + Column_chain g_cell_sub(g_cell.dimension()) ; + for (typename Column_chain::const_iterator it = g_cell.begin(); it != g_cell.end(); ++it) + { + + if (_subCC.get_bit(dim, it->first)) + { + g_cell_sub.set_coefficient(it->first, it->second) ; + } + } + return g_cell_sub ; + } + else + throw "Error : trying to export g_chain without proper HDVF option" ; + } + + /** + * \brief Exports the cohomology generator *of the current sub chain complex* associated to `cell_index` (critical cell) of dimension `q`. + * + * The method exports the chain \f$f^\star(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`. + * + * \return A column-major chain. + */ + Column_chain cohomology_chain (size_t cell_index, int dim) const + { + if ((dim<0) || (dim>this->_K.dimension())) + throw "Error : cohomology_chain with dim out of range" ; + if (!_subCC.get_bit(dim, cell_index)) + throw "Error : cohomology_chain for a cell out of current sub chain complex" ; + + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) + { + Row_chain fstar_cell(OSM::get_row(this->_F_row.at(dim), cell_index)) ; + // Add 1 to the cell + fstar_cell.set_coefficient(cell_index, 1) ; + + // Keep cells of the chain belonging to _subCC + Column_chain fstar_cell_sub(fstar_cell.dimension()) ; + for (typename Column_chain::const_iterator it = fstar_cell.begin(); it != fstar_cell.end(); ++it) + { + + if (_subCC.get_bit(dim, it->first)) + { + fstar_cell_sub.set_coefficient(it->first, it->second) ; + } + } + return fstar_cell_sub ; + } + } + + /* \brief Compares the `HDVF_duality` with another `HDVF_duality` over the same underlying complex and sub-complex. + * + * \param other Other `HDVF_duality` to compare. + * \param full_compare Turns on "in depth" HDVF comparison (reduction matrices). + */ + + bool compare(const Hdvf_duality& other, bool full_compare = false) const { + // Compare Hdvf_core structure of *this and other + bool res(Base::compare(other, full_compare)); + if (!res) + return res; + // Compare duality data + res = (_KCC == other._KCC); + res = res && (_critical_K == other._critical_K); + res = res && (_critical_L_K == other._critical_L_K); + return res; + } +} ; + +// Constructor +template +Hdvf_duality::Hdvf_duality(const ChainComplex& L, Sub_chain_complex_mask& K, int hdvf_opt) : +Hdvf_core(L,hdvf_opt), _L(L), _KCC(K), _subCC(K) { + // Check if the complex is non empty + size_t acc(L.number_of_cells(0)); + for (int q=1; q +Cell_pair Hdvf_duality::find_pair_A(int q, bool &found) const +{ + found = false; + Cell_pair p; + + // Iterate through columns of _DD_col[q+1] + for (OSM::Bitboard::iterator it_col = this->_DD_col[q+1].begin(); (it_col != this->_DD_col[q+1].end() && !found); ++it_col) + { + const typename Base::Column_chain& col(OSM::cget_column(this->_DD_col[q+1], *it_col)) ; + + // Iterate through the entries of the column + // Check that the row belongs to the subchaincomplex + for (typename Base::Column_chain::const_iterator it = col.begin(); (it != col.end() && !found); ++it) { + if (_subCC.get_bit(q, it->first) && (is_invertible(it->second))) { + // If an entry with coefficient 1 or -1 is found, set the pair and mark as found + p.sigma = it->first; + p.tau = *it_col; + p.dim = q; + found = true; + } + } + } + return p; +} + +// find a valid Cell_pair containing tau for A in dimension q +template +Cell_pair Hdvf_duality::find_pair_A(int q, size_t tau, bool &found) const +{ + found = false; + Cell_pair p ; + // Check tau belongs to _subCC + if (!_subCC.get_bit(q, tau)) + throw("Hdvf_duality: searching for a cell tau outside _subCC") ; + + // Search for a q-1 cell tau' such that <_d(tau),tau'> invertible + // and tau' belongs to _subCC + const typename Base::Column_chain& tmp2(OSM::cget_column(this->_DD_col.at(q), tau)) ; + for (typename Base::Column_chain::const_iterator it = tmp2.cbegin(); (it != tmp2.cend() && !found); ++it) + { + if (_subCC.get_bit(q-1, it->first) && is_invertible(it->second)) + { + found = true ; + p.sigma = it->first ; + p.tau = tau ; + p.dim = q-1 ; + } + } + + // Search for a q+1 cell tau' such that <_d(tau'),tau> invertible, ie <_cod(tau),tau'> invertible + // and tau' belongs to _subCC + typename Base::Row_chain tmp(OSM::get_row(this->_DD_col.at(q+1), tau)) ; + for (typename Base::Row_chain::const_iterator it = tmp.cbegin(); (it != tmp.cend() && !found); ++it) + { + if (_subCC.get_bit(q+1, it->first) && (abs(it->second) == 1)) + { + found = true ; + Cell_pair p ; + p.sigma = tau ; + p.tau = it->first ; + p.dim = q ; + } + } + return p; +} + +// find all the valid Cell_pair for A in dimension q +template +std::vector Hdvf_duality::find_pairs_A(int q, bool &found) const +{ + std::vector pairs; + found = false ; + + // Iterate through columns of _DD_col[q+1] + for (OSM::Bitboard::iterator it_col = this->_DD_col[q+1].begin(); it_col != this->_DD_col[q+1].end(); ++it_col) + { + const typename Base::Column_chain& col(OSM::cget_column(this->_DD_col[q+1], *it_col)) ; + + // Iterate through the entries of the column + for (typename Base::Column_chain::const_iterator it = col.begin(); it != col.end(); ++it) { + if (_subCC.get_bit(q, it->first) && (is_invertible(it->second))) { + // If an entry of _subCC with coefficient 1 or -1 is found, set the pair and mark as found + Cell_pair p; + p.sigma = it->first; + p.tau = *it_col; + p.dim = q; + pairs.push_back(p) ; + found = true; + } + } + } + return pairs; +} + +// find all the valid Cell_pair containing tau for A in dimension q +template +std::vector Hdvf_duality::find_pairs_A(int q, size_t tau, bool &found) const +{ + found = false; + std::vector pairs; + // Check if tau belongs to _subCC + if (!_subCC.get_bit(q, tau)) + throw("Hdvf_duality: searching for a cell tau outside _subCC") ; + + // Search for a q+1 cell tau' such that <_d(tau'),tau> invertible, ie <_cod(tau),tau'> invertible + // and tau' belongs to _subCC + typename Base::Row_chain tmp(OSM::get_row(this->_DD_col.at(q+1), tau)) ; + for (typename Base::Row_chain::const_iterator it = tmp.cbegin(); it != tmp.cend(); ++it) + { + if (_subCC.get_bit(q+1, it->first) && (is_invertible(it->second))) + { + found = true ; + Cell_pair p ; + p.sigma = tau ; + p.tau = it->first ; + p.dim = q ; + pairs.push_back(p) ; + } + } + // Search for a q-1 cell tau' such that <_d(tau),tau'> invertible + // and tau' belongs to _subCC + const typename Base::Column_chain& tmp2(OSM::cget_column(this->_DD_col.at(q), tau)) ; + for (typename Base::Column_chain::const_iterator it = tmp2.cbegin(); it != tmp2.cend(); ++it) + { + if (_subCC.get_bit(q-1, it->first) && (abs(it->second) == 1)) + { + found = true ; + Cell_pair p ; + p.sigma = it->first ; + p.tau = tau ; + p.dim = q-1 ; + pairs.push_back(p) ; + } + } + return pairs; +} + +// Compute dual perfect HDVFs (over K and L-K) +template +std::vector Hdvf_duality::compute_perfect_hdvf(bool verbose) +{ +#ifdef DEBUG_HDVF_DUALITY + std::cout << std::endl << "==== Compute perfect HDVF over K" << std::endl ; +#endif + // Set _subCC to K + _subCC = _KCC ; + // Restrict _DD_col accordingly + _subCC.screen_matrices(this->_DD_col); + // Compute perfect HDVF over K + std::vector tmp = Base::compute_perfect_hdvf(verbose) ; +#ifdef DEBUG_HDVF_DUALITY + std::cout << tmp.size() << " cells paired" << std::endl ; +#endif + _critical_K = psc_flag(CRITICAL) ; + +#ifdef DEBUG_HDVF_DUALITY + std::cout << std::endl << "==== Compute perfect HDVF over L-K" << std::endl ; +#endif + // set _subCC to L-K + _subCC = _KCC.complement() ; + // Restrict _DD_col accordingly + _subCC.screen_matrices(this->_DD_col); + // Compute perfect HDVF over L-K + std::vector tmp2 = Base::compute_perfect_hdvf(verbose) ; +#ifdef DEBUG_HDVF_DUALITY + std::cout << tmp2.size() << " cells paired" << std::endl ; +#endif + _critical_L_K = psc_flag(CRITICAL) ; + + // Return the vector of paired cells + tmp.insert(tmp.end(), tmp2.begin(), tmp2.end()); + return tmp; +} + +// Compute random dual perfect HDVFs (over K and L-K) +template +std::vector Hdvf_duality::compute_rand_perfect_hdvf(bool verbose) +{ +#ifdef DEBUG_HDVF_DUALITY + std::cout << std::endl << "==== Compute perfect HDVF over K" << std::endl ; +#endif + // Set _subCC to K + _subCC = _KCC ; + // Restrict _DD_col accordingly + _subCC.screen_matrices(this->_DD_col); + // Compute perfect HDVF over K + std::vector tmp = Base::compute_rand_perfect_hdvf(verbose) ; +#ifdef DEBUG_HDVF_DUALITY + std::cout << tmp.size() << " cells paired" << std::endl ; +#endif + _critical_K = psc_flag(CRITICAL) ; + +#ifdef DEBUG_HDVF_DUALITY + std::cout << std::endl << "==== Compute perfect HDVF over L-K" << std::endl ; +#endif + // set _subCC to L-K + _subCC = _KCC.complement() ; + // Restrict _DD_col accordingly + _subCC.screen_matrices(this->_DD_col); + // Compute perfect HDVF over L-K + std::vector tmp2 = Base::compute_rand_perfect_hdvf(verbose) ; +#ifdef DEBUG_HDVF_DUALITY + std::cout << tmp2.size() << " cells paired" << std::endl ; +#endif + _critical_L_K = psc_flag(CRITICAL) ; + + // Return the vector of paired cells + tmp.insert(tmp.end(), tmp2.begin(), tmp2.end()); + return tmp; +} + +// Compute Alexander isomorphism (A pairing between critical cells of K / L-K) +template +std::vector Hdvf_duality::compute_pairing_hdvf() +{ +#ifdef DEBUG_HDVF_DUALITY + std::cout << std::endl << "==== Compute pairing" << std::endl ; +#endif + + // Copy the HDVF before computing the pairing -> otherwise we loose it... + Hdvf_duality hdvf_tmp(*this); + + if (!is_perfect_hdvf()) { + std::cerr << "Cannot compute duality pairing: HDVFs over K or L-K not perfect" << std::endl; + throw(std::runtime_error("Cannot compute duality pairing: HDVFs over K or L-K not perfect")); + } + + // Create a full Sub_chain_complex_mask + hdvf_tmp._subCC = Sub_chain_complex_mask(hdvf_tmp._L) ; + hdvf_tmp._subCC.screen_matrices(this->_DD_col); + + std::vector pairing = hdvf_tmp.Base::compute_perfect_hdvf() ; + for (Cell_pair p : pairing) { + std::cout << p.sigma << " : " << _KCC.get_bit(p.dim, p.sigma) << " / " << p.tau << " : " << _KCC.get_bit(p.dim+1, p.tau) << std::endl; + } + return pairing ; +} + +// Compute random Alexander isomorphism (A pairing between critical cells of K / L-K) +template +std::vector Hdvf_duality::compute_rand_pairing_hdvf() +{ + // Copy the HDVF before computing the pairing -> otherwise we loose it... + Hdvf_duality hdvf_tmp(*this); + + if (!is_perfect_hdvf()) { + std::cerr << "Cannot compute duality pairing: HDVFs over K or L-K not perfect" << std::endl; + throw(std::runtime_error("Cannot compute duality pairing: HDVFs over K or L-K not perfect")); + } + +#ifdef DEBUG_HDVF_DUALITY + std::cout << std::endl << "==== Compute pairing" << std::endl ; +#endif + + // Create a full Sub_chain_complex_mask + hdvf_tmp._subCC = Sub_chain_complex_mask(hdvf_tmp._L) ; + hdvf_tmp._subCC.screen_matrices(this->_DD_col); + // If necessary, copy the HDVF before computing the pairing -> otherwise we loose it... + std::vector pairing = hdvf_tmp.Base::compute_rand_perfect_hdvf() ; + return pairing ; +} + +// Method to get cells of _subCC with a given PSC_flag for each dimension +template +std::vector > Hdvf_duality::psc_flag (PSC_flag flag) const +{ + std::vector > res(_L.dimension()+1) ; + for (int q=0; q<=_L.dimension(); ++q) + { + for (size_t i=0; i<_L.number_of_cells(q); ++i) + { + if (_subCC.get_bit(q, i) && (this->_flag.at(q).at(i) == flag)) + res.at(q).push_back(i) ; + } + } + return res ; +} + +// Method to get cells of _subCC with a given PSC_flag for a given dimension +template +std::vector Hdvf_duality::psc_flag (PSC_flag flag, int q) const +{ + std::vector res ; + for (size_t i=0; i_K.number_of_cells(q); ++i) + { + if (_subCC.get_bit(q, i) && (this->_flag.at(q).at(i) == flag)) + res.push_back(i) ; + } + return res ; +} + +// Save HDVF and reduction +template +std::ostream& Hdvf_duality::write_hdvf_reduction(std::ostream& out) const { + // Write HDVF prefix type + out << "#HDVF_duality" << std::endl; + // Call parent method main code + Base::write_hdvf_reduction_main(out); + // Write duality data + // ---- _KCC + for (int q=0; q<=this->_K.dimension(); ++q) { + out << _KCC.get_bitboard(q) << std::endl; + } + return out; +} + +// Save HDVF and reduction +template +std::istream& Hdvf_duality::read_hdvf_reduction(std::istream& in) { + // Load and check HDVF prefix + // For Hdvf_core: #HDVF_core + // For Hdvf_persistence: #HDVF_persistence + // For Hdvf_duality: #HDVF_duality + std::string prefix; + in >> prefix; + if (prefix.compare("#HDVF_duality")) { + std::cerr << "try to load a HDVF with incompatible prefix" << std::endl ; + throw (std::runtime_error("try to load a HDVF with incompatible prefix")); + } + + // Call parent method + Hdvf_core::read_hdvf_reduction_main(in); + // Read duality data + // ---- _KCC + // Init bitboards _KCC + for (int q=0; q<=this->_K.dimension(); ++q) { + OSM::Bitboard bb(this->_K.number_of_cells(q)); + in >> bb; + _KCC.set_bitboard(bb,q); + } + // Restore _critical_K and _critical_L_K + _subCC.set_full(); + _critical_K.clear(); + _critical_L_K.clear(); + _critical_K.resize(this->_K.dimension()+1); + _critical_L_K.resize(this->_K.dimension()+1); + for (int q=0; q<=this->_K.dimension(); ++q) { + std::vector criticals = this->psc_flaghd(CRITICAL,q); + for (size_t cell : criticals) { + if (_KCC.get_bit(q,cell)) // cell is critical in _K + _critical_K.at(q).push_back(cell); + else + _critical_L_K.at(q).push_back(cell); + } + } + _subCC = _KCC; + return in; +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_HDVF_DUALITY_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_persistence.h b/HDVF/include/CGAL/HDVF/Hdvf_persistence.h new file mode 100644 index 000000000000..17edb584faa9 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_persistence.h @@ -0,0 +1,1131 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_HDVF_PERSISTENCE_H +#define CGAL_HDVF_HDVF_PERSISTENCE_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +// Types for persistent homology / cohomology + + + +/*! + \ingroup PkgHDVFHDVFClasses + + The class `Hdvf_persistence` computes persistent homology using HDVFs. + + \warning The ring of coefficients provided should be a **field**. + + Unlike other persistence computation libraries, beside standard persistent intervals informations (birth/death indices, degrees, associated cells), `Hdvf_persistence` also provides **homology and cohomology generators for persistent pairs** (see \ref secHDVFpersistentHom_package "user manual" for details, including the link to cascades). Intuitively, holes die when they are "filled" by a cell: associated homology and cohomology generators provide a representation of the hole and of the cells filling the hole. + + Given a `Filtration`, the `Hdvf_persistence` class computes persistent homology as a perfect HDVF adapted to the filtration (through the `compute_perfect_hdvf()` method). If the reduction \f$g\f$ map is computed (depending the the computation option), HDVFs also provide persistent homology generators, together with the corresponding "filling" chains. + + \cgalModels{HDVF} + + \tparam ChainComplex a model of the `AbstractChainComplex` concept, providing the type of abstract chain complex used (the underlying ring of coefficients must be a model of `Field`). The sparse matrix provided by `ChainComplex` must be an instance of the `Sub_sparse_matrix_structure` template. + \tparam Degree a scalar data type used for the degrees of the filtration (a model of `RealEmbeddable` concept). + \tparam Filtration_ a model of the `Filtration` concept, providing the filtration used to compute persistence. + +\warning + - The sparse matrix provided by `ChainComplex` must be an instance of the `Sub_sparse_matrix_structure` template. + - For persistent homology computation, the selection of cells paired by the `A()` operation is totally determined by the filtration. Hence, the `find_pair_A()` family of functions and `A()` operation are private to `Hdvf_persistence`. + + */ + +//constructor basically builds a HDVF where indices of cells in the bases follow the filtration order (permutations between initial indices of cells in the chain complex and new indices given by the filtration are stored). +//Besides, `Hdvf_persistence` derives from `Hdvf_core` with the SparseMatrix parameter type set to `OSM::Sub_sparse_matrix_structure`. Hence, `Hdvf_persistence` computes homology over a larger and larger sub-complex of `K` (encoded through a Bitboard mask of `OSM::Sub_sparse_matrix_structure`) following the filtration `f`. At each step of the filtration, the new cell is paired (A operation) with the youngest cell valid for A. +// +//A few words about persistent generators. At a given time \f$t_2\f$ along the filtration, let us denote by \f$\tau\f$ the \f$q+1\f$-cell inserted and let us assume that persistent homology computation pairs \f$A(\sigma, \tau)\f$ (let \f$t_1\f$ be the index of \f$\sigma\f$ along the filtration). A new persistent interval \f$(t_1, t_2)\f$is created, generated by \f$\sigma\f$ and filled by \f$\tau\f$. +//Reduction at time \f$t_2-1\f$ (just before the insertion of \f$\tau\f$) provides persistent generators: \f$g(\sigma)\f$ is a cycle and \f$g(\tau)\f$ is a chain "filling" this cycle (before the pairing \f$A(\sigma, \tau)\f$, the HDVF over \f$K_{t_2}\f$ is not perfect, thus \f$g(\tau)\f$ is not a cycle but a chain bounded by \f$q\f$-cycles). +// +//Next figures illustrate such informations for a filtration of a dented double torus. +// +// +// +//Filtration `Filtration_lower_star` along the z axis of a dented double torus. +// +// +// +// +// +//The first persistent hole is a dimension 1 hole with a duration of 0.359 along z. +//- (Left) the complex at time 622 along the filtration (in blue, the 2-cell \f$\tau\f$ added at time 623). This 1-homology of this complex contains two cycles depicted in white and yellow respectively. Yellow curve: a 1-cycle born at time 319 (with cell \f$\sigma\f$) along the filtration. White curve: the second 1-cycle of this complex (born at time 156 along the filtration). +//- (Middle) the filtration adds the (blue) 2-cell \f$\tau\f$ at time 623: this cell fills one the 1-cycles (actually the "youngest" cycle, thus the yellow one). Middle image illustrates as a yellow patch the "filling 2-chain", a chain a 2-cells filling the hole when the cell \f$\tau\f$ is added. As stated earlier, this filling chain is not a cycle and it equals \f$g(\tau)\f$ in the (non perfect) reduction before the pairing `A(319, 623)` between \f$\sigma\f$ and \f$\tau\f$. Middle figure illustrates that in the (non perfect) HDVF before A operation, \f$g(\tau)\f$ is not a cycle but a chain bounded by \f$q\f$-cycles. +//- (Right) the complex after insertion of \f$\tau\f$ and the pairing `A(319, 623)` between \f$\sigma\f$ and \f$\tau\f$. Only the white 1-cycle remains (actually its persistent lifetime is infinite). +// +// +// +//The figure above illustrates next persistent hole (dimension 0 hole) which duration is 0.684 along z. The cell \f$\tau\f$ is added at time 1237 and fills the 0-hole (connected component) introduced at time 756 by the cell \f$\sigma\f$. The persistent generator associated to \f$\sigma\f$ is simply the cell itself identifying its connected component. The persistent generator associated to \f$\tau\f$ is a 1-chain (depicted in yellow) connecting \f$\sigma\f$ to the critical cell identifying the second connected component. +// +// +// +// +// +//Figures above illustrate next finite persistent intervals and (right) infinite dimension 1 persistent intervals. + +template +class Hdvf_persistence : public Hdvf_core +{ +public: + /*! \brief Chain complex type */ + typedef ChainComplex Chain_complex; + + /*! \brief Type of coefficients used to compute homology. */ + typedef typename Chain_complex::Coefficient_ring Coefficient_ring; + + /*! Type of parent HDVF class (`Hdvf_core` with appropriate template parameters) + * The `SparseMatrix` model is set to `Sub_sparse_matrix_structure` to activate (co)homology computation over a subcomplex. + * + * @todo + * Check that the SparseMatrix provided by ChainComplex is a Sub_sparse_matrix_structure (and not a generic Sparse_matrix_structure). + */ + typedef Hdvf_core Base ; + + /*! \brief Structure storing (full) persistent interval data: + * - `time_birth`, `time_death`: persistent interval filtration indices + * - `cell_birth`, `cell_death`: persistent interval cells + * - `degree_birth`, `degree_death`: persistent interval degrees + * + * Infinite intervals are encoded by setting birth data = death data (time, degree and cell). + */ + struct Persistence_interval { + size_t time_birth, time_death; + Degree degree_birth, degree_death; + Cell cell_birth, cell_death; + + /** \brief Computes the (degree) duration of a persistent interval (i.e.\ persistent hole) + * + * By definitions of "default" values, infinite intervals have a duration of -1. + */ + inline Degree duration () const + { + // Test if the interval is infinite + if (time_birth == time_death) + return -1 ; + else + return degree_death-degree_birth; + } + + /** + * \brief Output the persistence interval to a stream. + */ + inline std::ostream& insert (std::ostream& out_stream) const + { + if (time_death != time_birth) // finite interval + { + out_stream << "[" << time_birth << " (" << cell_birth.first << ", " << cell_birth.second << ") -> " ; + out_stream << time_death << " (" << cell_death.first << ", " << cell_death.second << ") / duration: " << duration() << "]" << std::endl ; + } + else + { + out_stream << "[" << time_birth << " (" << cell_birth.first << ", " << cell_birth.second << ") -> " ; + out_stream << "inf]" << std::endl ; + } + return out_stream ; + } + + /** \brief Comparison operator. + * + * Compares with `other` `Persistence_interval` (for numerical stability, only integer `time_birth` and `time_death` are compared. Corresponding degrees are equal as soon as both `Persistence_interval` relate to the same filtration. + */ + inline bool operator== (const Persistence_interval& other) const { + return (time_birth == other.time_birth) && + (time_death == other.time_death); + } + }; + + // Inherited types + /* + Type of column-major chains + */ + typedef typename Base::Column_chain Column_chain; + + /* + Type of row-major chains + */ + typedef typename Base::Row_chain Row_chain; + + /* + Type of column-major sparse matrices + */ + typedef typename Base::Column_matrix Column_matrix; + + /* + Type of row-major sparse matrices + */ + typedef typename Base::Row_matrix Row_matrix; + + /*! Type of filtrations used to compute persistence. + */ + typedef Filtration_ Filtration; + + /*! \brief Hole information returned by the persistent diagram iterator. + * + * Information comprises: + * - `Persistence_interval` providing informations related to the "times" of the persistent interval (index, degree and cell of birth/death). + * - `labelsPSC` storing HDVF flags at death time of the hole + */ + typedef struct { + Persistence_interval hole ; + std::vector > labelsPSC ; + Column_chain homology_chain_birth, homology_chain_death, cohomology_chain_birth, cohomology_chain_death ; + } Persistent_hole ; + +protected: + /* \brief Reference to the filtration used for persistence */ + const Filtration &_f ; + + /* \brief Permutation between indices in the chain complex `K` and indices along the filtration + * Indices along the filtration provide new indices for cells in each dimension. + */ + std::vector > _K_to_per, _per_to_K ; + + /* \brief Vector of persistent pairs computed */ + std::vector _persist ; + + /* \brief Boolean determining whether or not export homology/cohomology generators associated to persistent pairs + * - If `_with_export` is `true`, PSC labels and homology/cohomology generators are stored for each persistent pair of duration (that is, such as the difference between degrees of birth/death) strictly positive. + * - If `_with_export` is `false`, only persistent intervals are stored. + */ + bool _with_export ; + + /* \brief Vector of exported PSC labels */ + std::vector >> _export_labels ; + + /* \brief Vector of exported homology/cohomology generators */ + std::vector > _export_g, _export_fstar ; + + /* Warning: + * In order to encode "time" as an unsigned int while the initial value is -1, + * we store current times + 1 + */ + + /* Current time + 1 (that is, current index + 1) in the filtration */ + size_t _t ; + /* Current time +1 (that is, current index + 1) along each dimension */ + std::vector _t_dim ; + + /* Bitboard masks in each dimension + * At a given filtration time, only cells already met have a bit set to 1 + */ + std::vector _masks ; + + +private: + // Hide find_pair_A methods of the Hdvf_core class + // Hide A operation of the Hdvf_core class + // This operation is redefined with a different prototype in Hdvf_persistence and set as private since persistence lets no choice for A pairing (rule of the "youngest") + using Base::find_pair_A; + using Base::find_pairs_A; + using Base::A; + +public: + /** + * \brief Hdvf_persistence default constructor + * + * Builds an "empty" HDVF_persistence (with all cells critical) associated to the chain complex `K` and the filtration `f`. + * By default, the HDVF option is set to OPT_FULL (full reduction computed). + * + * If the `with_export`parameter is set to `true`, homology and cohomology generators of persistent pairs, together with the corresponding HDVFs (see \ref secHDVFpersistentHom_package "user manual" for details) are stored along the computation; they can then be visited via the persistent pairs iterator. + * + * \param K A chain complex (a model of `AbstractChainComplex`). + * \param f A filtration (a model of `Filtration`). + * \param hdvf_opt Option for HDVF computation (`OPT_BND`, `OPT_F`, `OPT_G` or `OPT_FULL`) + * \param with_export Boolean option to activate or not the storage of the `Persistent_hole` informations (PSC labels and homology/cohomology generators) for persistent intervals of positive duration. This information is required for vtk exporters. + * \param dimension_restriction Determines if persistence is computed along any dimensions (if `dimension_restriction` is -1) or a single dimension (specified by `dimension_restrictions`) + * + * \exception Empty_complex If the complex `K` is empty, raises a `%std::runtime_error`. + */ + Hdvf_persistence(const Chain_complex& K, const Filtration& f, int hdvf_opt = OPT_BND, bool with_export = false, int dimension_restriction = -1) ; + + /** + * \brief Computes a perfect persistent HDVF. + * + * This method follows the filtration and considers cells one by one. For each of them, it searches the youngest possible cell valid for `A()` and applies the corresponding operation. + * By definition of persistent homology, the `IntegralDomainWithoutDivision` of coefficients *must be* a `Field`. + * + * If `dimension_restriction` is a positive parameter (in the HDVF), then persistence is computed only in dimension `dimension_restriction`. + * + * \param verbose If this parameter is `true`, all intermediate reductions are printed out. + * + * \returns The vector of all `Cell_pair` paired with A. + */ + std::vector compute_perfect_hdvf(bool verbose = false) + { + int dim = this->_K.dimension(); // Get the dimension of the complex K + + int min_dim, max_dim; + + // Compute min and max dimensions of find_pair_A + if (this->_dimension_restriction == -1) { + min_dim = 0; + max_dim = dim; + } + else { + min_dim = this->_dimension_restriction; + max_dim = this->_dimension_restriction; + } + + bool found; + Cell_pair pair; + std::vector res ; + for (size_t i=0; i < _f._filtration.size(); ++i) + { + this->progress_bar(i, _f._filtration.size()) ; + found = false ; + pair = step_persist(found, verbose) ; + if (found) + res.push_back(pair) ; + } + + // Compute "infinite" holes + std::vector > criticals(this->psc_flag(CRITICAL)) ; + for (int q=min_dim; q <= max_dim; ++q) + { + for (size_t i : criticals.at(q)) + { + // i : persistence index + // encoding of the second "infinite" cell: + // -> equals the first cell (i.e. birth = death) + const Cell_pair p = {i, this->_K.number_of_cells(q+1), q} ; + const size_t ki(_per_to_K.at(q).at(i)) ; // K index + const Cell c(ki,q) ; + const size_t ti(_f._cell_to_t.at(c)) ; + const Degree di(_f._deg.at(i)) ; + + Persistence_interval hole; + hole.time_birth = ti; + hole.time_death = ti; + hole.degree_birth = di; + hole.degree_death = di; + hole.cell_birth = c; + hole.cell_death = c; + + _persist.push_back(hole) ; + + // If export is on, store export data + if (_with_export) + export_hdvf_persistence_pair(p) ; + } + } + return res; + } + + /** \brief Gets the "with_export" %Boolean flag. + * If the flag is `true`, homology/cohomology generators and corresponding PSC labels are exported for each persistent interval of positive duration. + */ + bool with_export () const { return _with_export ; } + + /** \brief Get a constant reference on the filtration + */ + const Filtration& filtration() const { return _f; } + + /** \brief Overload of `operator<<()` for `Hdvf_persistence`. + * + * Prints out finite and infinite persistence intervals in a user friendly way. + * + * \param out_stream Reference to an output stream. + * \param per_hdvf Constant reference on the `Hdvf_persistence` to print. + */ + friend std::ostream& operator<< (std::ostream& out_stream, const Hdvf_persistence& per_hdvf) + { + size_t i = 0 ; + for (Persistence_interval hole : per_hdvf._persist) + { + if (std::abs(hole.duration()) > 0) + { + out_stream << i << " --- duration : " << hole.duration() << " -- " ; + hole.insert(out_stream); + out_stream << std::endl ; + } + ++i ; + } + return out_stream ; + } + + /** \brief Prints informations related to the filtration. + * + * Prints out the filtration and associated permutations (`_K_to_per` and `_per_to_K`) between indices of cells in each dimension in the basis of `K` and indices along the filtration. + * + * \param out Reference to an output stream. + */ + std::ostream& print_hdvf_persistence_information (std::ostream& out) const + { + out << "Filtration: " << _f << std::endl ; + out << "_K_to_per and _per_to_K" << std::endl ; + for (int q=0; q<=this->_K.dimension(); ++q) + { + out << "-> dim " << q << std::endl ; + out << "index_per -(_per_to_K)-> index_K -(_K_to_per)-> index_per" << std::endl ; + for (size_t i=0; i_K.number_of_cells(q); ++i) + { + const size_t id_K(_per_to_K.at(q).at(i)) ; + out << i << " -> " << id_K << " -> " << _K_to_per.at(q).at(id_K) << std::endl ; + } + } + return out ; + } + + + /* + * \brief Exports primary/secondary/critical labels to integers (e.g.\ for vtk export). + * + * The method exports the labels of every cell in each dimension. + * Encoding used: + * - `PRIMARY`: -1 + * - `SECONDARY`: +1 + * - `CRITICAL`: 0 + * + * \returns A vector containing, for each dimension, the vector of integers encoding labels by cell index. + */ + std::vector > psc_labels () const + { + std::vector > labels(this->_K.dimension()+1) ; + for (int q=0; q<=this->_K.dimension(); ++q) + { + for (size_t i = 0; i_K.number_of_cells(q); ++i) + { + const size_t id_per(_K_to_per.at(q).at(i)) ; + if (id_per < _t_dim.at(q)) + { + if (this->_flag.at(q).at(id_per) == PRIMARY) + labels.at(q).push_back(-1) ; + else if (this->_flag.at(q).at(id_per) == SECONDARY) + labels.at(q).push_back(1) ; + else if (this->_flag.at(q).at(id_per) == CRITICAL) + labels.at(q).push_back(0) ; + else // NONE + labels.at(q).push_back(2) ; + } + else + labels.at(q).push_back(2) ; + } + } + return labels ; + } + + /* + * \brief Exports homology generators associated to `cell_index` (critical cell) of dimension `q` (e.g.\ for vtk export). + * + * The method exports the chain \f$g(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell_index` and dimension `q`. + * + * \returns A column-major chain. + */ + Column_chain homology_chain (size_t cell_index, int q) const + { + CGAL_precondition(this->_K.is_valid_cell(cell_index, q)); + + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) + { + // Get g(cell, dim) with per indices + Column_chain g_cell(OSM::get_column(this->_G_col.at(q), cell_index)) ; + // Add 1 to the cell + g_cell.set_coefficient(cell_index, 1) ; + // Compute the chain with _K indices + Column_chain g_cell_K(g_cell.dimension()) ; + for (typename Column_chain::const_iterator it = g_cell.begin(); it != g_cell.end(); ++it) + { + const size_t i(_per_to_K.at(q).at(it->first)) ; + g_cell_K.set_coefficient(i, it->second) ; + } + return g_cell_K ; + } + else + throw std::runtime_error("Cannot export homology generators without proper HDVF option") ; + } + + /* + * \brief Exports cohomology generators associated to `cell` (critical cell) of dimension `q` (used by vtk export). + * + * The method exports the chain \f$f^\star(\sigma)\f$ for \f$\sigma\f$ the cell of index `cell` and dimension `q`. + * + * \returns A column-major chain. + * + * \exception Invalid_hdvf_option If the HDVF option is neither `OPT_FULL` nor `OPT_F` raises a `%std::runtime_error`. + */ + Column_chain cohomology_chain (size_t cell_index, int q) const + { + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) + { + // Get fstar(cell, dim) with per indices + Row_chain fstar_cell(OSM::get_row(this->_F_row.at(q), cell_index)) ; + // Add 1 to the cell + fstar_cell.set_coefficient(cell_index, 1) ; + + // Compute the chain with _K indices + Column_chain fstar_cell_K(fstar_cell.dimension()) ; + for (typename Column_chain::const_iterator it = fstar_cell.begin(); it != fstar_cell.end(); ++it) + { + const size_t i(_per_to_K.at(q).at(it->first)) ; + fstar_cell_K.set_coefficient(i, it->second) ; + } + return fstar_cell_K ; + } + else + throw std::runtime_error("Cannot export cohomology generators without proper HDVF option") ; + } + + /*! \brief Iterator over (finite) persistent intervals. + * + * Iterate over persistent intervals of finite degree duration. + * If `discard_small` is true (which is the default), the iterator discards persistent intervals with a null degree duration (that is, small persistent holes). + */ + struct iterator + { + // Iterator tags + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Persistent_hole; + + /*! \brief Iterator constructor + * + * \param per_hdvf Constant reference over the Hdvf_persistence iterated. + * \param i The initial index. + * \param discard_small If `true` (default), only persistent intervals of (strictly) positive degree duration are iterated. Otherwise, all persistent intervals are iterated. + */ + iterator(const Hdvf_persistence& per_hdvf, size_t i=0, bool discard_small = true) : _i(i), _per_hdvf(per_hdvf), _discard_small(discard_small) + { + if(_discard_small) { + // Iterate only over holes of duration > 0 + while ((_i<_per_hdvf._persist.size()) && ((_per_hdvf._persist.at(_i)).duration() == 0)) { + ++_i ; + } + } + else { + // Proceed to next hole + ++i; + } + } + + /** \brief Returns the `discard_small` flag. */ + bool has_discard_small() { return _discard_small; } + + // Operators + /*! \brief Iterator dereference + * + * \returns A `Persistent_hole` is a structure containing all information related to the current persistence hole. + */ + value_type operator*() const + { + Persistent_hole res ; + res.hole = _per_hdvf._persist.at(_i) ; + res.labelsPSC = _per_hdvf._export_labels.at(_i) ; + if (_per_hdvf._hdvf_opt & (OPT_G | OPT_FULL)) + { + res.homology_chain_birth = _per_hdvf._export_g.at(_i).first ; + res.homology_chain_death = _per_hdvf._export_g.at(_i).second ; + } + if (_per_hdvf._hdvf_opt & (OPT_F | OPT_FULL)) + { + res.cohomology_chain_birth = _per_hdvf._export_fstar.at(_i).first ; + res.cohomology_chain_death = _per_hdvf._export_fstar.at(_i).second ; + } + return res ; + } + + /** + * \brief Prefix increment. Finds next persistent interval. + * + * If `discard_small` is `true`, the iterator searches next persistent interval with a (strictly) positive degree duration, otherwise, the iterator returns next persistent interval. + * + * \returns The reference to the current iterator. + */ + iterator& operator++() + { + ++_i; + if (_discard_small) + { + // Iterate only over holes of duration > 0 + while ((_i<_per_hdvf._persist.size()) && ((_per_hdvf._persist.at(_i)).duration() == 0)) + { + ++_i ; + } + } + return *this; + } + + /** + * \brief Postfix increment. Finds the next non-null index. + * \returns The pre-increment iterator. + */ + iterator operator++(int) { iterator tmp = *this; ++(*this); return tmp; } + + /** + * \brief Equality check. + * \returns `true` if the indices are equal. + */ + friend bool operator== (const iterator& a, const iterator& b) { return a._i == b._i; }; + + /** + * \brief Inequality check. + * \returns `true` if the indices are different. + */ + friend bool operator!= (const iterator& a, const iterator& b) { return a._i != b._i; }; + + private: + size_t _i ; // Index along _persist + const Hdvf_persistence& _per_hdvf ; // reference to the Hdvf_persistence iterated + const bool _discard_small ; + }; + + /** + * \brief Iterator to the beginning of persistent intervals. + * + * \param discard_small If `true`, the iterator visits only persistent intervals of (strictly) positive degree duration. Otherwise, visit all persistent intervals. + * + * \returns The iterator to the beginning of the chain indices. + */ + iterator begin(bool discard_small = true) const { return iterator(*this, 0, discard_small) ; } + + /** + * \brief Iterator past-the-end of persistent intervals. + * + * \returns The past-the-end iterator of persistent intervals. + */ + iterator end() const { return iterator(*this, _persist.size()) ; } + + /* + * \brief Writes a `HDVF_persistence` together with the associated reduction (f, g, h, d matrices) + * + * Exports a `HDVF_persistence` to a stream in `hdvf` file format (a simple text file format, see for a specification). + * + * \param out Output stream. + */ + std::ostream& write_hdvf_reduction(std::ostream& out) const ; + + /* + * \brief Writes a `HDVF_persistence` together with the associated reduction to a file (f, g, h, d matrices). + * + * Exports a `HDVF_persistence` to a file in `hdvf` file format (a simple text file format, see for a specification). + * + * \param filename Output file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void write_hdvf_reduction(std::string filename) const { + std::ofstream out_file ( filename, std::ios::out | std::ios::trunc); + if ( ! out_file . good () ) { + std::cerr << "Out fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + write_hdvf_reduction(out_file) ; + + out_file.close(); + } + + /* + * \brief Reads a `HDVF_persistence` together with the associated reduction (f, g, h, d matrices) + * + * Imports a `HDVF_persistence` from a stream in `hdvf` file format (a simple text file format, see for a specification). + * + * \param in Input stream. + */ + std::istream& read_hdvf_reduction(std::istream& in) ; + + /* + * \brief Loads a `HDVF_persistence` together with the associated reduction from a file (f, g, h, d matrices) + * + * Imports a `HDVF_persistence` and its reduction from a file in `hdvf` file format, a simple text file format (see for a specification). + * \warning The underlying complex is not stored in the file! + * + * \param filename Input file name. + * + * \exception File_not_found If the file `filename` cannot be opened, raises a `%std::runtime_error`. + */ + void read_hdvf_reduction(std::string filename) { + std::ifstream in_file (filename); + if ( ! in_file . good () ) { + std::cerr << "Out fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + read_hdvf_reduction(in_file); + + in_file.close(); + } + + /* \brief Compares the `HDVF_persistence` with another `HDVF_persistence` over the same underlying complex and filtration. + * + * \param other Other `HDVF_persistence` to compare. + * \param full_compare Turns on "in depth" HDVF comparison (reduction matrices). + */ + + virtual bool compare(const Hdvf_persistence& other, bool full_compare = false) const { + // Compare Hdvf_core structure of *this and other + bool res(Base::compare(other, full_compare)); + if (!res) + return res; + // Compare persistence data + res = (_K_to_per == other._K_to_per); + res = res && (_persist == other._persist); + + if (_with_export){ + res = res && (_export_labels == other._export_labels); + res = res && (_export_g == other._export_g); + res = res && (_export_fstar == other._export_fstar); + } + return res; + } + +private: + /* \brief Export current persistent pair informations. + * + * This method exports and saves PSC labels, homology and cohomology generators of the current persistent pair `p` to corresponding private vectors (`_export_label`, `_export_g`, `_export_fstar`). + * The method is invoqued on the result of `find_pair_A()` before pairing cells with the A operation. + */ + void export_hdvf_persistence_pair(Cell_pair p) + { + // Export labels + std::vector > labels(this->psc_labels()) ; + _export_labels.push_back(labels) ; + // Export g (according to options) + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) + { + Column_chain chain_sigma(homology_chain(p.sigma, p.dim)) ; + Column_chain chain_tau ; + if (p.tau != this->_K.number_of_cells(p.dim+1)) // Check if the second cell is "finite" + // if (p.tau != p.sigma) // Check if the second cell is "finite" + chain_tau = homology_chain(p.tau, p.dim+1) ; + _export_g.push_back(std::pair(chain_sigma, chain_tau)) ; + } + // Export fstar (according to options) + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) + { + Column_chain chain_sigma(cohomology_chain(p.sigma, p.dim)) ; + Column_chain chain_tau ; + if (p.tau != this->_K.number_of_cells(p.dim+1)) // Check if the second cell is "finite" + chain_tau = cohomology_chain(p.tau, p.dim+1) ; + _export_fstar.push_back(std::pair(chain_sigma, chain_tau)) ; + } + } + + /* \brief Export empty persistent pair. + * Export empty persistence information (for "small" discarded pairs). + */ + void export_hdvf_persistence_pair() + { + // Export labels + std::vector > labels ; + _export_labels.push_back(labels) ; + // Export g (according to options) + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) + { + Column_chain chain_sigma, chain_tau ; + _export_g.push_back(std::pair(chain_sigma, chain_tau)) ; + } + // Export fstar (according to options) + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) + { + Column_chain chain_sigma, chain_tau ; + _export_fstar.push_back(std::pair(chain_sigma, chain_tau)) ; + } + } + + /* + * \brief Find a valid `Cell_pair` for `A()` for persistent homology. + * + * The function searches, at a given time \f$t\f$ in the filtration, the youngest critical cell \f$\gamma'\f$ forming a valid pair with the cell \f$\gamma\f$. Hence, \f$(\gamma', \gamma)\f$ valid pair is a valid pair + * (i.e.\ such that \f$\langle \partial(\gamma), \gamma' \rangle\f$ invertible). + * + * \param found Reference to a %Boolean variable. The method sets `found` to `true` if a valid pair is found, `false` otherwise. + */ + Cell_pair find_pair_A(bool &found) ; + + /* + * \brief Step forward along the filtration. + * + * Searches a possible persistent pair for `A()` with `find_pair_A`, apply `A()` and update internal structures. + */ + Cell_pair step_persist(bool& found, bool verbose = false) ; +} ; + + +template +Hdvf_persistence::Hdvf_persistence(const ChainComplex& K, const Filtration_& f, int hdvf_opt, bool with_export, int dimension_restriction) : Hdvf_core(K,hdvf_opt,dimension_restriction), _f(f), _with_export(with_export), _t(0) { + // Check if the complex is non empty + size_t acc(K.number_of_cells(0)); + for (int q=1; q_K.dimension()+1, 0) ; + _K_to_per.resize(this->_K.dimension()+1) ; + _per_to_K.resize(this->_K.dimension()+1) ; + for (int q=0; q<=this->_K.dimension(); ++q) { + _K_to_per.at(q).resize(this->_K.number_of_cells(q)) ; + _t_dim.at(q) = 0 ; + } + + for (size_t i = 0; i<_f._filtration.size(); ++i) { + const Cell c(_f._filtration.at(i)); + const int q(c.second) ; + const size_t ind_K_i(c.first) ; + const size_t ind_per_i(_per_to_K.at(q).size()) ; + _per_to_K.at(q).push_back(ind_K_i) ; + _K_to_per.at(q).at(ind_K_i) = ind_per_i ; + } + + // Init _masks + _masks.resize(this->_K.dimension()+1) ; + for (int q=0; q_K.dimension()+1; ++q) { + _masks.at(q) = OSM::Bitboard(this->_K.number_of_cells(q)) ; + } + + // Init boundary matrices + std::vector _DD_per(this->_K.dimension()+1) ; + + // Set sub_sparse_matrix mask to full and copy _DD_col with filtration order (for dimensions q>0) + for (int q = 0 ; q <= this->_K.dimension(); ++q) { + const std::pair s(this->_DD_col.at(q).dimensions()) ; + // Set mask to full + this->_DD_col.at(q).set_sub(OSM::Bitboard(s.second,false)); + _DD_per.at(q) = Column_matrix(s.first, s.second) ; + } + // Set empty mask for _DD_per[0] + _DD_per.at(0).complement(); + + for (int q = 1 ; q <= this->_K.dimension(); ++q) { + // Cross _DD_col.at(q) and set _DD_per.at(q) coefficients on the fly + for (OSM::Bitboard::iterator it_col = this->_DD_col.at(q).begin(); it_col != this->_DD_col.at(q).end(); ++it_col) + { + const size_t j(*it_col) ; + const Column_chain& col(OSM::cget_column(this->_DD_col.at(q), j)) ; + for (typename Column_chain::const_iterator it = col.begin(); it != col.end(); ++it) + { + const size_t i(it->first) ; + const Coefficient_ring v(it->second) ; + // Cells in the _K basis : i(dim q-1) / j(dim q) + // Convert to indices in the persistent order + const size_t pi(_K_to_per.at(q-1).at(i)), pj(_K_to_per.at(q).at(j)) ; + set_coefficient(_DD_per.at(q), pi, pj, v) ; + } + } + } + this->_DD_col = _DD_per ; + + // Init _DD_col mask (empty for all cells) + for (int q = 1 ; q <= this->_K.dimension(); ++q) + this->_DD_col.at(q).set_sub(_masks.at(q)) ; +} + +template +Cell_pair Hdvf_persistence::find_pair_A(bool &found) +{ + Cell_pair p ; + // Get current cell (in the basis K) + const size_t current_time(_t-1); + Cell c(_f._filtration.at(current_time)) ; + const int q(c.second), sigma(_K_to_per.at(q).at(c.first)) ; + // Search for pairing + found = false; + + // Check if the cell belongs to the dimension restriction constraint + if ((this->_dimension_restriction >=0) && (q != this->_dimension_restriction) && (q != this->_dimension_restriction+1)) + return p; + + if (q >= 1) + { + // Compute bounded max while iterating over the Sparse_chain + const Column_chain& tmp2(OSM::cget_column(this->_DD_col.at(q), sigma)) ; + std::size_t tmax = _t_dim.at(q-1) ; + std::size_t i ; + for (typename Column_chain::const_iterator it = tmp2.cbegin(); it != tmp2.cend(); ++it) + { + if ((it->first < tmax) && is_invertible(it->second)) + { + if (!found) // for first cell met + { + found = true ; + i = it->first ; + } + else // update the i with the maximum cell index (persistence) + { + if (it->first > i) + i = it->first ; + } + } + } + if (found) + { + p.sigma = i ; + p.tau = sigma ; + p.dim = q-1 ; + } + } + return p; +} + +template +Cell_pair Hdvf_persistence::step_persist(bool& found, bool verbose) +{ + Cell_pair p; + found = false; + // Compute next persistent pair + + ++_t ; // Step forward in the filtration + const size_t t_current(_t-1); // Current time + const size_t q_current(_f._filtration.at(t_current).second) ; // Get the dimension of the new current cell + ++_t_dim.at(q_current) ; // Update time in the dimension of the current cell + const size_t t_dim_current(_t_dim.at(q_current)-1); // Time of the current cell in its dimension + + // Test if _dimension_restriction is off (-1) or current dimension in the range of considered dimensions + if ((this->_dimension_restriction == -1) || ((q_current >= this->_dimension_restriction) && (q_current <= this->_dimension_restriction+1))) { + _masks.at(q_current).set_on(t_dim_current) ; // Update mask accordingly + this->_DD_col.at(q_current).set_bit_on(t_dim_current) ; // Update _DD_col mask + + // Search for pairing (between cells of dimension q_current and q_current+1) + p=find_pair_A(found) ; + if (found) + { + // Corresponding persistent interval + const int q(p.dim) ; + // indices of both cells in the _K basis + const size_t ki(_per_to_K.at(q).at(p.sigma)), kj(_per_to_K.at(q+1).at(p.tau)) ; + Cell ci(ki, q), cj(kj, q+1) ; // cells of the interval - in the K basis + size_t ti(_f._cell_to_t.at(ci)), tj(_f._cell_to_t.at(cj)) ; // times of the interval + + Persistence_interval hole; + hole.time_birth = ti; + hole.time_death = tj; + hole.degree_birth = _f._deg.at(ti); + hole.degree_death = _f._deg.at(tj); + hole.cell_birth = ci; + hole.cell_death = cj; + // Add this interval + _persist.push_back(hole) ; + + // If export is on, store export data for significant persistent intervals + if (_with_export) + { + if (hole.duration() > 0) + export_hdvf_persistence_pair(p) ; + else + export_hdvf_persistence_pair() ; + } + + + // Prepare for next step + this->A(p.sigma, p.tau, p.dim) ; // Update the reduction + if (verbose) + { + std::cout << "A : " << p.sigma << " - " << p.tau << " (dim " << p.dim << ")" << std::endl ; + this->write_matrices(std::cout) ; + } + } + } + return p; +} + +template +std::ostream& operator<< (std::ostream& out_stream, const typename Hdvf_persistence::Persistence_interval& hole) { + typedef Hdvf_persistence Base; + // time (cell, dim) -> time (cell, dim) / degree duration + + if (hole.time_death != hole.time_birth) // finite interval + { + out_stream << "[" << hole.time_birth << " (" << hole.cell_birth.first << ", " << hole.cell_birth.second << ") -> " ; + out_stream << hole.time_death << " (" << hole.cell_death.first << ", " << hole.cell_death.second << ") / duration: " << hole.duration() << "]" << std::endl ; + } + else + { + out_stream << "[" << hole.time_birth << " (" << hole.cell_birth.first << ", " << hole.cell_birth.second << ") -> " ; + out_stream << "inf]" << std::endl ; + } + return out_stream ; +} + +// Save HDVF and reduction +template +std::ostream& Hdvf_persistence::write_hdvf_reduction(std::ostream& out) const { + // Write HDVF prefix type + out << "#HDVF_persistence" << std::endl; + // Call parent method main code + Base::write_hdvf_reduction_main(out); + // Write persistence data + // ---- with export + out << _with_export << std::endl; + // ---- _K_to_per permutation + for (int q=0; q<=this->_K.dimension(); ++q) { + for (int i=0; i_K.number_of_cells(q); ++i) { + out << _K_to_per.at(q).at(i) << " " ; + } + out << std::endl; + } + // ---- _persist + // size + out << _persist.size() << std::endl; + // list of persistent intervals (one by line) + for (Persistence_interval inter : _persist) { + out << inter.time_birth << " " << inter.time_death << " " << inter.degree_birth << " " << inter.degree_death << " "; + out << inter.cell_birth.first << " " << inter.cell_birth.second << " "; + out << inter.cell_death.first << " " << inter.cell_death.second << std::endl; + } + if (_with_export) { + // Number of persistant intervals informations exported + size_t cpt(0); + for (int i=0; i<_export_labels.size(); ++i) { + if (_export_labels.at(i).size() > 0) + ++cpt; + } + out << cpt << std::endl; + // For each persistent interval information + for (int i=0; i<_export_labels.size(); ++i) { + if (_export_labels.at(i).size() > 0) { + // -> index + out << i << std::endl; + // _export_labels + for (int q=0; q<=this->_K.dimension(); ++q) { + for (int label : _export_labels.at(i).at(q)) + out << label << " "; + out << std::endl ; + } + // _export_g pair of chains + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) { + OSM::write_chain(_export_g.at(i).first, out); + OSM::write_chain(_export_g.at(i).second, out); + } + // _export_fstar pair of chains + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) { + OSM::write_chain(_export_fstar.at(i).first, out); + OSM::write_chain(_export_fstar.at(i).second, out); + } + } + } + } + return out; +} + +// Save HDVF and reduction +template +std::istream& Hdvf_persistence::read_hdvf_reduction(std::istream& in) { + // Load and check HDVF prefix + // For Hdvf_core: #HDVF_core + // For Hdvf_persistence: #HDVF_persistence + // For Hdvf_duality: #HDVF_duality + std::string prefix; + in >> prefix; + if (prefix.compare("#HDVF_persistence")) { + std::cerr << "try to load a HDVF with incompatible prefix" << std::endl ; + throw (std::runtime_error("try to load a HDVF with incompatible prefix")); + } + + // Call parent method + Hdvf_core::read_hdvf_reduction_main(in); + // Read persistence data + // ---- with export + in >> _with_export; + // ---- _K_to_per and _per_to_K permutations + _K_to_per.clear(); + _per_to_K.clear(); + _K_to_per.resize(this->_K.dimension()+1); + _per_to_K.resize(this->_K.dimension()+1); + for (int q=0; q<=this->_K.dimension(); ++q) { + _K_to_per.at(q).resize(this->_K.number_of_cells(q)); + _per_to_K.at(q).resize(this->_K.number_of_cells(q)); + for (int i=0; i_K.number_of_cells(q); ++i) { + int cell; + in >> cell; + _K_to_per.at(q).at(i) = cell; + _per_to_K.at(q).at(cell) = i ; + } + } + // ---- _persist + // size + int nb_persist; + in >> nb_persist; + _persist.clear(); + _persist.resize(nb_persist); + _t = nb_persist; + _t_dim.resize(this->_K.dimension()+1); + for (int q=0; q<=this->_K.dimension(); ++q) + _t_dim.at(q) = this->_K.number_of_cells(q); + _masks.resize(this->_K.dimension()+1); + for (int q=0; q<=this->_K.dimension(); ++q) { + for (int j=0; j_K.number_of_cells(q); ++j) + _masks.at(q).set_on(j); + } + // list of persistent intervals (one by line) + for (int i=0; i> inter.time_birth >> inter.time_death >> inter.degree_birth >> inter.degree_death; + in >> inter.cell_birth.first >> inter.cell_birth.second; + in >> inter.cell_death.first >> inter.cell_death.second; + _persist.at(i) = inter; + } + + if (_with_export) { + // Number of persistant intervals informations exported (>0 duration) + size_t cpt; + in >> cpt; + // Resizes + _export_labels.clear(); + _export_labels.resize(nb_persist); + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) { + _export_g.clear(); + _export_g.resize(nb_persist); + } + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) { + _export_fstar.clear(); + _export_fstar.resize(nb_persist); + } + + // For each persistent interval exported information (with >0 duration) + for (int i_per=0; i_per index + in >> i; + // _export_labels.at(i) + _export_labels.at(i).resize(this->_K.dimension()+1); + for (int q=0; q<=this->_K.dimension(); ++q) { + int label; + for (int j = 0; j_K.number_of_cells(q); ++j) { + in >> label; + _export_labels.at(i).at(q).push_back(label); + } + } + // _export_g pair of chains + if (this->_hdvf_opt & (OPT_FULL | OPT_G)) { + OSM::read_chain(_export_g.at(i).first, in); + OSM::read_chain(_export_g.at(i).second, in); + } + // _export_fstar pair of chains + if (this->_hdvf_opt & (OPT_FULL | OPT_F)) { + OSM::read_chain(_export_fstar.at(i).first, in); + OSM::read_chain(_export_fstar.at(i).second, in); + } + } + } + return in; +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_HDVF_PERSISTENCE_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_tools.h b/HDVF/include/CGAL/HDVF/Hdvf_tools.h new file mode 100644 index 000000000000..8d2ff645b1b5 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_tools.h @@ -0,0 +1,135 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_HDVF_TOOLS_H +#define CGAL_HDVF_HDVF_TOOLS_H + +#include + +#include + +#include +#include + +// IO tools + +namespace CGAL { +namespace Homological_discrete_vector_field { + +inline std::ostream & operator<< (std::ostream &out, const Cell_pair& p) +{ + out << "(" << p.sigma << "/" << p.tau << " - dim " << p.dim << ")" ; + return out ; +} + +/** + *\ingroup PkgHDVFToolsClasses + * + * \brief Runs an interaction loop to iterate M, W or MW operations and export the results to vtk. + * + * The loop runs until the key `Q` is pressed. Otherwise, the loop asks for an operation (M, W or MW) and a cell (index and dimension). + * Then all possible paired cells are listed and the user can chose one of them (or none). + */ + +template +void interaction_loop(Hdvf &hdvf, + ChainComplex &complex, + const std::function &hdvf, ChainComplex &complex)> &output_vtk) +{ + bool over = false ; + output_vtk(hdvf, complex) ; + while (!over) + { + std::string instr ; + int ipair ; + std::cout << "Next instruction : M, W, MW or Q (to quit)" << std::endl ; + std::cin >> instr ; + if (instr == std::string("M")) + { + int sigma, q ; + std::cout << "Provide sigma / q (separate with space):" << std::endl ; + std::cin >> sigma >> q ; + bool found = false ; + std::vector possM(hdvf.find_pairs_M(q, found, sigma)) ; + std::cout << "-> possible pairings for M:" << std::endl ; + for (int i = 0; i 0) + { + std::cout << "chose indice of pairing (out the range = quit): " ; + std::cin >> ipair ; + if ((ipair >= 0) && (ipair < possM.size())) + { + Cell_pair p(possM.at(ipair)) ; + std::cout << "M(" << p << ")" << std::endl ; + hdvf.M(p.sigma, p.tau, p.dim) ; + // hdvf.write_matrices() ; + output_vtk(hdvf, complex) ; + } + } + } + else if (instr == std::string("W")) + { + int sigma, q ; + std::cout << "Provide sigma / q (separate with space):" << std::endl ; + std::cin >> sigma >> q ; + bool found = false ; + std::vector possW(hdvf.find_pairs_W(q, found, sigma)) ; + std::cout << "-> possible pairings for W:" << std::endl ; + for (int i = 0; i 0) + { + std::cout << "chose indice of pairing (out the range = quit): " ; + std::cin >> ipair ; + if ((ipair >= 0) && (ipair < possW.size())) + { + Cell_pair p(possW.at(ipair)) ; + std::cout << "W(" << p << ")" << std::endl ; + hdvf.W(p.sigma, p.tau, p.dim) ; + // hdvf.write_matrices() ; + output_vtk(hdvf, complex) ; + } + } + } + else if (instr == std::string("MW")) + { + int sigma, q ; + std::cout << "Provide sigma / q (separate with space):" << std::endl ; + std::cin >> sigma >> q ; + bool found = false ; + std::vector possMW(hdvf.find_pairs_MW(q, found, sigma)) ; + std::cout << "-> possible pairings for MW:" << std::endl ; + for (int i = 0; i 0) + { + std::cout << "chose indice of pairing (out the range = quit): " ; + std::cin >> ipair ; + if ((ipair >= 0) && (ipair < possMW.size())) + { + Cell_pair p(possMW.at(ipair)) ; + std::cout << "MW(" << p << ")" << std::endl ; + hdvf.MW(p.sigma, p.tau, p.dim) ; + // hdvf.write_matrices() ; + output_vtk(hdvf, complex) ; + } + } + } + else if (instr == std::string("Q")) + over = true ; + } +} + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_HDVF_TOOLS_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_traits_2.h b/HDVF/include/CGAL/HDVF/Hdvf_traits_2.h new file mode 100644 index 000000000000..1c121caa67ec --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_traits_2.h @@ -0,0 +1,57 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_TRAITS_2_H +#define CGAL_HDVF_TRAITS_2_H + +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + + /*! + \ingroup PkgHDVFTraitsClasses + + The class `Hdvf_traits_2` implements the `HDVFTraits` concept for 2D data, using a geometric kernel `K`. + + @tparam K a geometric kernel model of the `Kernel` concept. + + \cgalModels{HDVFTraits} + + */ + +template +struct Hdvf_traits_2 { + using Dimension = Dimension_tag< 2 >; + typedef K Kernel; + typedef typename K::Point_2 Point; + typedef typename K::Vector_2 Vector; + typedef typename K::FT FT; + typedef CGAL::Bbox_2 Bbox; + typedef typename K::Point_3 Point3; + /** \brief Converts a `Point` to a `Point3` for %VTK rendering. + * + * Update this function to change the projection for %VTK rendering. + */ + static std::function to_point3; +}; + +template +std::function Hdvf_traits_2::to_point3 = [](const typename K::Point_2& p) { return typename K::Point_3(p[0], p[1], 0); }; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_TRAITS_2_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_traits_3.h b/HDVF/include/CGAL/HDVF/Hdvf_traits_3.h new file mode 100644 index 000000000000..d7f0f9437557 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_traits_3.h @@ -0,0 +1,57 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_TRAITS_3_H +#define CGAL_HDVF_TRAITS_3_H + +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + + /*! + \ingroup PkgHDVFTraitsClasses + + The class `Hdvf_traits_3` implements the `HDVFTraits` concept for 3D data, using a geometric kernel `K`. + + @tparam K a geometric kernel model of the `Kernel` concept. + + \cgalModels{HDVFTraits} + + */ + +template +struct Hdvf_traits_3 { + using Dimension = Dimension_tag< 3 >; + typedef K Kernel; + typedef typename K::Point_3 Point; + typedef typename K::Vector_3 Vector; + typedef typename K::FT FT; + typedef CGAL::Bbox_3 Bbox; + typedef typename K::Point_3 Point3; + /** \brief Converts a `Point` to a `Point3` for %VTK rendering. + * + * By default, this function is the identity. Update it to change the projection for %VTK rendering. + */ + static std::function to_point3; +}; + +template +std::function Hdvf_traits_3::to_point3 = [](const typename K::Point_3& p) { return typename K::Point_3(p); }; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_TRAITS_3_H diff --git a/HDVF/include/CGAL/HDVF/Hdvf_traits_d.h b/HDVF/include/CGAL/HDVF/Hdvf_traits_d.h new file mode 100644 index 000000000000..4159061c540a --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Hdvf_traits_d.h @@ -0,0 +1,157 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_TRAITS_D_H +#define CGAL_HDVF_TRAITS_D_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + + /*! + \ingroup PkgHDVFTraitsClasses + + The class `Hdvf_traits_d` implements the `HDVFTraits` concept for dD data, using a geometric kernel `K`. + + @tparam K a geometric traits class. Must be either `Epick_d` or `Epeck_d` with a fixed dimension. + + \cgalModels{HDVFTraits} + + */ + +template +struct Hdvf_traits_d { + using Dimension = Dimension_tag< K::Dimension::value >; + typedef K Kernel; + typedef typename K::Point_d Point; + typedef typename K::Vector_d Vector; + typedef typename K::FT FT; + typedef CGAL::Bbox_d Bbox; + typedef typename Exact_predicates_inexact_constructions_kernel::Point_3 Point3; + /** \brief Converts a `Point` to a `Point3` for %VTK rendering. + * + * By default, this function is set to `default_projection` (which maps \f$\mathbb R^d\f$ to \f$\mathbb R^3\f$ by projecting on the three first coordinates). Update it to change the projection for %VTK rendering. + * + * \param p Constant reference over the projected point. + * + * \return The projected `Point3`. + */ + static std::function to_point3; + + // Set of standard projection operators + + /* \brief Default projection operator. + * + * Projects a dD point to its three first coordinates. + */ + // R^d -> R^3 projection on first coordinates (x,y,z) + static std::function default_projection ; + + /** \brief Builds a projector over an affine subspace of dimension 3. + * + * Builds the projector over the affine space generated by the point `p0` and an orthonormal basis `d1`, `d2` and `d3`. + * + * \param p0 A `Point_d` contained in the affine subspace. + * \param d1 First direction of the subspace. + * \param d2 Second direction of the subspace. + * \param d3 Third direction of the subspace. + * + * \return The orthogonal projector of a `Point` onto this affine subspace of dimension 3. + */ + static std::function plane_projection_builder(typename K::Point_d p0, typename K::Vector_d d1, typename K::Vector_d d2, typename K::Vector_d d3) { + typedef Exact_predicates_inexact_constructions_kernel::Point_3 Point_3; + typedef typename K::Point_d Point_d; + typedef typename K::Vector_d Vector_d; + // See NewKernel_d\include\CGAL\NewKernel_d\Kernel_d_interface.h for what is available in K + std::function project = [=](const Point_d& p) { + Vector_d tmp(K().difference_of_points_d_object()(p,p0)); + return Point_3(K().scalar_product_d_object()(tmp,d1), K().scalar_product_d_object()(tmp,d2), K().scalar_product_d_object()(tmp,d3)); + }; + return project; + } + + /** \brief Builds a projector over the affine subspace of dimension 3 given by the principal components analysis of a set of points. + * + * Builds the projector over the affine space generated by the barycenter of `pts` points and three principal directions. + * + * \param pts A vector of points. + * + * \return The projector (performing dimension reduction) over the subspace of dimension 3 given by the PCA of the points. + */ + static std::function pca_frame_builder(const std::vector& pts) { + typedef Exact_predicates_inexact_constructions_kernel::Point_3 Point_3; + typedef typename K::Point_d Point_d; + typedef typename K::Vector_d Vector_d; + typedef double FT; + typedef Eigen_vector Vector; + typedef Eigen_matrix Matrix; + + // Barycenter of points + Vector_d bary; + for (Point p : pts){ + Vector_d v = K().point_to_vector_d_object()(p) ; + bary = bary + v; + } + Vector_d v = K().scaled_vector_d_object()(bary, FT(1)/FT(pts.size())) ; + Point_d barycenter = K().vector_to_point_d_object()(v) ; + + // Define the PCA matrix by SVD + Matrix A(pts.size(), K::Dimension::value); + // Fill the matrix (p[i]-barycenter along line i) + for (int i=0; i jacobiSvd(A.eigen_object()); +#else + Eigen::JacobiSVD jacobiSvd(A.eigen_object(), ::Eigen::ComputeFullV); +#endif + + std::vector> d(3); + std::vector dirs(3); + // Fill d[i] vector + for (int i=0; i<3; ++i) { + d.at(i).resize(K::Dimension::value); + for (int j=0; j +std::function Hdvf_traits_d::to_point3 = Hdvf_traits_d::default_projection; + +template +std::function Hdvf_traits_d::default_projection = + [](const typename K::Point_d& p) { return typename Exact_predicates_inexact_constructions_kernel::Point_3(p[0], p[1], p[2]); }; + + + + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_TRAITS_D_H diff --git a/HDVF/include/CGAL/HDVF/Icosphere_object_io.h b/HDVF/include/CGAL/HDVF/Icosphere_object_io.h new file mode 100644 index 000000000000..2b42f39f319c --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Icosphere_object_io.h @@ -0,0 +1,148 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_ICOSPHERE_OBJECT_H +#define CGAL_HDVF_ICOSPHERE_OBJECT_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFIOClasses + + The class `Icosphere_object_io` is an intermediate %IO class, used to produce a `Mesh_object_io` containing an icosphere (for `Hdvf_duality` with tetgen). + + \tparam Traits a geometric traits class model of the `HDVFTraits` concept. + */ + +template +class Icosphere_object_io : public Mesh_object_io +{ +protected: + using Point = typename Traits::Point; + using Vector = typename Traits::Vector; + + using Index=size_t ; + using Lookup=std::map, Index>; + +public: + /** \brief Builds a `Mesh_object_io` icosphere. + * + * Such icospheres are usually used to compute Alexander duality isomorphisms with `HDVF_duality`. The initial complex is embedded into a `Mesh_object_io` icosphere and the resulting set of simplices is triangulated using tetgen. + * + * \param subdivisions Subdivision level of the icosphere. + * \param c Center of the icosphere. + * \param r Radius of the icosphere. + */ + Icosphere_object_io(size_t subdivisions, const Point &c = Point({0, 0, 0}), double r=1.) : Mesh_object_io(2, vertices_ico, triangles_ico) + { + for (size_t i=0; i vertices_ico = + { + Point(-X,N,Z), Point(X,N,Z), Point(-X,N,-Z), Point(X,N,-Z), + Point(N,Z,X), Point(N,Z,-X), Point(N,-Z,X), Point(N,-Z,-X), + Point(Z,X,N), Point(-Z,X, N), Point(Z,-X,N), Point(-Z,-X, N) + }; + + inline static const std::vector triangles_ico = + { + {0,4,1},{0,9,4},{9,5,4},{4,5,8},{4,8,1}, + {8,10,1},{8,3,10},{5,3,8},{5,2,3},{2,7,3}, + {7,10,3},{7,6,10},{7,11,6},{11,0,6},{0,1,6}, + {6,1,10},{9,0,11},{9,11,2},{9,2,5},{7,2,11} + }; + + // Methods + Index vertex_for_edge(Lookup& lookup, Index first, Index second) + { + Lookup::key_type key(first, second); + if (key.first>key.second) + std::swap(key.first, key.second); + + auto inserted=lookup.insert({key, this->_nodes.size()}); + if (inserted.second) + { + const Point& edge0(this->nodes().at(first)); + const Point& edge1(this->nodes().at(second)); + Vector v = (edge1 - ORIGIN) + (edge0 - ORIGIN); + v = v / std::sqrt(v.squared_length()); + this->add_node(ORIGIN + v); + } + + return inserted.first->second; + } + + void subdivide() + { + Lookup lookup; + std::vector result ; + + for (const Io_cell_type & each:this->cells()) + { + std::array mid; + std::vector each_vertex ; + for (size_t c : each) + each_vertex.push_back(c) ; + for (size_t edge=0; edge<3; ++edge) + { + mid[edge]=vertex_for_edge(lookup, + each_vertex[edge], each_vertex[(edge+1)%3]); + } + result.push_back({each_vertex[0], mid[0], mid[2]}); + result.push_back({each_vertex[1], mid[1], mid[0]}); + result.push_back({each_vertex[2], mid[2], mid[1]}); + result.push_back({mid[0], mid[1], mid[2]}); + } + this->clear_cells() ; + for (Io_cell_type c : result) + this->add_cell(c) ; + } + + void rigid_transformation(const Point &c, double r) + { + for (size_t i=0; inumber_of_nodes(); ++i) + { + // Apply rotation + this->set_node(i, ORIGIN + (r * (this->nodes()[i] - ORIGIN))) ; + // Apply translation + this->set_node(i, this->nodes()[i] + (c - ORIGIN)) ; + } + } +}; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + + +#endif // CGAL_HDVF_ICOSPHERE_OBJECT_H diff --git a/HDVF/include/CGAL/HDVF/Mesh_object_io.h b/HDVF/include/CGAL/HDVF/Mesh_object_io.h new file mode 100644 index 000000000000..b1d2f61b290a --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Mesh_object_io.h @@ -0,0 +1,698 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_MESH_OBJECT_H +#define CGAL_HDVF_MESH_OBJECT_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/** + * \relates Mesh_object_io + * + * \brief Type of cells of Mesh_object_io. + * + * *Sorted* vector of the vertex indices. + */ +typedef std::vector Io_cell_type ; + +/** \relates Mesh_object_io + + \brief Type respresenting sets of cells. */ +typedef std::vector Io_cells_type ; + + +// ----- VTK format ----- + +// For triangular meshes +// Associated to any dimension the type number of associated VTK cells +static std::vector VTK_types_IO = {1, 3, 5, 10} ; + + +// ----- Generic ----- + +inline bool check_sanity_line(const std::string &line, const std::string &file) { + // Check that line is sanitized. If not, throw. + for ( size_t i = 0; i < line.size(); ++ i ) { + if ( ! ( std::isspace(line[i]) || std::isdigit(line[i]) || (line[i] == '-') || (line[i] == '.') || (line[i] == 'e')) ) { + std::cerr << "Error:\n Cannot parse file " << file << std::endl ; + std::cerr << "--- " << line[i] << std::endl ; + return false; + } + } + return true ; +} + +inline bool get_next_uncommented_line(std::ifstream &infile, std::string &result) { + while(getline(infile,result)) { + if(result.length() > 1 && result[0] != '#') { + return true; + } + } + return false; +} + +/** \relates Mesh_object_io + * + * \brief Loads vertices coordinates from a .nodes file. + * + * Reads nodes (ie.\ vertices coordinates) from a .nodes file (tetgen format, see \ref secHDVFTetgen "user manual" for details) . + * + * \param filename Name of the input file. + * \param nodes Pointer to a vector of points into which nodes are outputed. + * \param adapt If `fill` is false, nodes must have the same %dimension as the traits Point, if true, nodes %dimension can be lower (and missing coordinates are filled with zeros) or higher (and coordinates are truncated to the traits %dimension). + * + * \exception File_not_found If `filename` does not exist, raise a `%std::runtime_error` exception. + * + * \exception Incoherent_dimension Raises a `%std::runtime_error` if %dimensions of the `Traits` and %dimension of nodes do not match. + */ + +template +inline size_t read_nodes(const std::string &filename, std::vector *nodes, bool adapt = false) { + typedef typename Traits::Point Point; + std::ifstream in_file (filename) ; + if ( ! in_file . good () ) { + std::cerr << "Mesh_object_io read_nodes. Fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + // First line is the number of nodes + size_t nnodes, nnodes_tmp, nodes_dim, padding; + int d ; + bool fill; + if ( ! in_file.eof()) + { + std::string line; + getline( in_file, line ); + check_sanity_line(line, filename) ; + // First number is the number of nodes, then dimension of nodes + std::istringstream is (line); + is >> nnodes ; + is >> nodes_dim ; + if (!adapt) { + if (nodes_dim != Traits::Dimension::value){ + std::cerr << "read_nodes error: dimension of nodes incompatible with Traits::Dimension" << std::endl; + throw("read_nodes error: dimension of nodes incompatible with Traits::Dimension"); + } + } + else { + if (nodes_dim < Traits::Dimension::value){ + fill = true; + padding = Traits::Dimension::value - nodes_dim; + } + else + fill = false; + } + d = (nodes_dim <= Traits::Dimension::value)?nodes_dim:Traits::Dimension::value; + } + nnodes_tmp = nnodes ; + while ( !(in_file.eof()) && (nnodes_tmp>0)) + { + size_t trash ; + double x ; + std::vector node ; + --nnodes_tmp ; + std::string line; + getline( in_file, line ); + check_sanity_line(line, filename) ; + std::istringstream is (line); + is >> trash ; + for (int i = 0; i> x ; + node.push_back(x) ; + } + + if constexpr (Traits::Dimension::value == 2){ + nodes->push_back(Point(typename Traits::FT(node[0]), typename Traits::FT(node[1]))) ; + }else if constexpr (Traits::Dimension::value == 3){ + if ((padding <= 0) || (!adapt)) + nodes->push_back(Point(typename Traits::FT(node[0]), typename Traits::FT(node[1]), typename Traits::FT(node[2]))) ; + else + nodes->push_back(Point(typename Traits::FT(node[0]), typename Traits::FT(node[1]), 0.)) ; + }else{ + std::vector res(Traits::Dimension::value) ; + for (int i=0; ipush_back(Point(res.begin(), res.end())); + } + } + in_file.close() ; + return nnodes; +} + +/*! + \ingroup PkgHDVFIOClasses + + The class `Mesh_object_io` is an intermediate IO class, used to load arbitrary collections of simplices. and produce simplicial complexes. + + The set of simplices stored in a `Mesh_object_io` is arbitrary. It is neither a simplicial complex (closed under the face operation), nor a proper mesh (including only cells of maximal dimension) and requires no manifold property. The collection of simplices stored in a `Mesh_object_io` are simply considered as maximal simplices when generating an associated simplicial complex. + + \tparam Traits a geometric traits class model of the `HDVFTraits` concept. + */ + +// Generic Mesh_object_io class - for 3D triangular meshes +template +class Mesh_object_io +{ +public: + /** \brief Underlying point type. */ + typedef typename Traits::Point Point ; + /** \brief Bounding box type. */ + typedef typename Traits::Bbox Bbox ; +protected: + // The variable `_dim` is used to encode both the dimension of the object loaded and whether it encodes a complex (with cells of various dimensions) or a mesh (a collection of triangles) + // - if `_dim` > 0 : `Mesh_object_io` encodes a mesh and all cells have dimension `d` + // - if `_dim` < 0 : `Mesh_object_io` encodes a complex (possibly incomplete) of dimension `d` + int _dim = 0 ; + size_t _nvertices, _ncells, _nedges ; + std::vector _nodes ; // Coordinates of vertices (optional) + std::vector _cells ; + + // Write vtk file + template + void write_vtk(const std::string &filename, const std::vector &nodes, const std::vector &chains, const std::vector *labels=NULL, const std::string scalar_type="none") { + bool with_scalars = (labels != NULL) ; + // Load ... + std::ofstream out ( filename, std::ios::out | std::ios::trunc); + + if ( !out . good () ) { + std::cerr << "SimplicialComplex::loadFromFile. Fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + // Header + out << "# vtk DataFile Version 2.0" << std::endl ; + out << "generators" << std::endl ; + out << "ASCII" << std::endl ; + out << "DATASET UNSTRUCTURED_GRID" << std::endl ; + + // Points + size_t nnodes = nodes.size() ; + out << "POINTS " << nnodes << " double" << std::endl ; + for (Point n : nodes) { + out << Traits::to_point3(n); + out << std::endl ; + } + + // Cells + // Number of cells : for each chain, each number of vertices for each cell + // Size : size of a cell defined by d vertices : d+1 + size_t ncells_tot = 0, size_cells_tot = 0 ; + for (size_t i = 0; i types ; + std::vector scalars ; + for (size_t i = 0; iat(i)) ; + } + } + + // CELL_TYPES + out << "CELL_TYPES " << ncells_tot << std::endl ; + for (size_t t : types) + out << t << " " ; + out << std::endl ; + + if (with_scalars) { + // CELL_TYPES + out << "CELL_DATA " << ncells_tot << std::endl ; + out << "SCALARS CriticalCellsId " << scalar_type << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (CoefficientRing s : scalars) + out << s << " " ; + out << std::endl ; + } + out.close() ; + } + +public: + /** \brief Returns the %dimension of the Mesh_object_io. + * + * The %dimension can be positive or negative: + * - if positive: the set of simplicial cells loaded is a "mesh" and all cells have the same %dimension which is the dimension of the `Mesh_object_io` + * - if negative: the set of simplicial cells loaded have various %dimensions and the %dimension of the `Mesh_object_io` is the maximum of these %dimensions. + */ + int dimension() const { return _dim; } + + /** \brief %Default constructor. + * + * Create an empty `Mesh_object_io` of dimension `d`. + * + * \param d Dimension of the `Mesh_object_io` (the default is 0). + */ + Mesh_object_io(int d = 0) : _dim(d), _nvertices(0), _ncells(0), _nedges(0) {} + + /** \brief Constructor from a vector of points (vertex coordinates) and a vector of simplices. + * + * Simplices are described by the list of vertex indices. + * + * \param d The %dimension `d` can be positive or negative: + * - if positive: the set of simplicial cells loaded is a "mesh" and all cells have the same %dimension + * - if negative: the set of simplicial cells loaded have various %dimensions and `d` must be the maximum of these %dimensions. + * + * Vertices are described by their index; this index must be unique (vertices with similar underlying points and different vertex indices will be considered as different). + * + * \param vnodes Vector of vertex coordinates. + * \param vcells Vector of cells (each cell is described by a vector of vertices index) + * \param sort_data If `true` the vectors of vertices index are sorted, if `false` they are assumed to be sorted (faster). + * + * \exception Traits_dimension_mismatch If the %dimension `d` provided does not match the %dimension of the Traits, raises a `%std::runtime_error`. + * + * \exception Inconsistent_dimension If the %dimension of one of the cell does not match the %dimension of the Traits, raises a `%std::runtime_error`. + */ + Mesh_object_io(int d, const std::vector &vnodes, const std::vector &vcells, bool sort_data = false) : _dim(d), _nvertices(vnodes.size()), _ncells(vcells.size()), _nedges(0), _nodes(vnodes), _cells(vcells) { + check_dimension() ; + // if (abs(d) != Traits::Dimension::value) + // throw std::runtime_error("Mesh_object_io: the Traits dimension differs from the dimension provided."); + if (sort_data) { + // Sort the vector encoding each cell + for (int i=0; i<_ncells; ++i) { + std::sort(_cells.at(i).begin(), _cells.at(i).end()); + } + } + } + + /** \brief Build a Mesh_object_io representing the bounding box of diagonal `BB_min` - `BBmax`. + * + * \param BBmin coordinates of the lower corner of the bounding box. + * \param BBmax coordinates of the higher corner of the bounding box. + */ + Mesh_object_io(const Point &BBmin, const Point &BBmax) + : _dim(3), _nvertices(8), _ncells(12), _nedges(0) { + std::vector points = {BBmin, BBmax} ; + Bbox ic = bounding_box(points.begin(), points.end()) ; + _nodes.resize(8) ; + _nodes[0] = ic[0] ; + _nodes[1] = ic[1] ; + _nodes[2] = ic[2] ; + _nodes[3] = ic[3] ; + _nodes[4] = ic[5] ; + _nodes[5] = ic[6] ; + _nodes[6] = ic[7] ; + _nodes[7] = ic[4] ; + + _cells.resize(12) ; + _cells[0] = Io_cell_type({0, 1, 4}) ; + _cells[1] = Io_cell_type({1, 4, 5}) ; + _cells[2] = Io_cell_type({1, 2, 6}) ; + _cells[3] = Io_cell_type({1, 5, 6}) ; + _cells[4] = Io_cell_type({0, 1, 3}) ; + _cells[5] = Io_cell_type({1, 2, 3}) ; + _cells[6] = Io_cell_type({2, 3, 6}) ; + _cells[7] = Io_cell_type({3, 6, 7}) ; + _cells[8] = Io_cell_type({0, 3, 4}) ; + _cells[9] = Io_cell_type({3, 4, 7}) ; + _cells[10] = Io_cell_type({4, 5, 6}) ; + _cells[11] = Io_cell_type({4, 6, 7}) ; + } + + // TODO : check that de dimensions of nodes are consistent... + + /** \brief Returns the vector of nodes (ie vertices coordinates). */ + const std::vector& nodes() const { + return _nodes ; + } + + /** \brief Returns the number of nodes (ie vertices). */ + int number_of_nodes() const { return _nvertices; } + + /** \brief Returns the vector of cells. */ + const std::vector& cells() const { + return _cells; + } + + /** \brief Updates the coordinates of the `i`th node (ie vertex). */ + void set_node(size_t i, const Point &P) { + _nodes[i] = P; + } + + // Mesh operations + + /** \brief Adds a `Mesh_object_io` to the current `Mesh_object_io`. + * + * In order to concatenate objects (ie.\ compute their disjoint union), the function updates indices of the simplices of `mesh` (such that the numbering of `mesh` vertices starts after vertices of `this`). + * + * \param mesh Set of simplices to concatenate to the current mesh. + */ + void push_back(const Mesh_object_io &mesh) { + size_t off = _nvertices ; // The index of all the cells of mesh has to be incremented by off + _nvertices += mesh._nvertices ; + _ncells += mesh._ncells ; + // Append all the vertices + for (size_t i=0; i> _nvertices >> _ncells >> _nedges; + + _nodes.resize(_nvertices) ; + for(auto i=0; i < _nvertices; ++i) { + if (!get_next_uncommented_line(infile,info)) { + return false; + } + std::istringstream info_stream(info); + std::array p; + info_stream >> p[0] >> p[1] >> p[2] ; + _nodes[i] = Point(p[0], p[1], p[2]) ; + } + + // 4 - the actual faces + _cells.resize(_ncells); + for(auto i=0; i < _ncells; ++i) { + if (!get_next_uncommented_line(infile,info)) { + return false; + } + std::istringstream info_stream(info); + unsigned long n; + unsigned long index; + info_stream >> n; + Io_cell_type c ; + // Read vertex indices + for (auto j = 0; j < n; ++j) { + info_stream >> index; + c.push_back(index) ; + } + // Sort the vector + std::sort(c.begin(), c.end()); + // Insert the cell + _cells[i] = c ; + _dim = (c.size()-1>_dim)?c.size()-1:_dim ; + } + + infile.close(); + return true; + } + + /** \brief Exports triangles (simplices of dimension 2) of the current object to an %OFF file. */ + bool write_off(const std::string &filename) const { + // 0 - open input file + std::ofstream outfile(filename); + if(!outfile.is_open()) { + std::cerr << "Warning: cannot open file " << filename << std::endl ; + // failed to open the file + return false; + } + // 1 - header + outfile << "OFF" << std::endl ; + outfile << _nvertices << " " << cells_of_dim(2) << " " << _nedges << std::endl ; + // 2 - nodes + for (size_t i=0; i<_nvertices; ++i) + { + outfile << _nodes.at(i)[0] << " " << _nodes.at(i)[1] << " " << _nodes.at(i)[2] << std::endl ; + } + // 3 - cells (export only triangles) + for (size_t i=0; i<_ncells; ++i) + { + const size_t ni = _cells.at(i).size() ; + if (ni == 3) + { + outfile << ni << " " ; + for (size_t k : _cells.at(i)) + outfile << k << " " ; + outfile << std::endl ; + } + } + outfile.close() ; + return true; + } + + // VTK + /** \brief Exports cells of the current object to an %VTK file. + * + * \exception Invalid_dimension If the %dimension is larger than 3, raises a `%std::invalid_argument` error. + */ + void write_vtk(const std::string &filename) + { + if (abs(_dim) > 3) { + std::cerr << "Mesh_object_io cannot be exported to VTK: dimension larger than 3"; + throw std::invalid_argument("Mesh_object_io cannot be exported to VTK: dimension larger than 3"); + } + + std::vector chains{_cells} ; + write_vtk(filename, _nodes, chains) ; + } + + // SIMP + /** \brief Exports cells to a %SIMP file. */ + bool write_simp(const std::string &filename) { + // 0 - open input file + std::ofstream outfile(filename); + if(!outfile.is_open()) { + std::cerr << "Warning: cannot open file " << filename << std::endl ; + // failed to open the file + return false; + } + // 1 - write cells + for (size_t i=0; i<_ncells; ++i) { + const Io_cell_type cell = _cells.at(i) ; + for (size_t c : cell) + outfile << c << " " ; + outfile << std::endl ; + } + outfile.close() ; + return true; + } + + /** \brief Imports cells from a %SIMP file. */ + bool read_simp(const std::string &filename) { + int d = 0 ; + // 0 - open input file + std::ifstream infile(filename); + if(!infile.is_open()) { + // failed to open the file + std::cerr << "Warning: file " << filename << " does not exist" << std::endl ; + return false; + } + std::size_t line_number = 0; + while ( !(infile.eof()) ) { + ++line_number; + std::string line; + getline( infile, line ); + // Check that line is sanitized. If not, throw. + for ( size_t i = 0; i < line.size(); ++ i ) { + if ( ! ( std::isspace(line[i]) || std::isdigit(line[i]) ) ) { + std::cerr << "Fatal Error:\n Cannot parse line #" << line_number << " of " << filename << "\n"; + std::cerr << " --> " << line << "\n"; + throw std::runtime_error("File Parsing Error: Invalid file"); + } + } + Io_cell_type cell ; + std::istringstream is( line ); + size_t v; + // Read vertex indices + while ( is >> v ) + cell.push_back(v); + // Sort the vector of indices + std::sort(cell.begin(), cell.end()); + // Add this simplex to cells + if (!(cell.empty())) + { + ++_ncells ; + _cells.push_back(cell) ; + const int dcell = cell.size()-1 ; + if (dcell > d) + d = dcell ; + } + } + _dim = d ; + infile.close() ; + return true ; + } + + /** \brief Reads nodes from a file in tetgen %NODES format. + * + * See \ref secHDVFTetgen "user manual" for details on the file format. + */ + bool read_nodes_file(const std::string &filename) { + _nvertices = read_nodes(filename, &_nodes) ; + return true; + } + + /** \brief Prints compact informations on the object into a stream. + * + * Prints the dimension, number of nodes, number of cells and number or cells by dimension. + */ + std::ostream& print_infos (std::ostream& out_stream = std::cout) const { + out_stream << "Mesh_object_io infos - dimension : "<< _dim << ", nodes : " << _nodes.size() << ", cells : " << _cells.size() << std::endl ; + for (int q = 0; q <= _dim; ++q) + out_stream << "cells of dimension " << q << " : " << cells_of_dim(q) << std::endl ; + return out_stream; + } + + /** \brief Prints compact informations on the object into a stream. + * + * Prints the dimension, number of nodes, number of cells and number or cells by dimension. + */ + friend std::ostream& operator<<(std::ostream& out_stream, const Mesh_object_io& mesh_io) { + return mesh_io.print_infos(out_stream); + } + + // Mesh computations + + /** \brief Returns the centroid of the object's nodes. */ + Point centroid() const { + return CGAL::centroid(_nodes.begin(), _nodes.end()) ; + } + + /** \brief Returns maximum distance from a point `bary` to the object's nodes. */ + double radius(const Point &bary) const { + double r = 0 ; + for (Point v : _nodes) + { + r = (std::max) (r, sqrt(squared_distance(v,bary))) ; + } + return r ; + } + + /** \brief Returns the bounding box of the object's nodes. */ + Bbox bbox(double ratio=1.) const { + return bounding_box(_nodes.begin(), _nodes.end()) .bbox(); + // @todo deal with ratio + } +private: + void check_dimension() { + if (_dim > 0) { // exact dimension + for (Io_cell_type cell : _cells) { + if (cell.size() != (_dim+1)) + throw std::runtime_error("Mesh has a cell of inconsistent dimension") ; + } + } + else { // max dim + for (Io_cell_type cell : _cells) { + if ((cell.size() > (-_dim+1))) + throw std::runtime_error("Mesh has a cell of inconsistent dimension") ; + } + } + } +} ; + + + + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + + +#endif // CGAL_HDVF_MESH_OBJECT_H diff --git a/HDVF/include/CGAL/HDVF/Simplex.h b/HDVF/include/CGAL/HDVF/Simplex.h new file mode 100644 index 000000000000..64746ab9c075 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Simplex.h @@ -0,0 +1,150 @@ + +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + + +#ifndef CGAL_HDVF_SIMPLEX_H +#define CGAL_HDVF_SIMPLEX_H + +#include + +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFChainComplexesClasses + + The class `Simplex` is used by the class `Abstract_simplicial_chain_complex` to represent a simplex (i.e.\ cells of a simplicial complex). + + Simplices are described by the *ordered vector* of the indices of their vertices (see the documentation of `Abstract_simplicial_chain_complex` for examples). + + */ + +class Simplex { + // Friend class: Abstract_simplicial_chain_complex + template + friend class Abstract_simplicial_chain_complex ; + +public: + /** \brief Type of simplices representation. + * + * Simplices are stored as a sorted vector of vertex indices (a simplex of dimension \f$q\f$ has \f$q+1\f$ vertices and the vector must be **sorted**). + **/ + typedef std::vector Simplex_data; +private: + // Indices of the simplex vertices - sorted vector + Simplex_data _vertices; + + public: + /** \brief Constructor from a vector of vertex indices. + * + * \param vertices Vector of the simplex vertex indices (a simplex of dimension \f$q\f$ has \f$q+1\f$ vertices and the vector must be **sorted**). + * \param sort_data If `true` vectors of vertex indices are sorted, if `false` indices are assumed to be sorted (faster). + */ + Simplex(const Simplex_data& vertices, bool sort_data = false) : _vertices(vertices) { + if (sort_data) + std::sort(_vertices.begin(), _vertices.end()); + } + + /** \brief Constant iterator over the vertices of a simplex. */ + typedef Simplex_data::const_iterator const_iterator ; + + /** \brief Returning a constant iterator on the first vertex of the simplex. */ + const_iterator cbegin () + { + return _vertices.cbegin() ; + } + /** \brief Returning a constant past-the-end iterator of the simplex. */ + const_iterator cend () { return _vertices.cend() ; } + + /** \brief Returns the dimension of a simplex. + * + * A simplex of dimension \f$q\f$ has \f$q+1\f$ vertices. + */ + int dimension() const + { + return _vertices.size() - 1; + } + + /** \brief Returns the boundary of a simplex. + * + * The method returns the vector of the simplices in the boundary of the object. + */ + + std::vector boundary() const + { + std::vector result; + result.reserve(_vertices.size()); + + auto it = _vertices.begin(); + for (size_t i = 0; i < _vertices.size(); ++i) { + Simplex_data simplex_vertices; + std::copy_if(_vertices.begin(), _vertices.end(), std::inserter(simplex_vertices, simplex_vertices.begin()), + [it](const size_t& vertex) { return vertex != *it; }); + result.emplace_back(simplex_vertices); + ++it; + } + + return result; + } + + /** \brief Gets the set of vertex indices of the simplex. */ + const Simplex_data& vertices() const + { + return _vertices ; + } + + /** \brief Comparison operator. + * + * Compare the object with another simplex according to the lexicographical order on vertex indices. + */ + bool operator<(const Simplex& other) const { + return _vertices < other._vertices; + } + + /** \brief Equality operator. + * + * As vertex indices must be store in increasing order, comparison just comes to compare the ordered vector of indices. + */ + bool operator==(const Simplex& other) const { + return _vertices == other._vertices; + } + + + /** \brief Outputs a simplex. + */ + friend std::ostream& operator<<(std::ostream& out, const Simplex& simplex) + { + out << "<"; + bool first = true; + for (size_t vertex : simplex._vertices) { + if (!first) { + + out << " "; + } + out << vertex; + first = false; + } + out << ">"; + return out; + } +}; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_SIMPLEX_H diff --git a/HDVF/include/CGAL/HDVF/Simplicial_chain_complex.h b/HDVF/include/CGAL/HDVF/Simplicial_chain_complex.h new file mode 100644 index 000000000000..f294b1f6b798 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Simplicial_chain_complex.h @@ -0,0 +1,397 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_SIMPLICIAL_CHAIN_COMPLEX_H +#define CGAL_HDVF_SIMPLICIAL_CHAIN_COMPLEX_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +// Forward declaration of SimpComplexTools +//template class Duality_simplicial_complex_tools ; + +/*! + \ingroup PkgHDVFChainComplexesClasses + + The class `Simplicial_chain_complex` refines the `Abstract_simplicial_chain_complex` class by assigning coordinates to vertices (i.e.\ 0-simplices). Hence, vtk output is available. + + \cgalModels{GeometricChainComplex} + + \tparam CoefficientRing a model of the `IntegralDomainWithoutDivision` concept. + \tparam Traits a geometric traits class model of the `HDVFTraits` concept. + \tparam SparseMatrixStruct a model of `SparseMatrixStructure` providing templates of sparse chains and sparse matrices built over a given model of `SparseChain`. + */ + +template > +class Simplicial_chain_complex : public Abstract_simplicial_chain_complex { +public: + + /** \brief Type of vertex coordinates */ + typedef typename Traits::Point Point ; + /** \brief Type of vtk export vertex coordinates */ + typedef typename Traits::Point3 Point3 ; + + /** \brief Type of parent `Abstract_simplicial_chain_complex` class. */ + typedef Abstract_simplicial_chain_complex Base; + +protected: + /* \brief Vector of vertex coordinates */ + std::vector _coords ; + +private: + /* \brief Vector of VTK types associated to cells in each dimension + e.g. {1, 3, 5, 10} */ + static const std::vector VTK_simptypes ; + +public: + + /** + * \brief %Default constructor: builds an empty simplicial complex. + */ + Simplicial_chain_complex() {} ; + + /** + * \brief Constructor from a Mesh_object_io. + * + * Builds a simplicial complex from a Mesh_object_io describing maximal faces and vertex coordinates. + + * \exception Invalid_geometry If the `Mesh_object_io` mesh does not provide the right number of vertices coordinates, throws a `%std::runtime_error` exception. + */ + Simplicial_chain_complex(const Mesh_object_io& mesh) : Abstract_simplicial_chain_complex(mesh), _coords(mesh.nodes()) { + if (_coords.size() != this->number_of_cells(0)) + { + std::cerr << "Simplicial_chain_complex. Error, wrong number of points provided.\n"; + throw std::runtime_error("Geometry of points invalid."); + } + } ; + + /* + * \brief Assignment + */ + Simplicial_chain_complex& operator= (const Simplicial_chain_complex& complex) + { + this->Abstract_simplicial_chain_complex::operator=(complex) ; + _coords = complex._coords ; + return *this ; + } + + /* \brief Gets the vector of vertex coordinates */ + const std::vector& points() const + { + return _coords ; + } + + /* \brief Gets the coordinates of the ith dimension-0 simplex + + * \warning This does not come to return vertex indices, as dimension 0 simplices enumerate vertices in any order. For instance, if an abstract simplicial complex is built from 3 vertices {1,2,3} such that the enumeration of dimension 0 simplices is: + * id0: 3, id1 : 2, id2: 1 + * then the bottom_faces of the 1-simplex {1,2} are two 0-simplices with id 2 and 1. + */ + Point point(size_t i) const + { + CGAL_precondition(this->is_valid_cell(i,0)); + const Simplex simpl(this->_ind2simp.at(0).at(i)) ; + const std::vector verts(simpl.vertices()) ; + const size_t id(*(verts.cbegin())) ; + return _coords.at(id); + } + + // VTK export + + /* + * \brief Exports a simplicial complex (plus, optionally, labels) to a VTK file. + * + * The method generates legacy text VTK files. Labels are exported as such in a VTK property, together with CellID property, containing the index of each cell. + * + * \tparam LabelType Type of labels provided (default: int). + * + * \param K Simplicial complex exported. + * \param filename Output file root (output filenames will be built from this root). + * \param labels Pointer to a vector of labels in each dimension. (*labels).at(q) is the set of integer labels of cells of dimension q. If labels is NULL, only CellID property is exported. + * \param label_type_name Typename used in vtk export (e.g. "int" or "unsigned_long", see VTK manual ). + */ + template + static void chain_complex_to_vtk(const Simplicial_chain_complex &K, const std::string &filename, const std::vector > *labels=NULL, std::string label_type_name = "int") + { + typedef Simplicial_chain_complex ChainComplex; + bool with_scalars = (labels != NULL) ; + + // Load out file... + std::ofstream out ( filename, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "SimpComplex_to_vtk. Fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + // Header + out << "# vtk DataFile Version 2.0" << std::endl ; + out << "generators" << std::endl ; + out << "ASCII" << std::endl ; + out << "DATASET UNSTRUCTURED_GRID" << std::endl ; + + // Points + size_t nnodes = K._coords.size() ; + out << "POINTS " << nnodes << " double" << std::endl ; + const std::vector& coords(K.points()) ; + for (size_t n = 0; n < nnodes; ++n) + { + Point3 p(Traits::to_point3(coords.at(n))) ; + out << p ; + out << std::endl ; + } + + size_t ncells_tot = 0, size_cells_tot = 0 ; + std::vector types ; + std::vector scalars ; + std::vector ids ; + // all cells must be printed + { + // Cells up to dimension 3 + // Size : size of a cell of dimension q : q+1 + for (int q=0; q<=K._dim; ++q) + { + ncells_tot += K.number_of_cells(q) ; + const size_t size_cell = q+1 ; + size_cells_tot += (size_cell+1)*K.number_of_cells(q) ; + } + out << "CELLS " << ncells_tot << " " << size_cells_tot << std::endl ; + // Output cells by increasing dimension + for (int q=0; q<=K._dim; ++q) + { + const size_t size_cell = q+1 ; + for (size_t id =0; id < K.number_of_cells(q); ++id) + { + Simplex verts(K._ind2simp.at(q).at(id)) ; + out << size_cell << " " ; + for (typename Simplex::const_iterator it = verts.cbegin(); it != verts.cend(); ++it) + out << *it << " " ; + out << std::endl ; + types.push_back(ChainComplex::VTK_simptypes.at(q)) ; + if (with_scalars) + { + scalars.push_back((*labels).at(q).at(id)) ; + ids.push_back(id) ; + } + } + } + + // CELL_TYPES + out << "CELL_TYPES " << ncells_tot << std::endl ; + for (int t : types) + out << t << " " ; + out << std::endl ; + } + + if (with_scalars) + { + // CELL_LABEL + out << "CELL_DATA " << ncells_tot << std::endl ; + out << "SCALARS Label " << label_type_name << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (LabelType s : scalars) + out << s << " " ; + out << std::endl ; + // CELL_IDs + out << "SCALARS CellId " << "int" << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (size_t i : ids) + out << i << " " ; + out << std::endl ; + } + out.close() ; + } + + /* + * \brief Exports a chain over a simplicial complex to a VTK file. + * + * The method generates legacy text VTK files. All the cells of the chain with non zero coefficient are exported. If a cellId is provided, labels are exported in a VTK property (2 for all cells, 0 for cell of index cellId). The index of each cell is exported in a CellID property. + * + * \param K Simplicial complex exported. + * \param filename Output file root (output filenames will be built from this root). + * \param chain Sparse_chain exported (all the cells with non-zero coefficients in the chain are exported to vtk). + * \param q Dimension of the cells of the chain. + * \param cellId If cellID is not -1 (that is MAX_SIZE_T), labels are exported to distinguish cells of the chain (label 2) from cellId cell (label 0). + */ + static void chain_to_vtk(const Simplicial_chain_complex &K, const std::string &filename, const typename Base::template Sparse_chain_type& chain, int q, size_t cellId = -1) ; +}; + +// Initialization of static VTK_simptypes +template const +std::vector Simplicial_chain_complex::VTK_simptypes({1, 3, 5, 10}); + + +// chain_to_vtk +template +void Simplicial_chain_complex::chain_to_vtk(const Simplicial_chain_complex &K, const std::string &filename, const typename Simplicial_chain_complex::Base:: template Sparse_chain_type& chain, int q, size_t cellId) +{ + typedef Simplicial_chain_complex ChainComplex ; + if (K._coords.size() != K.number_of_cells(0)) + { + std::cerr << "SimpComplex_chain_to_vtk. Error, wrong number of points provided.\n"; + throw std::runtime_error("Geometry of points invalid."); + } + + bool with_scalars = (cellId < K.number_of_cells(q)) ; + + // Load out file... + std::ofstream out ( filename, std::ios::out | std::ios::trunc); + + if ( ! out . good () ) { + std::cerr << "SimpComplex_chain_to_vtk. Fatal Error:\n " << filename << " not found.\n"; + throw std::runtime_error("File Parsing Error: File not found"); + } + + // Header + out << "# vtk DataFile Version 2.0" << std::endl ; + out << "generators" << std::endl ; + out << "ASCII" << std::endl ; + out << "DATASET UNSTRUCTURED_GRID" << std::endl ; + + // Points + size_t nnodes = K._coords.size() ; + out << "POINTS " << nnodes << " double" << std::endl ; + const std::vector& coords(K.points()) ; + for (size_t n = 0; n < nnodes; ++n) + { + Point3 p(Traits::to_point3(coords.at(n))) ; + out << p ; + out << std::endl ; + } + + size_t ncells_tot = 0, size_cells_tot = 0 ; + std::vector types ; + std::vector scalars ; + std::vector ids ; + + // output only cells of the chain (dimension q) + { + // 1 - Compute the number of cells / size of encoding + { + const size_t size_cell = q+1 ; + for (size_t id =0; id < K.number_of_cells(q); ++id) + { + if (!chain.is_null(id)) + { + ++ncells_tot; + size_cells_tot += (size_cell+1) ; + } + } + } + // 2 - Output cells + out << "CELLS " << ncells_tot << " " << size_cells_tot << std::endl ; + + { + const size_t size_cell = q+1 ; + for (size_t id =0; id < K.number_of_cells(q); ++id) + { + if (!chain.is_null(id)) + { + Simplex verts(K._ind2simp.at(q).at(id)) ; + out << size_cell << " " ; + for (typename Simplex::const_iterator it = verts.cbegin(); it != verts.cend(); ++it) + out << *it << " " ; + out << std::endl ; + types.push_back(ChainComplex::VTK_simptypes.at(q)) ; + if (with_scalars) + { + ids.push_back(id) ; + if (id != cellId) + scalars.push_back(2) ; + else + scalars.push_back(0) ; + } + } + } + } + // CELL_TYPES + out << "CELL_TYPES " << ncells_tot << std::endl ; + for (int t : types) + out << t << " " ; + out << std::endl ; + } + + if (with_scalars) + { + // CELL_TYPES + out << "CELL_DATA " << ncells_tot << std::endl ; + out << "SCALARS Label " << "int" << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (int s : scalars) + out << s << " " ; + out << std::endl ; + // CELL_IDs + out << "SCALARS CellId " << "int" << " 1" << std::endl ; + out << "LOOKUP_TABLE default" << std::endl ; + for (size_t i : ids) + out << i << " " ; + out << std::endl ; + } + out.close() ; +} + +} /* end namespace Homological_discrete_vector_field */ + +namespace IO { +/* + * \brief Exports a column chain `chain` to %VTK files. + * + * Exports cells of `chain` with a non null coefficient to a %VTK file, with a distinguished cell (for instance the underlying critical cell to a generator). Two properties are exported: + * - the `Label` property (2 for the distinguished cell, 0 for other cells) + * - the `CellId` property containing the "basis" index of the cell. + * + * \param K Underlying cubical complex. + * \param filename Output file name. + * \param chain Column chain exported to %VTK. + * \param q Dimension of the chain. + * \param cellId Index of the distinguished cell. + * + * \exception File_not_found If the file `filename` cannot be created and opened, throw a `%std::runtime_error` exception. + */ +template > +void write_VTK(const CGAL::Homological_discrete_vector_field::Simplicial_chain_complex &K, const std::string &filename, const typename Homological_discrete_vector_field::Simplicial_chain_complex::Base:: template Sparse_chain_type& chain, int q, size_t cellId) { + CGAL::Homological_discrete_vector_field::Simplicial_chain_complex::chain_to_vtk(K, filename, chain, q, cellId); +} + +/* + * \brief Exports a simplicial chain complex (plus, optionally, labels) to a %VTK file. + * + * The method generates legacy text %VTK files. Labels are exported as such in a %VTK property, together with CellID property, containing the index of each cell. + * + * \tparam CoefficientRing Coefficient ring of the underlying complex `K`. + * \tparam Traits Traits class of the underlying complex `K`. + * \tparam LabelType Type of labels provided (default: int). + * + * \param K Simplicial chain complex exported. + * \param filename Output file root (output filenames will be built from this root). + * \param labels Pointer to a vector of labels in each dimension. (*labels).at(q) is the set of integer labels of cells of dimension q. If labels is NULL, only CellID property is exported. + * \param label_type_name Typename used in %VTK export (e.g. "int" or "unsigned_long", see VTK manual ). + * + * \exception File_not_found If the file `filename` cannot be created and opened, throw a `%std::runtime_error` exception. + */ +template , typename LabelType = int> +static void write_VTK(const CGAL::Homological_discrete_vector_field::Simplicial_chain_complex &K, const std::string &filename, const std::vector > *labels=NULL, std::string label_type_name = "int") { + CGAL::Homological_discrete_vector_field::Simplicial_chain_complexchain_complex_to_vtk(K, filename, labels, label_type_name); +} + +} /* end namespace IO */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_SIMPLICIAL_CHAIN_COMPLEX_H diff --git a/HDVF/include/CGAL/HDVF/Sub_chain_complex_mask.h b/HDVF/include/CGAL/HDVF/Sub_chain_complex_mask.h new file mode 100644 index 000000000000..9d63c48ad56f --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Sub_chain_complex_mask.h @@ -0,0 +1,351 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac + +#ifndef CGAL_HDVF_SUB_CHAIN_COMPLEX_MASK_H +#define CGAL_HDVF_SUB_CHAIN_COMPLEX_MASK_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Homological_discrete_vector_field { + +/*! + \ingroup PkgHDVFToolsClasses + + The class `Sub_chain_complex_mask` is a technical class implementing a sub chain complex. A sub chain complex \f$A\f$ of a chain complex \f$K\f$ is a subset \f$A\subseteq K\f$ such that the restricted boundary operator \f$\partial_A = \partial_K|_A\f$ still satisfies \f$\partial_A^2 = 0\f$. + + The `Sub_chain_complex_mask` class is used to compute reduced homology. This class is based on a set of bitboard masks (one in each dimension) used to define sub chain complexes and their associated reduction encoded in sub-sparse matrices (`OSM::Sub_sparse_matrix` class). Technically, `Sub_chain_complex_mask` are used to partially screen chain complexes and chains in associated boundary matrices, and hence compute homology "locally". + + \warning For efficiency reasons, when a `Sub_chain_complex_mask` is used to screen sparse matrices (with the `screen_matrices()` method), screening is **only** performed on the major direction of matrices (thus column-major matrices are restricted over columns and row-major matrices are restricted over rows). Iterators are restricted accordingly. But chains themselves are not restricted.
+ However, if `A` is a proper sub-complex of `K` (that is, closed with respect to faces), chains automatically comply with the screening. Indeed, for any \f$q\f$-cell \f$\sigma\in A\f$ (thus the corresponding bit is on in the mask), all the faces of \f$\sigma\f$ also belong to \f$A\f$. Thus for any \f$q-1\f$-cell \f$\tau\f$ in the boundary of \f$\sigma\f$ (that is, such that \f$\langle\partial_k(\sigma),\tau\rangle\neq 0\f$), \f$\tau\f$ belongs to \f$A\f$ (and thus the corresponding bit in the mask is also on). + + \tparam ChainComplex a model of the `AbstractChainComplex` concept (type of the chain complex screened by `Sub_chain_complex_mask`). + */ + +template +class Sub_chain_complex_mask +{ +public: + /*! \brief Chain complex type */ + typedef ChainComplex Chain_complex; + + /*! \brief Type of coefficients used to compute homology. */ + typedef typename Chain_complex::Coefficient_ring Coefficient_ring; + +protected: + /** \brief Dimension of the underlying complex. */ + int _dim ; + /** \brief Vector of bitboards coding masks in each dimension. */ + std::vector _sub ; + /** \brief Number of cells in the mask in each dimension. */ + std::vector _nb_cells ; + /** \brief Constant reference to the underlying complex. */ + const ChainComplex& _K ; + /** \brief Is full sub_complex. + * This boolean flag is true is all bits in the bitboards are on. */ + bool _full ; +private: + /** Load a set of cells and close the enumeration down. + * + * The method activates bits corresponding to the set of cells encoded by `cells` and recursively activates all bits corresponding to their faces. + * + * \param cells A vector containing, in each dimension, a vector of cell indices. + */ + void down_closure (const std::vector > &cells) + { + std::vector > faces(cells.size()) ; + bool rec_needed = false ; + for (int q = 0; q < cells.size(); ++q) + { + for (int i : cells.at(q)) + { + if (!_sub.at(q).is_on(i)) + { + _sub.at(q).set_on(i) ; + ++_nb_cells.at(q) ; + if (q>0) // Then faces must be considered + { + // Add all its faces to faces.at(q-1) + OSM::Sparse_chain bnd(_K.d(i,q)) ; + for (typename OSM::Sparse_chain::const_iterator it = bnd.cbegin(); it != bnd.cend(); ++it) + { + const int c(it->first) ; + if (!_sub.at(q-1).is_on(c)) + { + faces.at(q-1).push_back(c) ; + rec_needed = true ; + } + } + } + } + } + } + // Call recursively + if (rec_needed) + down_closure(faces, _K); + } + + /** Load a set of cells (without closure). + * + * The method activates bits corresponding to the set of cells encoded by `cells`. + * + * \param cells A vector giving, for each dimension, a vector of cell indices. + */ + void set_cells (const std::vector > &cells) + { + for (int q = 0; q < cells.size(); ++q) + { + for (int i : cells.at(q)) + { + _sub.at(q).set_on(i) ; + ++_nb_cells.at(q) ; + } + } + } + +public: + /** \brief Constructor from a complex. + * + * Build a `Sub_chain_complex_mask` associated to `K` with all bits set to 1 in the masks if `full` is true, and all bits set to 0 otherwise. + * + * \param K A constant reference to the underlying complex. + * \param full Build full / empty masks (default: full). + */ + Sub_chain_complex_mask(const Chain_complex& K, bool full=true) : _K(K) + { + _dim = K.dimension() ; + _sub.resize(_dim+1) ; + _nb_cells.resize(_dim+1) ; + // Create Bitboards + for (int q=0; q<=K.dimension(); ++q) + { + if (full) + { + _sub.at(q) = OSM::Bitboard(K.number_of_cells(q),false) ; // all cells to 1 + _full = true ; + } + else + _sub.at(q) = OSM::Bitboard(K.number_of_cells(q),true) ; // all cells to 0 + _nb_cells.at(q) = K.number_of_cells(q) ; + } + } + + + /** \brief Constructor from an enumeration of cells. + * + * Build masks associated to the underlying complex `K` with all bits corresponding to `cells` (and their faces if `close` is true) set to 1. + * + * \param K A constant reference to the underlying complex. + * \param cells A vector containing, in each dimension, a vector of cell indices. + * \param close If this boolean is true, the faces of `cells` are also set to 1. + */ + Sub_chain_complex_mask(const Chain_complex& K, const std::vector > &cells, bool close = true) : _K(K) + { + _dim = K.dimension() ; + _sub.resize(_dim+1) ; + _nb_cells.resize(_dim+1) ; + _full = true ; + // Create Bitboards + for (int q=0; q<=K.dimension(); ++q) + { + _sub.at(q) = OSM::Bitboard(K.number_of_cells(q)) ; + } + + // Set bits + if (close) + down_closure(cells, K) ; + else + set_cells(cells, K) ; + + for (int q=0; q<=K.dimension(); ++q) + { + _full = (_full && (_nb_cells.at(q) == _sub.at(q).size())) ; + } + } + + /** + * \brief Copy constructor. + */ + Sub_chain_complex_mask(const Sub_chain_complex_mask& otherToCopy) : _K(otherToCopy._K) + { + _dim = otherToCopy._dim ; + _nb_cells = otherToCopy._nb_cells ; + _full = otherToCopy._full ; + _sub = otherToCopy._sub ; + } + + /** \brief Assignment + * + * \warning The operator argument must provide a sub chain complex mask over the *same underlying chain complex*. It not so, the assignment will throw an exception. + */ + Sub_chain_complex_mask& operator= (const Sub_chain_complex_mask& otherToCopy) + { + // Check that otherToCopy and current mask share the same chain complex + if (_K.id() != otherToCopy._K.id() ) + throw("Error, operator= can only copy mask over the same chain complex."); + + // Perform assignment + _dim = otherToCopy._dim ; + _sub = otherToCopy._sub ; + _nb_cells = otherToCopy._nb_cells ; + _full = otherToCopy._full ; + return *this; + } + + /** \brief Returns the complement of the mask. + * + * The method return a new `Sub_chain_complex_mask` containing the complement of the current mask (0 and 1 bits in the mask are exchanged). + */ + Sub_chain_complex_mask complement() + { + Sub_chain_complex_mask cSub(*this) ; + // Complement bitboards + for (int q=0; q<=_dim; ++q) + { + cSub._sub.at(q).bit_not() ; + cSub._nb_cells.at(q) = _sub.at(q).size() - _nb_cells.at(q) ; + cSub._full = ~_full ; + } + return cSub ; + } + + /** \brief Gets a bit of the mask (bit i in dimension q). */ + inline bool get_bit(int q, int i) const + { + return _sub.at(q).is_on(i) ; + } + + /** \brief Sets a bit to 1 (bit i in dimension q). */ + inline void set_bit_on(int q, int i) + { + if (!_sub.at(q).is_on(i)) + { + _nb_cells[q]++ ; + _sub.at(q).set_on(i) ; + } + } + + /** \brief Sets a bit to 0 (bit i in dimension q). */ + inline void set_bit_off(int q, int i) + { + if (_sub.at(q).is_on(i)) + { + _nb_cells[q]--; + _sub.at(q).set_off(i) ; + } + } + + /** \brief Gets the bitboards of the sub chain complex. + * + * Returns a constant reference to the vector of bitboards in each dimension. + */ + inline const std::vector& get_bitboards() const + { + return _sub ; + } + + /** \brief Gets the bitboard of the sub chain complex in dimension q. + + @todo Correct the reference to local temporary + */ + inline const OSM::Bitboard& get_bitboard(int q) const + { + if ((q>=0) && (q<=_dim)) + return _sub.at(q) ; + else + return OSM::Bitboard(0); + } + + /** \brief Sets the bitboard of the sub chain complex in dimension q. */ + inline void set_bitboard(const OSM::Bitboard& bb, int q) + { + _sub.at(q) = bb ; + } + + /** \brief Sets all bits to 1. */ + inline void set_full() { + for (int q=0; q<=_dim; ++q) + _sub.at(q) = OSM::Bitboard(_sub.at(q).size(), false); + } + + /** \brief Screens a sequence of `OSM::Sub_sparse_matrix` (in each dimension). + * + * Given a sequence of matrices (vector of `OSM::Sub_sparse_matrices`) sets the masks of `Sub_sparse_matrices` in each dimension to the current `Sub_chain_complex_mask`. + * + * \warning For efficiency, screening is performed on the major direction of matrices (so along columns for column-major matrices and along row for row-major matrices). + * + * \param matrices A vector of `OSM::Sub_sparse_matrix` (in each dimension). + */ + template typename SCT> + void screen_matrices(std::vector >& matrices) + { + for (int q=0; q<=_K.dimension(); ++q) + { + matrices.at(q).set_sub(_sub.at(q)) ; + } + } + + /** + * \brief Restricts a chain in a given dimension to the sub chain complex mask. + * + * Nullify all coefficients out of the mask. + * + * \param chain The chain restricted. + * \param q Dimension of the chain. + */ + template typename SCT> + inline void screen_chain(SCT& chain, int q) + { + std::vector indices; + for (typename SCT::iterator it = chain.begin(); it != chain.end(); ++it) + { + if (!get_bit(q, it->first)) + indices.push_back(it->first); + } + chain/=indices; + } + + /*! \brief Overload of the `<<`operator for `Sub_chain_complex_mask`. + */ + friend std::ostream & operator << (std::ostream & out, const Sub_chain_complex_mask & sub) + { + for (int q = 0; q <= sub._dim; ++q) + out << "dim " << q << " : " << sub._sub.at(q) << std::endl ; + return out ; + } + + /** \brief Comparison operator. + * + * \param other Other Sub_chain_complex_mask to compare. + */ + bool operator==(const Sub_chain_complex_mask& other) const { + bool res(_dim == other._dim); + for (int q=0; res &&(q<=_dim); ++q) { + // Compare number of cells + res = res && (_nb_cells.at(q) == other._nb_cells.at(q)); + // Compare _sub + res = res && (_sub.at(q) == other._sub.at(q)); + } + } +}; + +} /* end namespace Homological_discrete_vector_field */ +} /* end namespace CGAL */ + +#endif // CGAL_HDVF_SUB_CHAIN_COMPLEX_MASK_H diff --git a/HDVF/include/CGAL/HDVF/Sub_sparse_matrix.h b/HDVF/include/CGAL/HDVF/Sub_sparse_matrix.h new file mode 100644 index 000000000000..dc88c9003598 --- /dev/null +++ b/HDVF/include/CGAL/HDVF/Sub_sparse_matrix.h @@ -0,0 +1,258 @@ +// Copyright (c) 2025 LIS Marseille (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Alexandra Bac +// Kevin Fedyna + +#ifndef CGAL_OSM_SUB_SPARSE_MATRIX_H +#define CGAL_OSM_SUB_SPARSE_MATRIX_H + + +#include + +#include +#include + +namespace CGAL { +namespace OSM { + +// Forward declaration of Sub_sparse_matrix +#ifndef DOXYGEN_RUNNING +template typename SparseChainType> +class Sub_sparse_matrix; +#endif + +/*! + \ingroup PkgHDVFAlgorithmClasses + + The structure `Sub_sparse_matrix_structure` provides a user friendly interface for sub-sparse matrices built over a given `SparseChain`model (this is actually a Curryfication of the `Sub_parse_matrix_core` template). + + Given the template parameter `SparseChainType` (a model of the `SparseChain` concept), corresponding sparse chains and matrices templates are given by: + \code + Sub_sparse_matrix_structure:: template Sparse_chain_type + Sub_sparse_matrix_structure:: template Sparse_matrix_type + \endcode + + \cgalModels{SparseMatrix} + + \tparam SparseChainType a model of `SparseChain` used to store chains of the sparse matrix (default: `OSM::Sparse_chain`). + + \warning Check if the documentation of this structure is correct. + */ + +template