From 6f3aa21f0de4dcd53e3630e658f77c1fbc643d94 Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Wed, 15 Feb 2023 11:10:45 +0100 Subject: [PATCH 01/18] added a first implementation of multiparameter simplextrees --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 11 + .../Simplex_tree_node_explicit_storage.h | 7 +- src/python/CMakeLists.txt | 2 + src/python/gudhi/simplex_tree_multi.pxd | 102 ++ src/python/gudhi/simplex_tree_multi.pyx | 985 ++++++++++++++++++ .../include/Simplex_tree_interface_multi.h | 324 ++++++ src/python/include/Simplex_tree_multi.h | 183 ++++ src/python/include/utils/box.h | 153 +++ src/python/include/utils/debug.h | 125 +++ src/python/include/utils/line.h | 94 ++ src/python/include/utils/utilities.h | 262 +++++ 11 files changed, 2246 insertions(+), 2 deletions(-) create mode 100644 src/python/gudhi/simplex_tree_multi.pxd create mode 100644 src/python/gudhi/simplex_tree_multi.pyx create mode 100644 src/python/include/Simplex_tree_interface_multi.h create mode 100644 src/python/include/Simplex_tree_multi.h create mode 100644 src/python/include/utils/box.h create mode 100644 src/python/include/utils/debug.h create mode 100644 src/python/include/utils/line.h create mode 100644 src/python/include/utils/utilities.h diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index e241fa1f17..e726287829 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1754,6 +1754,17 @@ class Simplex_tree { /** \brief Upper bound on the dimension of the simplicial complex.*/ int dimension_; bool dimension_to_be_lowered_ = false; + +//MULTIPERS STUFF +public: + void set_number_of_parameters(unsigned int num){ + number_of_parameters_ = num; + } + unsigned int get_number_of_parameters() const{ + return number_of_parameters_; + } +private: + unsigned int number_of_parameters_; }; // Print a Simplex_tree in os. diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h index 63023daabe..2c434b3be6 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h @@ -31,11 +31,14 @@ struct Simplex_tree_node_explicit_storage : SimplexTree::Filtration_simplex_base typedef typename SimplexTree::Filtration_value Filtration_value; typedef typename SimplexTree::Simplex_key Simplex_key; - Simplex_tree_node_explicit_storage(Siblings * sib = nullptr, - Filtration_value filtration = 0) + Simplex_tree_node_explicit_storage(Siblings * sib, + Filtration_value filtration) //MULTIPERS : init to 0 not possible : children_(sib) { this->assign_filtration(filtration); } + Simplex_tree_node_explicit_storage() // Empty constructor necessary + : children_(nullptr) { + } /* * Assign children to the node diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 42141fbb32..0193774aeb 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -55,6 +55,7 @@ if(PYTHONINTERP_FOUND) # Cython modules set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'off_utils', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'simplex_tree', ") + set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'simplex_tree_multi', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'rips_complex', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'cubical_complex', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'periodic_cubical_complex', ") @@ -154,6 +155,7 @@ if(PYTHONINTERP_FOUND) set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'off_utils', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'simplex_tree', ") + set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'simplex_tree_multi', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'rips_complex', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'cubical_complex', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'periodic_cubical_complex', ") diff --git a/src/python/gudhi/simplex_tree_multi.pxd b/src/python/gudhi/simplex_tree_multi.pxd new file mode 100644 index 0000000000..f97f1f3748 --- /dev/null +++ b/src/python/gudhi/simplex_tree_multi.pxd @@ -0,0 +1,102 @@ +# This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. +# See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. +# Author(s): Vincent Rouvreau +# +# Copyright (C) 2016 Inria +# +# Modification(s): +# - 2022 David Loiseaux, Hannah Schreiber: adapt for multipersistence. +# - YYYY/MM Author: Description of the modification + +from cython cimport numeric +from libcpp.vector cimport vector +from libcpp.utility cimport pair +from libcpp cimport bool +from libcpp.string cimport string + +__author__ = "Vincent Rouvreau" +__copyright__ = "Copyright (C) 2016 Inria" +__license__ = "MIT" + +ctypedef int dimension_type +ctypedef vector[double] point_type +ctypedef double filtration_value_type +ctypedef vector[double] filtration_type +ctypedef vector[int] simplex_type +ctypedef vector[simplex_type] simplex_list +ctypedef vector[pair[pair[int,int], pair[double, double]]] edge_list +ctypedef vector[int] euler_char_list + +cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": + cdef cppclass Simplex_tree_options_multidimensional_filtration: + pass + + cdef cppclass Simplex_tree_multi_simplex_handle "Gudhi::Simplex_tree_interface::Simplex_handle": + pass + + cdef cppclass Simplex_tree_multi_simplices_iterator "Gudhi::Simplex_tree_interface::Complex_simplex_iterator": + Simplex_tree_multi_simplices_iterator() nogil + Simplex_tree_multi_simplex_handle& operator*() nogil + Simplex_tree_multi_simplices_iterator operator++() nogil + bint operator!=(Simplex_tree_multi_simplices_iterator) nogil + + cdef cppclass Simplex_tree_multi_skeleton_iterator "Gudhi::Simplex_tree_interface::Skeleton_simplex_iterator": + Simplex_tree_multi_skeleton_iterator() nogil + Simplex_tree_multi_simplex_handle& operator*() nogil + Simplex_tree_multi_skeleton_iterator operator++() nogil + bint operator!=(Simplex_tree_multi_skeleton_iterator) nogil + + cdef cppclass Simplex_tree_multi_boundary_iterator "Gudhi::Simplex_tree_interface::Boundary_simplex_iterator": + Simplex_tree_multi_boundary_iterator() nogil + Simplex_tree_multi_simplex_handle& operator*() nogil + Simplex_tree_multi_boundary_iterator operator++() nogil + bint operator!=(Simplex_tree_multi_boundary_iterator) nogil + + + cdef cppclass Simplex_tree_multi_interface "Gudhi::Simplex_tree_interface": + Simplex_tree_multi_interface() nogil + Simplex_tree_multi_interface(Simplex_tree_multi_interface&) nogil + filtration_type simplex_filtration(vector[int] simplex) nogil + void assign_simplex_filtration(vector[int] simplex, filtration_type filtration) nogil + void initialize_filtration() nogil + int num_vertices() nogil + int num_simplices() nogil + void set_dimension(int dimension) nogil + dimension_type dimension() nogil + dimension_type upper_bound_dimension() nogil + bool find_simplex(vector[int] simplex) nogil + bool insert(vector[int] simplex, filtration_type filtration) nogil + vector[pair[simplex_type, filtration_type]] get_star(vector[int] simplex) nogil + vector[pair[simplex_type, filtration_type]] get_cofaces(vector[int] simplex, int dimension) nogil + void expansion(int max_dim) nogil except + + void remove_maximal_simplex(simplex_type simplex) nogil + bool prune_above_filtration(filtration_type filtration) nogil + # bool make_filtration_non_decreasing() nogil + # void compute_extended_filtration() nogil + Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) nogil except + + void reset_filtration(filtration_type filtration, int dimension) nogil + bint operator==(Simplex_tree_multi_interface) nogil + # Iterators over Simplex tree + pair[simplex_type, filtration_type] get_simplex_and_filtration(Simplex_tree_multi_simplex_handle f_simplex) nogil + Simplex_tree_multi_simplices_iterator get_simplices_iterator_begin() nogil + Simplex_tree_multi_simplices_iterator get_simplices_iterator_end() nogil + vector[Simplex_tree_multi_simplex_handle].const_iterator get_filtration_iterator_begin() nogil + vector[Simplex_tree_multi_simplex_handle].const_iterator get_filtration_iterator_end() nogil + Simplex_tree_multi_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil + Simplex_tree_multi_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil + pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] get_boundary_iterators(vector[int] simplex) nogil except + + # Expansion with blockers + ctypedef bool (*blocker_func_t)(vector[int], void *user_data) + void expansion_with_blockers_callback(int dimension, blocker_func_t user_func, void *user_data) + + ## MULTIPERS STUFF + void reset_keys() nogil + int get_key(const simplex_type) nogil + void set_key(simplex_type, int) nogil + void fill_lowerstar(vector[double], int) nogil + simplex_list get_simplices_of_dimension(int) nogil + edge_list get_edge_list() nogil + euler_char_list euler_char(vector[filtration_type]) nogil + void resize_all_filtrations(int) nogil + void set_number_of_parameters(int) nogil + int get_number_of_parameters() nogil diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx new file mode 100644 index 0000000000..dc267097a7 --- /dev/null +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -0,0 +1,985 @@ +# This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. +# See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. +# Author(s): Vincent Rouvreau +# +# Copyright (C) 2016 Inria +# +# Modification(s): +# - 2022/11 Hannah Schreiber / David Loiseaux : adapt for multipersistence. +# - YYYY/MM Author: Description of the modification + + + +__author__ = "Vincent Rouvreau" +__copyright__ = "Copyright (C) 2016 Inria" +__license__ = "MIT" + + +from cython.operator import dereference, preincrement +from libc.stdint cimport intptr_t +from libc.stdint cimport uintptr_t + +cimport numpy as cnp +import numpy as np +np.import_array() + +cimport gudhi.simplex_tree_multi +cimport gudhi.simplex_tree +cimport cython + +from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated + + +from warnings import warn + +ctypedef double value_type + +cdef extern from "Simplex_tree_multi.h" namespace "Gudhi": + void multify(const uintptr_t, const uintptr_t, const unsigned int) nogil + void flatten(const uintptr_t, const uintptr_t, const unsigned int) nogil + void flatten_diag(const uintptr_t, const uintptr_t, const vector[value_type], int) nogil + void squeeze_filtration(uintptr_t, const vector[vector[value_type]]&, bool) nogil + vector[vector[value_type]] get_filtration_values(uintptr_t) nogil + + +cdef bool callback(vector[int] simplex, void *blocker_func): + return (blocker_func)(simplex) + +# SimplexTree python interface +cdef class SimplexTreeMulti: + """The simplex tree is an efficient and flexible data structure for + representing general (filtered) simplicial complexes. The data structure + is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex + Tree: An Efficient Data Structure for General Simplicial Complexes. + Algorithmica, pages 1–22, 2014. + + This class is a filtered, with keys, and non contiguous vertices version + of the simplex tree. + """ + # unfortunately 'cdef public Simplex_tree_multi_interface* thisptr' is not possible + # Use intptr_t instead to cast the pointer + cdef public intptr_t thisptr + + + # Get the pointer casted as it should be + cdef Simplex_tree_multi_interface* get_ptr(self) nogil: + return (self.thisptr) + + # cdef Simplex_tree_persistence_interface * pcohptr + # Fake constructor that does nothing but documenting the constructor + def __init__(self, other = None, num_parameters:int=2): + """SimplexTree constructor. + + :param other: If `other` is `None` (default value), an empty `SimplexTree` is created. + If `other` is a `SimplexTree`, the `SimplexTree` is constructed from a deep copy of `other`. + :type other: SimplexTree (Optional) + :returns: An empty or a copy simplex tree. + :rtype: SimplexTree + + :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`. + :note: If the `SimplexTree` is a copy, the persistence information is not copied. If you need it in the clone, + you have to call :func:`compute_persistence` on it even if you had already computed it in the original. + """ + + # The real cython constructor + def __cinit__(self, other = None, num_parameters:int=2): #TODO doc + if other: + if isinstance(other, SimplexTreeMulti): + self.thisptr = _get_copy_intptr(other) + else: + raise TypeError("`other` argument requires to be of type `SimplexTree`, or `None`.") + else: + self.thisptr = (new Simplex_tree_multi_interface()) + self.get_ptr().set_number_of_parameters(num_parameters) + + # TODO : set number of parameters outside the constructor ? + + def __dealloc__(self): + cdef Simplex_tree_multi_interface* ptr = self.get_ptr() + if ptr != NULL: + del ptr + # if self.pcohptr != NULL: + # del self.pcohptr + + def __is_defined(self): + """Returns true if SimplexTree pointer is not NULL. + """ + return self.get_ptr() != NULL + + # def __is_persistence_defined(self): + # """Returns true if Persistence pointer is not NULL. + # """ + # return self.pcohptr != NULL + + def copy(self)->SimplexTreeMulti: + """ + :returns: A simplex tree that is a deep copy of itself. + :rtype: SimplexTree + + :note: The persistence information is not copied. If you need it in the clone, you have to call + :func:`compute_persistence` on it even if you had already computed it in the original. + """ + stree = SimplexTreeMulti(num_parameters=self.num_parameters) + stree.thisptr = _get_copy_intptr(self) + return stree + + def __deepcopy__(self): + return self.copy() + + def filtration(self, simplex)->filtration_type: + """This function returns the filtration value for a given N-simplex in + this simplicial complex, or +infinity if it is not in the complex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :returns: The simplicial complex filtration value. + :rtype: float + """ + return self.get_ptr().simplex_filtration(simplex) + + def assign_filtration(self, simplex, cnp.ndarray[float, ndim=1] filtration): + """This function assigns a new filtration value to a + given N-simplex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :param filtration: The new filtration value. + :type filtration: float + + .. note:: + Beware that after this operation, the structure may not be a valid + filtration anymore, a simplex could have a lower filtration value + than one of its faces. Callers are responsible for fixing this + (with more :meth:`assign_filtration` or + :meth:`make_filtration_non_decreasing` for instance) before calling + any function that relies on the filtration property, like + :meth:`persistence`. + """ + assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0 + self.get_ptr().assign_simplex_filtration(simplex, filtration) + + + def num_vertices(self)->int: + """This function returns the number of vertices of the simplicial + complex. + + :returns: The simplicial complex number of vertices. + :rtype: int + """ + return self.get_ptr().num_vertices() + + def num_simplices(self)->int: + """This function returns the number of simplices of the simplicial + complex. + + :returns: the simplicial complex number of simplices. + :rtype: int + """ + return self.get_ptr().num_simplices() + + + def dimension(self)->dimension_type: + """This function returns the dimension of the simplicial complex. + + :returns: the simplicial complex dimension. + :rtype: int + + .. note:: + + This function is not constant time because it can recompute + dimension if required (can be triggered by + :func:`remove_maximal_simplex` + or + :func:`prune_above_filtration` + methods). + """ + return self.get_ptr().dimension() + + def upper_bound_dimension(self)->dimension_type: + """This function returns a valid dimension upper bound of the + simplicial complex. + + :returns: an upper bound on the dimension of the simplicial complex. + :rtype: int + """ + return self.get_ptr().upper_bound_dimension() + + def set_dimension(self, dimension)->None: + """This function sets the dimension of the simplicial complex. + + :param dimension: The new dimension value. + :type dimension: int + + .. note:: + + This function must be used with caution because it disables + dimension recomputation when required + (this recomputation can be triggered by + :func:`remove_maximal_simplex` + or + :func:`prune_above_filtration` + ). + """ + self.get_ptr().set_dimension(dimension) + + def find(self, simplex)->bool: + """This function returns if the N-simplex was found in the simplicial + complex or not. + + :param simplex: The N-simplex to find, represented by a list of vertex. + :type simplex: list of int + :returns: true if the simplex was found, false otherwise. + :rtype: bool + """ + return self.get_ptr().find_simplex(simplex) + + def insert(self, simplex, filtration:list|np.ndarray|None=None)->bool: + """This function inserts the given N-simplex and its subfaces with the + given filtration value (default value is '0.0'). If some of those + simplices are already present with a higher filtration value, their + filtration value is lowered. + + :param simplex: The N-simplex to insert, represented by a list of + vertex. + :type simplex: list of int + :param filtration: The filtration value of the simplex. + :type filtration: float + :returns: true if the simplex was not yet in the complex, false + otherwise (whatever its original filtration value). + :rtype: bool + """ + # TODO C++ + num_parameters = self.get_ptr().get_number_of_parameters() + if filtration is None: + filtration = np.array([-np.inf]*num_parameters, dtype = np.float) + simplex_already_exists = not self.get_ptr().insert(simplex, filtration) + if simplex_already_exists: + old_filtration = np.array(self.filtration(simplex)) + old_filtrations = np.array_split(old_filtration, old_filtration.shape[0] // num_parameters) + filtration = np.array(filtration) + for f in old_filtration: + if np.all(f >= filtration) or np.all(f <= filtration): + return False + new_filtration = np.concatenate([old_filtration, filtration], axis = 0) + self.assign_filtration(simplex, new_filtration) + return True + return True + + def get_simplices(self): + """This function returns a generator with simplices and their given + filtration values. + + :returns: The simplices. + :rtype: generator with tuples(simplex, filtration) + """ + cdef Simplex_tree_multi_simplices_iterator it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplex_handle sh = dereference(it) + + while it != end: + yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + preincrement(it) + + def get_filtration(self): + """This function returns a generator with simplices and their given + filtration values sorted by increasing filtration values. + + :returns: The simplices sorted by increasing filtration values. + :rtype: generator with tuples(simplex, filtration) + """ + cdef vector[Simplex_tree_multi_simplex_handle].const_iterator it = self.get_ptr().get_filtration_iterator_begin() + cdef vector[Simplex_tree_multi_simplex_handle].const_iterator end = self.get_ptr().get_filtration_iterator_end() + + while it != end: + yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + preincrement(it) + + def get_skeleton(self, dimension): + """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension. + + :param dimension: The skeleton dimension value. + :type dimension: int + :returns: The (simplices of the) skeleton of a maximum dimension. + :rtype: generator with tuples(simplex, filtration) + """ + cdef Simplex_tree_multi_skeleton_iterator it = self.get_ptr().get_skeleton_iterator_begin(dimension) + cdef Simplex_tree_multi_skeleton_iterator end = self.get_ptr().get_skeleton_iterator_end(dimension) + + while it != end: + yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + preincrement(it) + + def get_star(self, simplex): + """This function returns the star of a given N-simplex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :returns: The (simplices of the) star of a simplex. + :rtype: list of tuples(simplex, filtration) + """ + cdef simplex_type csimplex + for i in simplex: + csimplex.push_back(i) + cdef vector[pair[simplex_type, filtration_type]] star \ + = self.get_ptr().get_star(csimplex) + ct = [] + for filtered_simplex in star: + v = [] + for vertex in filtered_simplex.first: + v.append(vertex) + ct.append((v, filtered_simplex.second)) + return ct + + def get_cofaces(self, simplex, codimension): + """This function returns the cofaces of a given N-simplex with a + given codimension. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :param codimension: The codimension. If codimension = 0, all cofaces + are returned (equivalent of get_star function) + :type codimension: int + :returns: The (simplices of the) cofaces of a simplex + :rtype: list of tuples(simplex, filtration) + """ + cdef vector[int] csimplex + for i in simplex: + csimplex.push_back(i) + cdef vector[pair[simplex_type, filtration_type]] cofaces \ + = self.get_ptr().get_cofaces(csimplex, codimension) + ct = [] + for filtered_simplex in cofaces: + v = [] + for vertex in filtered_simplex.first: + v.append(vertex) + ct.append((v, filtered_simplex.second)) + return ct + + def get_boundaries(self, simplex): + """This function returns a generator with the boundaries of a given N-simplex. + If you do not need the filtration values, the boundary can also be obtained as + :code:`itertools.combinations(simplex,len(simplex)-1)`. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int. + :returns: The (simplices of the) boundary of a simplex + :rtype: generator with tuples(simplex, filtration) + """ + cdef pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] it = self.get_ptr().get_boundary_iterators(simplex) + + while it.first != it.second: + yield self.get_ptr().get_simplex_and_filtration(dereference(it.first)) + preincrement(it.first) + + def remove_maximal_simplex(self, simplex): + """This function removes a given maximal N-simplex from the simplicial + complex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + + .. note:: + + The dimension of the simplicial complex may be lower after calling + remove_maximal_simplex than it was before. However, + :func:`upper_bound_dimension` + method will return the old value, which + remains a valid upper bound. If you care, you can call + :func:`dimension` + to recompute the exact dimension. + """ + self.get_ptr().remove_maximal_simplex(simplex) + + def prune_above_filtration(self, filtration)->bool: + """Prune above filtration value given as parameter. + + :param filtration: Maximum threshold value. + :type filtration: float + :returns: The filtration modification information. + :rtype: bool + + + .. note:: + + Note that the dimension of the simplicial complex may be lower + after calling + :func:`prune_above_filtration` + than it was before. However, + :func:`upper_bound_dimension` + will return the old value, which remains a + valid upper bound. If you care, you can call + :func:`dimension` + method to recompute the exact dimension. + """ + return self.get_ptr().prune_above_filtration(filtration) + + def expansion(self, max_dim)->SimplexTreeMulti: + """Expands the simplex tree containing only its one skeleton + until dimension max_dim. + + The expanded simplicial complex until dimension :math:`d` + attached to a graph :math:`G` is the maximal simplicial complex of + dimension at most :math:`d` admitting the graph :math:`G` as + :math:`1`-skeleton. + The filtration value assigned to a simplex is the maximal filtration + value of one of its edges. + + The simplex tree must contain no simplex of dimension bigger than + 1 when calling the method. + + :param max_dim: The maximal dimension. + :type max_dim: int + """ + cdef int maxdim = max_dim + current_dim = self.dimension() + with nogil: + self.get_ptr().expansion(maxdim) + + # This is a fix for multipersistence. FIXME expansion in c++ + self.make_filtration_non_decreasing(start_dimension=current_dim+1) + return self + + def make_filtration_non_decreasing(self, start_dimension:int=1)->SimplexTreeMulti: # FIXME TODO code in c++ + """This function ensures that each simplex has a higher filtration + value than its faces by increasing the filtration values. + + :returns: True if any filtration value was modified, + False if the filtration was already non-decreasing. + :rtype: bool + """ + # return self.get_ptr().make_filtration_non_decreasing() + if start_dimension <= 0: + start_dimension = 1 + for dim in range(start_dimension, self.dimension()+1): + for splx, f in self.get_skeleton(dim): + if len(splx) != dim + 1: continue + self.assign_filtration(splx, np.max([g for _,g in self.get_boundaries(splx)] + [f], axis=0)) + # FIXME adapt for multicritical filtrations # TODO : C++ + return self + + def reset_filtration(self, filtration, min_dim = 0): + """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the + simplex tree when `min_dim = 0`. + `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to + make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing` + afterwards for instance). + + :param filtration: New threshold value. + :type filtration: float. + :param min_dim: The minimal dimension. Default value is 0. + :type min_dim: int. + """ + self.get_ptr().reset_filtration(filtration, min_dim) + + # def extend_filtration(self): + # """ Extend filtration for computing extended persistence. This function only uses the filtration values at the + # 0-dimensional simplices, and computes the extended persistence diagram induced by the lower-star filtration + # computed with these values. + # + # .. note:: + # + # Note that after calling this function, the filtration values are actually modified within the simplex tree. + # The function :func:`extended_persistence` retrieves the original values. + # + # .. note:: + # + # Note that this code creates an extra vertex internally, so you should make sure that the simplex tree does + # not contain a vertex with the largest possible value (i.e., 4294967295). + # + # This `notebook `_ + # explains how to compute an extension of persistence called extended persistence. + # """ + # self.get_ptr().compute_extended_filtration() + + # def extended_persistence(self, homology_coeff_field=11, min_persistence=0): + # """This function retrieves good values for extended persistence, and separate the diagrams into the Ordinary, + # Relative, Extended+ and Extended- subdiagrams. + # + # :param homology_coeff_field: The homology coefficient field. Must be a prime number. Default value is 11. Max is 46337. + # :type homology_coeff_field: int + # :param min_persistence: The minimum persistence value (i.e., the absolute value of the difference between the + # persistence diagram point coordinates) to take into account (strictly greater than min_persistence). + # Default value is 0.0. Sets min_persistence to -1.0 to see all values. + # :type min_persistence: float + # :returns: A list of four persistence diagrams in the format described in :func:`persistence`. The first one is + # Ordinary, the second one is Relative, the third one is Extended+ and the fourth one is Extended-. + # See https://link.springer.com/article/10.1007/s10208-008-9027-z and/or section 2.2 in + # https://link.springer.com/article/10.1007/s10208-017-9370-z for a description of these subtypes. + # + # .. note:: + # + # This function should be called only if :func:`extend_filtration` has been called first! + # + # .. note:: + # + # The coordinates of the persistence diagram points might be a little different than the + # original filtration values due to the internal transformation (scaling to [-2,-1]) that is + # performed on these values during the computation of extended persistence. + # + # This `notebook `_ + # explains how to compute an extension of persistence called extended persistence. + # """ + # cdef vector[pair[int, pair[value_type, value_type]]] persistence_result + # if self.pcohptr != NULL: + # del self.pcohptr + # self.pcohptr = new Simplex_tree_persistence_interface(self.get_ptr(), False) + # self.pcohptr.compute_persistence(homology_coeff_field, -1.) + # return self.pcohptr.compute_extended_persistence_subdiagrams(min_persistence) + + def expansion_with_blocker(self, max_dim, blocker_func): + """Expands the Simplex_tree containing only a graph. Simplices corresponding to cliques in the graph are added + incrementally, faces before cofaces, unless the simplex has dimension larger than `max_dim` or `blocker_func` + returns `True` for this simplex. + + The function identifies a candidate simplex whose faces are all already in the complex, inserts it with a + filtration value corresponding to the maximum of the filtration values of the faces, then calls `blocker_func` + with this new simplex (represented as a list of int). If `blocker_func` returns `True`, the simplex is removed, + otherwise it is kept. The algorithm then proceeds with the next candidate. + + .. warning:: + Several candidates of the same dimension may be inserted simultaneously before calling `blocker_func`, so + if you examine the complex in `blocker_func`, you may hit a few simplices of the same dimension that have + not been vetted by `blocker_func` yet, or have already been rejected but not yet removed. + + :param max_dim: Expansion maximal dimension value. + :type max_dim: int + :param blocker_func: Blocker oracle. + :type blocker_func: Callable[[List[int]], bool] + """ + self.get_ptr().expansion_with_blockers_callback(max_dim, callback, blocker_func) + + # def persistence(self, homology_coeff_field=11, min_persistence=0, persistence_dim_max = False): + # """This function computes and returns the persistence of the simplicial complex. + # + # :param homology_coeff_field: The homology coefficient field. Must be a + # prime number. Default value is 11. Max is 46337. + # :type homology_coeff_field: int + # :param min_persistence: The minimum persistence value to take into + # account (strictly greater than min_persistence). Default value is + # 0.0. + # Set min_persistence to -1.0 to see all values. + # :type min_persistence: float + # :param persistence_dim_max: If true, the persistent homology for the + # maximal dimension in the complex is computed. If false, it is + # ignored. Default is false. + # :type persistence_dim_max: bool + # :returns: The persistence of the simplicial complex. + # :rtype: list of pairs(dimension, pair(birth, death)) + # """ + # self.compute_persistence(homology_coeff_field, min_persistence, persistence_dim_max) + # return self.pcohptr.get_persistence() + + def get_edge_list(self): + return self.get_ptr().get_edge_list() + + def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False)->SimplexTreeMulti: + """(Strong) collapse of 1 critical clique complex, compatible with 2-parameter filtration. + + Parameters + ---------- + max_dimension:int + Max simplicial dimension of the complex. Unless specified, keeps the same dimension. + num:int + The number of collapses to do. + strong:bool + Whether to use strong collapses or collapses (slower, but may remove more edges) + full:bool + Collapses the maximum number of edges if true, i.e., will do at most 100 strong collapses and 100 non-strong collapses afterward. + progress:bool + If true, shows the progress of the number of collapses. + + WARNING + ------- + - This will destroy all of the k-simplices, with k>=2. Be sure to use this with a clique complex, if you want to preserve the homology strictly above dimension 1. + - This is for 1 critical simplices, with 2 parameter persistence. + Returns + ------- + self:SimplexTree + A simplex tree that has the same homology over this bifiltration. + + """ + # TODO : find a way to do multiple edge collapses without python conversions. + assert self.get_ptr().get_number_of_parameters() == 2 + if self.dimension() > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !") + from tqdm import tqdm + if num <= 0: + return self + max_dimension = self.dimension() if max_dimension is None else max_dimension + # edge_list = std::vector, std::pair>> + # cdef vector[pair[pair[int,int],pair[value_type,value_type]]] + edges = self.get_ptr().get_edge_list() + # cdef int n = edges.size() + n = len(edges) + if full: + num = 100 + for i in tqdm(range(num), total=num, desc="Removing edges", disable=not(progress)): + if strong: + edges = remove_strongly_filtration_dominated(edges) # nogil ? + else: + edges = remove_filtration_dominated(edges) + # Prevents doing useless collapses + if len(edges) >= n: + if full and strong: + strong = False + n = len(edges) + # n = edges.size() # len(edges) + else : + break + else: + n = len(edges) + # n = edges.size() + reduced_tree = SimplexTreeMulti(num_parameters=self.num_parameters) + for splx, f in self.get_skeleton(0): # Adds vertices back + reduced_tree.insert(splx, f) + for e, (f1, f2) in edges: # Adds reduced edges back # TODO : with insert_batch + reduced_tree.insert(e, [f1,f2]) + self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable) + self.expansion(max_dimension) # Expands back the simplextree to the original dimension. + # self.make_filtration_non_decreasing(2) + return self + + def to_rivet(self, path="rivet_dataset.txt", degree:int = 1, progress:bool=False, overwrite:bool=False, xbins:int=0, ybins:int=0)->None: + """ Create a file that can be imported by rivet, representing the filtration of the simplextree. + + Parameters + ---------- + path:str + path of the file. + degree:int + The homological degree to ask rivet to compute. + progress:bool = True + Shows the progress bar. + overwrite:bool = False + If true, will overwrite the previous file if it already exists. + Returns + ------- + Nothing + """ + from os.path import exists + from os import remove + if exists(path): + if not(overwrite): + print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") + return + remove(path) + file = open(path, "a") + file.write("--datatype bifiltration\n") + file.write(f"--homology {degree}\n") + file.write(f"-x {xbins}\n") + file.write(f"-y {ybins}\n") + file.write("--xlabel time of appearance\n") + file.write("--ylabel density\n\n") + from tqdm import tqdm + with tqdm(total=self.num_simplices(), position=0, disable = not(progress), desc="Writing simplex to file") as bar: + for dim in range(0,self.dimension()+1): # Not sure if dimension sort is necessary for rivet. Check ? + for s,F in self.get_skeleton(dim): + if len(s) != dim+1: continue + for i in s: + file.write(str(i) + " ") + file.write("; ") + for f in F: + file.write(str(f) + " ") + file.write("\n") + bar.update(1) + file.close() + return + + @property + def num_parameters(self)->int: + return self.get_ptr().get_number_of_parameters() + def get_simplices_of_dimension(self, dim:int): + return self.get_ptr().get_simplices_of_dimension(dim) + def key(self, simplex:list|np.ndarray): + return self.get_ptr().get_key(simplex) + def reset_keys(self)->None: + self.get_ptr().reset_keys() + return + def set_key(self,simplex:list|np.ndarray, key:int)->None: + self.get_ptr().set_key(simplex, key) + return + + + def to_scc(self, path="scc_dataset.txt", progress:bool=True, overwrite:bool=False, ignore_last_generators:bool=True, strip_comments:bool=False, reverse_block:bool=True, rivet_compatible=False)->None: + """ Create a file with the scc2020 standard, representing the n-filtration of the simplextree. + Link : https://bitbucket.org/mkerber/chain_complex_format/src/master/ + + Parameters + ---------- + path:str + path of the file. + ignore_last_generators:bool = True + If false, will include the filtration values of the last free persistence module. + progress:bool = True + Shows the progress bar. + overwrite:bool = False + If true, will overwrite the previous file if it already exists. + + Returns + ------- + Nothing + """ + ### GUDHI BUGFIX + self.reset_keys() + ### File + from os.path import exists + from os import remove + if exists(path): + if not(overwrite): + print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") + return + remove(path) + file = open(path, "a") + file.write("scc2020\n") if not rivet_compatible else file.write("firep\n") + if not strip_comments and not rivet_compatible: file.write("# Number of parameters\n") + num_parameters = self.get_ptr().get_number_of_parameters() + if rivet_compatible: + assert num_parameters == 2 + file.write("Filtration 1\n") + file.write("Filtration 2\n") + else: + file.write(f"{num_parameters}\n") + if not strip_comments: file.write("# Sizes of generating sets\n") + ## WRITES TSR VARIABLES + tsr:list[int]= [0]*(self.dimension()+1) # dimension --- 0 + for splx,f in self.get_simplices(): + dim = len(splx)-1 + tsr[dim] += (int)(len(f) // num_parameters) + tsr.reverse() + file.write(" ".join([str(n) for n in tsr])+"\n") + + ## Adds the boundaries to the dictionnary + tsr + dict_splx_to_firep_number = {} + tsr:list[list[int]] = [[] for _ in range(len(tsr))] # tsr stores simplices vertices, according to dimension, and the dictionnary + for dim in range(self.dimension(),-1 , -1): # range(2,-1,-1): + for splx,F in self.get_skeleton(dim): + if len(splx) != dim+1: continue + for b,_ in self.get_boundaries(splx): + if not self.key(b) in dict_splx_to_firep_number: + dict_splx_to_firep_number[self.key(b)] = len(tsr[dim-1]) + tsr[dim-1].append(b) + + ## Adds simplices that are not borders to tsr, i.e., simplices not in the dictionnary + for splx,_ in self.get_simplices(): + if not self.key(splx) in dict_splx_to_firep_number: + tsr[len(splx)-1].append(splx) + ## Writes simplices of tsr to file + dim_range = range(self.dimension(),0,-1) if ignore_last_generators else range(self.dimension(),-1,-1) + for dim in dim_range: # writes block by block + if not strip_comments: file.write(f"# Block of dimension {dim}\n") + if reverse_block: tsr[dim].reverse() + for splx in tsr[dim]: # for simplices of dimension + F = self.filtration(splx) + nbirth = (int)(len(F)//num_parameters) + for i in range(nbirth): + simplex_filtration = F[i*num_parameters:(i+1)*num_parameters] + file.write(" ".join([str(f) for f in simplex_filtration])) + file.write(" ;") + for b,_ in self.get_boundaries(splx): + file.write(f" {dict_splx_to_firep_number[self.key(b)]}") + file.write("\n") + file.close() + return + + def get_filtration_grid(self, resolution, box=None, grid_strategy:str="regular"): + if resolution is None: + warn("Provide a grid on which to squeeze !") + return + if box is None: + box = self.filtration_bounds() + assert(len(box[0]) == len(box[1]) == len(resolution) == self.num_parameters, f"Number of parameter not concistent. box: {len(box[0])}, resolution:{len(resolution)}, simplex tree:{self.num_parameters}") + if grid_strategy == "regular": + filtration_grid = np.array([np.linspace(*np.asarray(box)[:,i], num=resolution[i]) for i in range(self.num_parameters)]) + elif grid_strategy == "quantile": + filtrations_values = np.asarray(get_filtration_values(self.thisptr)) + filtration_grid = [np.quantile(filtration, np.linspace(0,1,num=res)) for filtration, res in zip(filtrations_values, resolution)] ## WARNING if multicritical cannot be turned into an array + else: + warn("Invalid grid strategy. Available ones are regular, and (todo) quantile") + return + return filtration_grid + + + def grid_squeeze(self, box = None, resolution = None, filtration_grid:np.ndarray|list|None = None, grid_strategy:str="regular", coordinate_values:bool=False): + """ + Fit the filtration of the simplextree to a grid + """ + if filtration_grid is None: + filtration_grid = self.get_filtration_grid(resolution, grid_strategy=grid_strategy, box=box) + + cdef vector[vector[value_type]] c_filtration_grid = filtration_grid + cdef intptr_t ptr = self.thisptr + cdef bool c_coordinate_values = coordinate_values + with nogil: + squeeze_filtration(ptr, c_filtration_grid, c_coordinate_values) + return filtration_grid + + def filtration_bounds(self): + """ + Returns the filtrations bounds. + """ + #TODO : check + low = np.min([f for s,F in self.get_simplices() for f in np.array_split(F, len(F) // self.num_parameters)], axis=0) + high = np.max([f for s,F in self.get_simplices() for f in np.array_split(F, len(F) // self.num_parameters)], axis=0) + return np.asarray([low,high]) + + + + def fill_lowerstar(self, F, parameter:int): + """ Fills the `dimension`th filtration by the lower-star filtration defined by F. + + Parameters + ---------- + F:1d array + The density over the vertices, that induces a lowerstar filtration. + parameter:int + Which filtration parameter to fill. /!\ python starts at 0. + + Returns + ------- + self:Simplextree + """ + # for s, sf in self.get_simplices(): + # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)]) + self.get_ptr().fill_lowerstar(F, parameter) + return self + + + def to_gudhi(self, parameter:int=0, basepoint:None|list|np.ndarray= None): + """Converts an multi simplextree to a gudhi simplextree. + Parameters + ---------- + parameter:int = 0 + The parameter to keep. WARNING will crash if the multi simplextree is not well filled. + basepoint:None + Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint. + WARNING + ------- + There are no safeguard yet, it WILL crash if asking for a parameter that is not filled. + Returns + ------- + A gudhi simplextree with chosen 1D filtration. + """ + # FIXME : deal with multicritical filtrations + import gudhi as gd + new_simplextree = gd.SimplexTree() + assert parameter < self.get_ptr().get_number_of_parameters() + cdef int c_parameter = parameter + cdef intptr_t old_ptr = self.thisptr + cdef intptr_t new_ptr = new_simplextree.thisptr + cdef vector[value_type] c_basepoint = [] if basepoint is None else basepoint + if basepoint is None: + with nogil: + flatten(old_ptr, new_ptr, c_parameter) + else: + with nogil: + flatten_diag(old_ptr, new_ptr, c_basepoint, c_parameter) + return new_simplextree + + def resize_all_filtrations(self, num:int): #TODO : num_parameters + self.get_ptr().resize_all_filtrations(num) + return + + def __eq__(self, other:SimplexTreeMulti): + """Test for structural equality + :returns: True if the 2 simplex trees are equal, False otherwise. + :rtype: bool + """ + return dereference(self.get_ptr()) == dereference(other.get_ptr()) + def euler_char(self, cnp.ndarray[float, ndim=2] points) -> np.ndarray: + """ Computes the Euler Characteristic of the filtered complex at given (multiparameter) time + + Parameters + ---------- + points: list[float] | list[list[float]] | cnp.ndarray + List of filtration values on which to compute the euler characteristic. + WARNING FIXME : the points have to have the same dimension as the simplextree. + + Returns + ------- + The list of euler characteristic values + """ +# if len(points) == 0: +# return cnp.empty() +# if type(points[0]) is float: +# points = cnp.array([points]) +# if type(points) is cnp.ndarray: +# assert len(points.shape) in [1,2] +# if len(points.shape) == 1: +# points = [points] + return np.array(self.get_ptr().euler_char(points), dtype=int) + + + +cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil: # Calls the c++ simplextree + return (new Simplex_tree_multi_interface(dereference(stree.get_ptr()))) + + + + +def from_gudhi(simplextree, num_parameters:int=2)->SimplexTreeMulti: + """Converts a gudhi simplextree to a multi simplextree. + Parameters + ---------- + parameters:int = 2 + The number of filtrations + Returns + ------- + A multi simplextree, with first filtration value being the one from the original simplextree. + """ + if isinstance(simplextree, SimplexTreeMulti): + return simplextree + st = SimplexTreeMulti(num_parameters=num_parameters) + cdef int c_num_parameters = num_parameters + cdef intptr_t old_ptr = simplextree.thisptr + cdef intptr_t new_ptr = st.thisptr + with nogil: + multify(old_ptr, new_ptr, c_num_parameters) + return st + + + +# def from_firep(path:str, enfore_1critical=True): ## Not finished yet +# st = SimplexTreeMulti() +# contains_pv = lambda line : ';' in line +# is_comment = lambda line : line[0] == "#" +# def get_splx_filtration(line:str): +# line += " " +# filtration, splx = line.split(";") +# splx = [truc for truc in splx.split(" ") if truc != ""] +# boundary = [truc for truc in splx.split(" ") if truc != ""] +# splx = convert(splx) +# return filtration, splx +# n_inserted_splxs = [0]*2 +# def convert(splx): +# if len(splx) <= 0: return [n_inserted_splxs[0]] +# dim:int = len(splx)-1 +# new_splx = [] +# if dim == 1: +# k = n_inserted_splxs[0] +# for s in splx: +# new_splx.append(k-s-1) +# return new_splx +# if dim > 2: +# warn("OSKOUR") +# return new_splx +# k = sum(n_inserted_splxs) +# for s in splx: +# new_splx.append(k-s-1) +# return new_splx + +# t,s,r = [-1]*3 +# with open(path, "r") as f: +# passed == false +# for line in f.readlines()[::-1]: +# if is_comment(line): continue +# if not contains_pv: break +# filtration, splx = get_splx_filtration(line) +# if not st.insert(splx, filtration): +# old_filtration = st.filtration(splx) +# st.assign_filtration(splx,old_filtration + filtration) +# else: +# n_inserted_splxs[max(len(splx)-1,0)] += 1 +# return st + + + + diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h new file mode 100644 index 0000000000..1faccb6c2c --- /dev/null +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -0,0 +1,324 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Vincent Rouvreau + * + * Copyright (C) 2016 Inria + * + * Modification(s): + * - 2022/11 David Loiseaux, Hannah Schreiber : adapt for multipersistence. + * - YYYY/MM Author: Description of the modification + */ + +#ifndef INCLUDE_SIMPLEX_TREE_INTERFACE_H_ +#define INCLUDE_SIMPLEX_TREE_INTERFACE_H_ + +//#include +//#include +//#include +//#include +#include +#include "Simplex_tree_multi.h" +#include + +#include +#include +#include // std::pair +#include +#include // for std::distance + +namespace Gudhi { + +template +class Simplex_tree_interface : public Simplex_tree { + public: + using Base = Simplex_tree; + using Filtration_value = typename Base::Filtration_value; + using Vertex_handle = typename Base::Vertex_handle; + using Simplex_handle = typename Base::Simplex_handle; + using Insertion_result = typename std::pair; + using Simplex = std::vector; + using Simplex_and_filtration = std::pair; + using Filtered_simplices = std::vector; + using Skeleton_simplex_iterator = typename Base::Skeleton_simplex_iterator; + using Complex_simplex_iterator = typename Base::Complex_simplex_iterator; + using Extended_filtration_data = typename Base::Extended_filtration_data; + using Boundary_simplex_iterator = typename Base::Boundary_simplex_iterator; + typedef bool (*blocker_func_t)(Simplex simplex, void *user_data); + using euler_chars_type = std::vector; + using point_type = std::vector; + using points_type = std::vector; + + + public: + + Extended_filtration_data efd; + + bool find_simplex(const Simplex& simplex) { + return (Base::find(simplex) != Base::null_simplex()); + } + + void assign_simplex_filtration(const Simplex& simplex, Filtration_value filtration) { + Base::assign_filtration(Base::find(simplex), filtration); + Base::clear_filtration(); + } + + bool insert(const Simplex& simplex, Filtration_value filtration = 0) { + Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); + if (result.first != Base::null_simplex()) + Base::clear_filtration(); + return (result.second); + } + + // Do not interface this function, only used in alpha complex interface for complex creation + bool insert_simplex(const Simplex& simplex, Filtration_value filtration = 0) { + Insertion_result result = Base::insert_simplex(simplex, filtration); + return (result.second); + } + + // Do not interface this function, only used in interface for complex creation + bool insert_simplex_and_subfaces(const Simplex& simplex, Filtration_value filtration = 0) { + Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); + return (result.second); + } + + // Do not interface this function, only used in strong witness interface for complex creation + bool insert_simplex(const std::vector& simplex, Filtration_value filtration = 0) { + Insertion_result result = Base::insert_simplex(simplex, filtration); + return (result.second); + } + + // Do not interface this function, only used in strong witness interface for complex creation + bool insert_simplex_and_subfaces(const std::vector& simplex, Filtration_value filtration = 0) { + Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); + return (result.second); + } + + Filtration_value simplex_filtration(const Simplex& simplex) { + return Base::filtration(Base::find(simplex)); + } + + void remove_maximal_simplex(const Simplex& simplex) { + Base::remove_maximal_simplex(Base::find(simplex)); + Base::clear_filtration(); + } + + Simplex_and_filtration get_simplex_and_filtration(Simplex_handle f_simplex) { + Simplex simplex; + for (auto vertex : Base::simplex_vertex_range(f_simplex)) { + simplex.insert(simplex.begin(), vertex); + } + return std::make_pair(std::move(simplex), Base::filtration(f_simplex)); + } + + Filtered_simplices get_star(const Simplex& simplex) { + Filtered_simplices star; + for (auto f_simplex : Base::star_simplex_range(Base::find(simplex))) { + Simplex simplex_star; + for (auto vertex : Base::simplex_vertex_range(f_simplex)) { + simplex_star.insert(simplex_star.begin(), vertex); + } + star.push_back(std::make_pair(simplex_star, Base::filtration(f_simplex))); + } + return star; + } + + Filtered_simplices get_cofaces(const Simplex& simplex, int dimension) { + Filtered_simplices cofaces; + for (auto f_simplex : Base::cofaces_simplex_range(Base::find(simplex), dimension)) { + Simplex simplex_coface; + for (auto vertex : Base::simplex_vertex_range(f_simplex)) { + simplex_coface.insert(simplex_coface.begin(), vertex); + } + cofaces.push_back(std::make_pair(simplex_coface, Base::filtration(f_simplex))); + } + return cofaces; + } + + void compute_extended_filtration() { + this->efd = this->extend_filtration(); + return; + } + +/* Simplex_tree_interface* collapse_edges(int nb_collapse_iteration) {*/ +/* using Filtered_edge = std::tuple;*/ +/* std::vector edges;*/ +/* for (Simplex_handle sh : Base::skeleton_simplex_range(1)) {*/ +/* if (Base::dimension(sh) == 1) {*/ +/* typename Base::Simplex_vertex_range rg = Base::simplex_vertex_range(sh);*/ +/* auto vit = rg.begin();*/ +/* Vertex_handle v = *vit;*/ +/* Vertex_handle w = *++vit;*/ +/* edges.emplace_back(v, w, Base::filtration(sh));*/ +/* }*/ +/* }*/ + +/* for (int iteration = 0; iteration < nb_collapse_iteration; iteration++) {*/ +/* edges = Gudhi::collapse::flag_complex_collapse_edges(std::move(edges));*/ +/* }*/ +/* Simplex_tree_interface* collapsed_stree_ptr = new Simplex_tree_interface();*/ +/* // Copy the original 0-skeleton*/ +/* for (Simplex_handle sh : Base::skeleton_simplex_range(0)) {*/ +/* collapsed_stree_ptr->insert({*(Base::simplex_vertex_range(sh).begin())}, Base::filtration(sh));*/ +/* }*/ +/* // Insert remaining edges*/ +/* for (auto remaining_edge : edges) {*/ +/* collapsed_stree_ptr->insert({std::get<0>(remaining_edge), std::get<1>(remaining_edge)}, std::get<2>(remaining_edge));*/ +/* }*/ +/* return collapsed_stree_ptr;*/ +/* }*/ + + void expansion_with_blockers_callback(int dimension, blocker_func_t user_func, void *user_data) { + Base::expansion_with_blockers(dimension, [&](Simplex_handle sh){ + Simplex simplex(Base::simplex_vertex_range(sh).begin(), Base::simplex_vertex_range(sh).end()); + return user_func(simplex, user_data); + }); + } + + // Iterator over the simplex tree + Complex_simplex_iterator get_simplices_iterator_begin() { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::complex_simplex_range().begin(); + } + + Complex_simplex_iterator get_simplices_iterator_end() { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::complex_simplex_range().end(); + } + + typename std::vector::const_iterator get_filtration_iterator_begin() { + // Base::initialize_filtration(); already performed in filtration_simplex_range + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::filtration_simplex_range().begin(); + } + + typename std::vector::const_iterator get_filtration_iterator_end() { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::filtration_simplex_range().end(); + } + + Skeleton_simplex_iterator get_skeleton_iterator_begin(int dimension) { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::skeleton_simplex_range(dimension).begin(); + } + + Skeleton_simplex_iterator get_skeleton_iterator_end(int dimension) { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::skeleton_simplex_range(dimension).end(); + } + + std::pair get_boundary_iterators(const Simplex& simplex) { + auto bd_sh = Base::find(simplex); + if (bd_sh == Base::null_simplex()) + throw std::runtime_error("simplex not found - cannot find boundaries"); + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + auto boundary_srange = Base::boundary_simplex_range(bd_sh); + return std::make_pair(boundary_srange.begin(), boundary_srange.end()); + } + + + +// ######################## MULTIPERS STUFF + void reset_keys(){ + unsigned int count = 0; + for (auto sh : Base::filtration_simplex_range()) + Base::assign_key(sh, count++); + } + + int get_key(const Simplex& simplex){ + return Base::key(Base::find(simplex)); + } + + void set_key(Simplex& simplex, int key){ + Base::assign_key(Base::find(simplex), key); + return; + } + void fill_lowerstar(std::vector filtration, int axis){ + for (auto &SimplexHandle : Base::complex_simplex_range()){ + std::vector current_birth = Base::filtration(SimplexHandle); + double to_assign = -1*std::numeric_limits::infinity(); + for (auto vertex : Base::simplex_vertex_range(SimplexHandle)){ + to_assign = std::max(filtration[vertex], to_assign); + } + current_birth[axis] = to_assign; + Base::assign_filtration(SimplexHandle, current_birth); + } + } + using simplices_list = std::vector>; + simplices_list get_simplices_of_dimension(int dimension){ + simplices_list simplex_list; + simplex_list.reserve(Base::num_simplices()); + for (auto &simplexhandle : Base::skeleton_simplex_range(dimension)){ + if (Base::dimension(simplexhandle) == dimension){ + std::vector simplex; + simplex.reserve(dimension+1); + for (int vertex : Base::simplex_vertex_range(simplexhandle)) + simplex.push_back(vertex); + simplex_list.push_back(simplex); + } + } + simplex_list.shrink_to_fit(); + return simplex_list; + } + using edge_list = std::vector, std::pair>>; + edge_list get_edge_list(){ + edge_list simplex_list; + simplex_list.reserve(Base::num_simplices()); + for (auto &simplexHandle : Base::skeleton_simplex_range(2)){ + if (Base::dimension(simplexHandle) == 1){ + std::pair simplex; + auto it = Base::simplex_vertex_range(simplexHandle).begin(); + simplex = {*it, *(++it)}; + auto f = Base::filtration(simplexHandle); + simplex_list.push_back({simplex, {f[0], f[1]}}); + } + } + simplex_list.shrink_to_fit(); + return simplex_list; + } + + euler_chars_type euler_char(const std::vector> &point_list){ // TODO multi-critical + const unsigned int npts = point_list.size(); + if (npts == 0){ + return {}; + } + const unsigned int nparameters = point_list[0].size(); + + euler_chars_type out(point_list.size(), 0.); + + auto is_greater = [nparameters](const point_type &a, const point_type &b){ //french greater + for (unsigned int i = 0; i< nparameters; i++) + if( a[i] < b[i]) + return false; + return true; + }; +#pragma omp parallel for + for (unsigned int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel + auto &euler_char_at_point = out[i]; +// #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR + for(const auto &SimplexHandle : Base::complex_simplex_range()){ + const auto &pt = point_list[i]; + const auto &filtration = Base::filtration(SimplexHandle); + if (is_greater(pt, filtration)){ + int sign = Base::dimension(SimplexHandle) %2 ? -1 : 1; + euler_char_at_point += sign; + } + } + } + return out; + } + void resize_all_filtrations(int num){ //TODO : that is for 1 critical filtrations + if (num < 0) return; + for(const auto &SimplexHandle : Base::complex_simplex_range()){ + std::vector new_filtration_value = Base::filtration(SimplexHandle); + new_filtration_value.resize(num); + Base::assign_filtration(SimplexHandle, new_filtration_value); + } + } + + +}; + + +} // namespace Gudhi + +#endif // INCLUDE_SIMPLEX_TREE_INTERFACE_H_ diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h new file mode 100644 index 0000000000..a6b9cda2ec --- /dev/null +++ b/src/python/include/Simplex_tree_multi.h @@ -0,0 +1,183 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2014 Inria """ + * + * + * Modification(s): + * - 2022/11 David Loiseaux / Hannah Schreiber : added multify / flatten to interface standard simplextree. + * - YYYY/MM Author: Description of the modification + */ +#ifndef SIMPLEX_TREE_MULTI_H_ +#define SIMPLEX_TREE_MULTI_H_ + +#include +#include +#include "utils/box.h" +#include "utils/line.h" + + + +namespace Gudhi { +/** Model of SimplexTreeOptions. + * + * Maximum number of simplices to compute persistence is std::numeric_limits::max() + * (about 4 billions of simplices). */ + +using value_type = double; +struct Simplex_tree_options_multidimensional_filtration { +public: + typedef linear_indexing_tag Indexing_tag; + typedef int Vertex_handle; + typedef std::vector Filtration_value; + typedef std::uint32_t Simplex_key; + static const bool store_key = true; + static const bool store_filtration = true; + static const bool contiguous_vertices = false; +}; + +using option_multi = Simplex_tree_options_multidimensional_filtration; +using option_std = Simplex_tree_options_full_featured; +bool operator<(const std::vector& v1, const std::vector& v2) +{ + bool isSame = true; + if (v1.size() != v2.size()) isSame = false; + for (unsigned int i = 0; i < std::min(v1.size(), v2.size()); ++i){ + if (v1[i] > v2[i]) return false; + if (isSame && v1[i] != v2[i]) isSame = false; + } + if (isSame) return false; + return true; +} + +void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const unsigned int dimension){ + Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(newsplxptr);; + if (dimension <= 0) + {std::cout << "Empty filtration\n"; return ;} + std::vector f(dimension); + for (auto &simplex_handle : st.complex_simplex_range()){ + std::vector simplex; + for (auto vertex : st.simplex_vertex_range(simplex_handle)) + simplex.push_back(vertex); + f[0] = st.filtration(simplex_handle); + st_multi.insert_simplex(simplex,f); + } +} +void flatten(const uintptr_t splxptr, const uintptr_t newsplxptr, const unsigned int dimension = 0){ + Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + + for (const auto &simplex_handle : st_multi.complex_simplex_range()){ + std::vector simplex; + for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) + simplex.push_back(vertex); + value_type f = st_multi.filtration(simplex_handle)[dimension]; + st.insert_simplex(simplex,f); + } +} + +void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ + Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + utils::Line l(basepoint); + for (const auto &simplex_handle : st_multi.complex_simplex_range()){ + std::vector simplex; + for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) + simplex.push_back(vertex); + + std::vector f = st_multi.filtration(simplex_handle); + if (dimension <0) dimension = 0; + value_type new_filtration = l.push_forward(f)[dimension]; + st.insert_simplex(simplex,new_filtration); + } + + +} + + +using filtration_grid = std::vector>; +using grid_value = std::vector; +template +std::vector find_coordinates(const grid_value &x, const filtration_grid &grid){ + // TODO: optimize with dichotomy + std::vector coordinates(grid.size()); + for (unsigned int parameter = 0; parameter< grid.size(); parameter++){ + const auto& filtration = grid[parameter]; + const auto& to_project = x[parameter]; + grid_value distance_vector(filtration.size()); + for (unsigned int i = 0; i < filtration.size(); i++){ + distance_vector[i] = std::abs(to_project - filtration[i]); + } + coordinates[parameter] = std::distance(distance_vector.begin(), std::min_element(distance_vector.begin(), distance_vector.end())); + } + return coordinates; +} + +// TODO integer filtrations, does this help with performance ? +// projects filtrations values to the grid. If coordinate_values is set to true, the filtration values are the coordinates of this grid +void squeeze_filtration(uintptr_t splxptr, const std::vector> &grid, bool coordinate_values=true){ + + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + unsigned int num_parameters = st_multi.get_number_of_parameters(); + if (grid.size() != num_parameters){ + std::cerr << "Bad grid !" << std::endl; + return; + } + for (const auto &simplex_handle : st_multi.complex_simplex_range()){ + std::vector simplex_filtration = st_multi.filtration(simplex_handle); + if (coordinate_values) + st_multi.assign_filtration(simplex_handle, find_coordinates(simplex_filtration, grid)); + else{ + auto coordinates = find_coordinates(simplex_filtration, grid); + grid_value squeezed_filtration(num_parameters); + for (unsigned int parameter = 0; parameter < num_parameters; parameter++) + squeezed_filtration[parameter] = grid[parameter][coordinates[parameter]]; + st_multi.assign_filtration(simplex_handle, squeezed_filtration); + } + } + return; +} +std::vector> get_filtration_values(const uintptr_t splxptr){ + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + unsigned int num_parameters = st_multi.get_number_of_parameters(); + std::vector> out(num_parameters, std::vector(st_multi.num_simplices())); + unsigned int count = 0; + for (const auto &simplex_handle : st_multi.complex_simplex_range()){ + const auto filtration = st_multi.filtration(simplex_handle); + for (unsigned int parameter=0; parameter < num_parameters; parameter++){ + out[parameter][count] = filtration[parameter]; + } + count++; + } + return out; + +} + +} // namespace Gudhi + +namespace std { + +template<> +class numeric_limits > +{ +public: + static std::vector infinity() throw(){ + return std::vector(1, numeric_limits::infinity()); + }; + + + static std::vector quiet_NaN() throw(){ + return std::vector(1, numeric_limits::quiet_NaN()); + }; + +}; + +} // namespace std + + + + + +#endif // SIMPLEX_TREE_MULTI_H_ diff --git a/src/python/include/utils/box.h b/src/python/include/utils/box.h new file mode 100644 index 0000000000..1fb517188a --- /dev/null +++ b/src/python/include/utils/box.h @@ -0,0 +1,153 @@ +/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. + * See file LICENSE for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2021 Inria + * + * Modification(s): + * + */ +/** + * @file box.h + * @author David Loiseaux + * @brief BOX. + */ + +#ifndef BOX_H_INCLUDED +#define BOX_H_INCLUDED + +#include +#include +// #include +#include +#include +#include +// #include "gudhi/Simplex_tree.h" + + + + + + +/** + * @brief Holds the square box on which to compute. + */ + +namespace utils{ + +template +class Box +{ + using corner_type = std::vector; + using point_type = std::vector; +public: + Box(); + Box(const corner_type& bottomCorner, const corner_type& upperCorner); + Box(const std::pair& box); + + void inflate(T delta); + const corner_type& get_bottom_corner() const; + const corner_type& get_upper_corner() const; + bool contains(const point_type& point) const; + void infer_from_filters(const std::vector> &Filters_list); + bool is_trivial() const ; + +private: + corner_type bottomCorner_; + corner_type upperCorner_; +}; + +template +inline Box::Box() +{} + +template +inline Box::Box(const corner_type &bottomCorner, const corner_type &upperCorner) + : bottomCorner_(bottomCorner), + upperCorner_(upperCorner) +{ + assert(bottomCorner.size() == upperCorner.size() + && is_smaller(bottomCorner, upperCorner) + && "This box is trivial !"); +} + +template +inline Box::Box(const std::pair &box) + : bottomCorner_(box.first), + upperCorner_(box.second) +{} + + +template +inline void Box::inflate(T delta) +{ +#pragma omp simd + for (unsigned int i = 0; i < bottomCorner_.size(); i++){ + bottomCorner_[i] -= delta; + upperCorner_[i] += delta; + } +} + +template +inline void Box::infer_from_filters(const std::vector> &Filters_list){ + unsigned int dimension = Filters_list.size(); + unsigned int nsplx = Filters_list[0].size(); + std::vector lower(dimension); + std::vector upper(dimension); + for (unsigned int i =0; i < dimension; i++){ + T min = Filters_list[i][0]; + T max = Filters_list[i][0]; + for (unsigned int j=1; j +inline bool Box::is_trivial() const { + return bottomCorner_.empty() || upperCorner_.empty() || bottomCorner_.size() != upperCorner_.size(); +} + +template +inline const std::vector &Box::get_bottom_corner() const +{ + return bottomCorner_; +} + +template +inline const std::vector &Box::get_upper_corner() const +{ + return upperCorner_; +} + +template +inline bool Box::contains(const std::vector &point) const +{ + if (point.size() != bottomCorner_.size()) return false; + + for (unsigned int i = 0; i < point.size(); i++){ + if (point[i] < bottomCorner_[i]) return false; + if (point[i] > upperCorner_[i]) return false; + } + + return true; +} + +template +std::ostream& operator<<(std::ostream& os, const Box& box) +{ + os << "Box -- Bottom corner : "; + os << box.get_bottom_corner(); + os << ", Top corner : "; + os << box.get_upper_corner(); + return os; +} + +} // namespace utils + + +#endif // BOX_H_INCLUDED diff --git a/src/python/include/utils/debug.h b/src/python/include/utils/debug.h new file mode 100644 index 0000000000..19c804739a --- /dev/null +++ b/src/python/include/utils/debug.h @@ -0,0 +1,125 @@ +/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. + * See file LICENSE for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2021 Inria + * + * Modification(s): + * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence class, renaming and cleanup. + */ +/** + * @file debug.h + * @author David Loiseaux, + * @brief Display functions for debug purposes + */ + + +#ifndef DEBUG_H_INCLUDED +#define DEBUG_H_INCLUDED + +#include +#include +#include +#include + +#include + +namespace utils { + +using clk = std::chrono::high_resolution_clock; +using tp = clk::time_point; + +constexpr bool debug = false; + + +class Timer +{ +public: + Timer() : activated_(false) {} + Timer(const std::string &string, bool verbose) + : timer_(clk::now()), activated_(verbose) + { + if(verbose){ + std::cout << string << std::flush; + } + } + ~Timer(){ + if (activated_) + { + std::chrono::duration elapsed = + std::chrono::duration_cast>( + clk::now() - timer_); + std::cout << " Done ! It took "<< elapsed.count() + << " seconds." << std::endl; + } + } + +private: + tp timer_; + bool activated_; +}; + + + + +template +void disp_vect(std::vector v){ + for(uint i=0; i< v.size(); i++){ + std::cout << v[i] << " "; + } + std::cout << std::endl; +} + +template +void disp_vect(std::list v){ + while(!v.empty()){ + std::cout << v.front() << " "; + v.pop_front(); + } + std::cout << std::endl; +} + +template +void disp_vect(std::vector > v){ + for(unsigned int i=0; i< v.size(); i++){ + std::cout << "(" << v[i].first << " " << v[i].second <<") "; + } +} + +template +void disp_vect(std::vector> v, bool show_small = true){ + for(uint i = 0; i < v.size(); i++){ + if(v[i].size() <= 1 && !show_small) continue; + std::cout << "("; + for (uint j = 0; j < v[i].size(); j++){ + std::cout << v[i][j]; + if(j < v[i].size() - 1) std::cout << " "; + } + std::cout << ") "; + } + std::cout << std::endl; +} + + +} //namespace Debug +namespace std{ + template + std::ostream& operator<<(std::ostream& stream, const std::vector truc){ + stream << "["; + for(unsigned int i = 0; i < truc.size()-1; i++){ + stream << truc[i] << ", "; + } + if(!truc.empty()) stream << truc.back(); + stream << "]"; + return stream; + } +} + + + + + + +#endif // DEBUG_H_INCLUDED + + diff --git a/src/python/include/utils/line.h b/src/python/include/utils/line.h new file mode 100644 index 0000000000..cba301ecae --- /dev/null +++ b/src/python/include/utils/line.h @@ -0,0 +1,94 @@ +/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. + * See file LICENSE for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2022 Inria + * + * Modification(s): + */ +/** + * @file line_filtration_translation.h + * @author David Loiseaux + * @brief + */ + + +#ifndef LINE_FILTRATION_TRANSLATION_H_INCLUDED +#define LINE_FILTRATION_TRANSLATION_H_INCLUDED + +#include "utilities.h" +#include "box.h" + +namespace utils{ + using point_type = std::vector; + template + class Line + { + + public: + Line(); + Line(point_type x); + Line(point_type x, point_type v); + point_type push_forward(point_type x) const; + point_type push_back(point_type x) const; + dimension_type get_dim() const; + std::pair get_bounds(const Box &box) const; + + + private: + point_type basepoint_; // any point on the line + point_type direction_; // direction of the line + + }; + template + Line::Line(){} + + template + Line::Line(point_type x){ + this->basepoint_.swap(x); + // this->direction_ = {}; // diagonal line + } + template + Line::Line(point_type x, point_type v){ + this->basepoint_.swap(x); + this->direction_.swap(v); + } + template + point_type Line::push_forward(point_type x) const{ //TODO remove copy + x -= basepoint_; + value_type t=negInf; + for (unsigned int i = 0; idirection_.size() > i ? direction_[i] : 1; + t = std::max(t, x[i]/dir); + } + point_type out(basepoint_.size()); + for (unsigned int i = 0; i < out.size(); i++) + out[i] = basepoint_[i] + t * (this->direction_.size() > i ? direction_[i] : 1) ; + return out; + } + template + point_type Line::push_back(point_type x) const{ + x -= basepoint_; + value_type t=inf; + for (unsigned int i = 0; idirection_.size() > i ? direction_[i] : 1; + t = std::min(t, x[i]/dir); + } + point_type out(basepoint_.size()); + for (unsigned int i = 0; i < out.size(); i++) + out[i] = basepoint_[i] + t * (this->direction_.size() > i ? direction_[i] : 1) ; + return out; + } + template + dimension_type Line::get_dim() const{ + return basepoint_.size(); + } + template + std::pair Line::get_bounds(const Box &box) const{ + return {this->push_forward(box.get_bottom_corner()), this->push_back(box.get_upper_corner())}; + } + + +} + +#endif // LINE_FILTRATION_TRANSLATION_H_INCLUDED diff --git a/src/python/include/utils/utilities.h b/src/python/include/utils/utilities.h new file mode 100644 index 0000000000..b754f01787 --- /dev/null +++ b/src/python/include/utils/utilities.h @@ -0,0 +1,262 @@ +/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. + * See file LICENSE for full license details. + * Author(s): David Loiseaux, Hannah Schreiber + * + * Copyright (C) 2022 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef UTILITIES_H +#define UTILITIES_H + +#include +#include +#include +#include + + +namespace utils { + +constexpr bool verbose = false; + +using index = unsigned int; +using value_type = double; +using filtration_type = std::vector; +using multifiltration_type = std::vector; +using dimension_type = int; +using persistence_pair = std::pair; +using boundary_type = std::vector; +using boundary_matrix = std::vector; +using permutation_type = std::vector; +using point_type = std::vector; +using corner_type = std::vector; +using corners_type = std::pair>, std::vector>>; +using bar = std::pair; +using multipers_barcode = std::vector>; +const value_type inf = std::numeric_limits::infinity(); +const value_type negInf = -1 * inf; +using interval_type = std::pair; + +template +bool is_smaller(const std::vector& x, const std::vector& y) +{ + for (unsigned int i = 0; i < std::min(x.size(), y.size()); i++) + if (x[i] > y[i]) return false; + return true; +} +template +bool is_greater(const std::vector& x, const std::vector& y) +{ + for (unsigned int i = 0; i < std::min(x.size(), y.size()); i++) + if (x[i] < y[i]) return false; + return true; +} + + + + +struct Bar{ + Bar() : dim(-1), birth(-1), death(-1) + {} + + Bar(dimension_type dim, int birth, int death) + : dim(dim), birth(birth), death(death) + {} + + dimension_type dim; + int birth; + int death; +}; + +using barcode_type = std::vector; + +struct Diagram_point{ + Diagram_point() : dim(-1), birth(-1), death(-1) + {} + + Diagram_point(dimension_type dim, + value_type birth, + value_type death) + : dim(dim), birth(birth), death(death) + {} + + dimension_type dim; + value_type birth; + value_type death; +}; +using diagram_type = std::vector; + +struct MultiDiagram_point{ +public: + MultiDiagram_point() : dim(-1), birth({}), death({}) + {} + + MultiDiagram_point(dimension_type dim, + corner_type birth, + corner_type death) + : dim(dim), birth(birth), death(death) + {} + dimension_type get_dimension() const {return dim;} + corner_type get_birth() const {return birth;} + corner_type get_death() const {return death;} +private: + dimension_type dim; + corner_type birth; + corner_type death; + +}; +struct MultiDiagram{ // for python interface +public: + using iterator = std::vector::const_iterator; + MultiDiagram(){} + MultiDiagram(std::vector& m) : multiDiagram(m) + {} + std::vector get_points(const dimension_type dimension = -1) const{ // dump for python interface + std::vector out; + out.reserve(multiDiagram.size()); + for (const MultiDiagram_point &pt : multiDiagram){ + if (dimension == -1 || pt.get_dimension() == dimension){ + if (pt.get_birth().size() > 0 && pt.get_death().size() > 0 && pt.get_birth()[0] != utils::inf ) + out.push_back({pt.get_birth(), pt.get_death()}); + } + } + out.shrink_to_fit(); + return out; + } + std::vector> to_multipers(const dimension_type dimension = -1) const{ // dump for python interface + std::vector> out; + out.reserve(multiDiagram.size()); + for (const MultiDiagram_point &pt : multiDiagram){ + if (pt.get_dimension() == dimension){ + const auto &b = pt.get_birth(); + const auto &d = pt.get_death(); + out.push_back({b[0], d[0],b[1], d[1]}); + } + } + out.shrink_to_fit(); + return out; + } + iterator begin() const {return this->multiDiagram.begin();} + iterator end() const {return this->multiDiagram.end();} + unsigned int size() {return this->multiDiagram.size();} + void set(std::vector& m) {this-> multiDiagram.swap(m);} + std::vector& getref() {return this->multiDiagram;} + MultiDiagram_point& operator[](unsigned int i) {return this->multiDiagram[i];} + MultiDiagram_point& at(const unsigned int i){ + return multiDiagram[i]; + } +private: + std::vector multiDiagram; + +}; +struct MultiDiagrams{ +public: + using iterator = std::vector::const_iterator; + using nciterator = std::vector::iterator; + MultiDiagrams() {} + MultiDiagrams(unsigned int size) : multiDiagrams(size) {} + std::vector>> to_multipers(){ + unsigned int nsummands = this->multiDiagrams.front().size(); + unsigned int nlines = this->multiDiagrams.size(); + // std::vector>> out(nsummands, std::vector>(nlines, std::vector(5))); + std::vector>> out(nsummands); + for (unsigned int i = 0; i < nsummands; i++){ + out[i].reserve(nlines); + for(unsigned int j = 0; j < nlines; j++){ + const MultiDiagram_point &pt = this->multiDiagrams[j][i]; + if(_is_inf(pt.get_birth()) || _is_negInf(pt.get_death())) + out[i].push_back({0, 0, 0, 0,static_cast(j)}); + else + out[i].push_back({pt.get_birth()[0], pt.get_death()[0], pt.get_birth()[1], pt.get_death()[1],static_cast(j)}); + } + out[i].shrink_to_fit(); + } + return out; + } + using __for_python_plot_type = std::pair>,std::vector>; + __for_python_plot_type _for_python_plot(dimension_type dimension=-1, double min_persistence=0){ + __for_python_plot_type out; + auto& bars = out.first; + auto& summand_idx= out.second; + bars.reserve(this->multiDiagrams.size() * this->multiDiagrams[0].size()*2); + summand_idx.reserve(this->multiDiagrams.size() * this->multiDiagrams[0].size()); + for (const MultiDiagram& multiDiagram : this->multiDiagrams){ + unsigned int count = 0; + for (const MultiDiagram_point& bar : multiDiagram){ + const auto& birth = bar.get_birth(); + const auto& death = bar.get_death(); + if ( (dimension == -1 || bar.get_dimension() == dimension) && (!_is_inf(birth) && (death[0] > birth[0] + min_persistence)) ) { + bars.push_back({birth[0], death[0]}); + bars.push_back({birth[1], death[1]}); + summand_idx.push_back(count); + } + count++; + } + } + return out; + } + MultiDiagram& operator[](const unsigned int i){ + return multiDiagrams[i]; + } + MultiDiagram& at(const unsigned int i){ + return multiDiagrams[i]; + } + iterator begin() const {return this->multiDiagrams.begin();} // cython bug : iterators like bc in bcs crash) + iterator end() const {return this->multiDiagrams.end();} + + using barcodes = std::vector>; + barcodes get_points(){ + unsigned int nsummands = this->multiDiagrams.front().size(); + unsigned int nlines = this->multiDiagrams.size(); + // std::vector>> out(nsummands, std::vector>(nlines, std::vector(5))); + barcodes out(nlines, std::vector(nsummands)); + for (unsigned int i = 0; i < nlines; i++){ + for(unsigned int j = 0; j < nsummands; j++){ + const MultiDiagram_point &pt = this->multiDiagrams[i][j]; + out[i][j] = {pt.get_birth(), pt.get_death()}; + } + } + return out; + } + + unsigned int size() const {return this->multiDiagrams.size();} +private: + std::vector multiDiagrams; + inline bool _is_inf(const std::vector &truc) const{ + for (const auto coord : truc) + if (coord != inf) return false; + return true; + } + inline bool _is_negInf(const std::vector &truc) const{ + for (const auto coord : truc) + if (coord != negInf) return false; + return true; + } +}; +} //namespace utils + + + + +namespace std{ + template + void sort(vector &to_sort, bool(*compare)(const T&, const T&)){ + std::sort(to_sort.begin(), to_sort.end(), compare); + } + template + std::vector& operator-=(std::vector &result, const std::vector &to_substract){ + std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); + return result; + } + template + std::vector& operator+=(std::vector &result, const std::vector &to_add){ + std::transform(result.begin(), result.end(), to_add.begin(),result.begin(), std::plus()); + return result; + } + +} // namespace std + + +#endif // UTILITIES_H From 8abadb5f05fc64e818b4a2f56a3ad9d4244122fd Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Wed, 15 Feb 2023 14:29:48 +0100 Subject: [PATCH 02/18] Fixes and renaming --- src/python/gudhi/simplex_tree_multi.pyx | 54 ++++++++++++++++--------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index dc267097a7..6755064718 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -18,14 +18,15 @@ __license__ = "MIT" from cython.operator import dereference, preincrement from libc.stdint cimport intptr_t from libc.stdint cimport uintptr_t +from typing import Any cimport numpy as cnp import numpy as np -np.import_array() +cnp.import_array() cimport gudhi.simplex_tree_multi -cimport gudhi.simplex_tree cimport cython +from gudhi import SimplexTree ## Small hack for typing from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated @@ -83,9 +84,12 @@ cdef class SimplexTreeMulti: # The real cython constructor def __cinit__(self, other = None, num_parameters:int=2): #TODO doc - if other: + if not other is None: if isinstance(other, SimplexTreeMulti): self.thisptr = _get_copy_intptr(other) + elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree + self.thisptr = (new Simplex_tree_multi_interface()) + multify(other.thisptr, self.thisptr, num_parameters) else: raise TypeError("`other` argument requires to be of type `SimplexTree`, or `None`.") else: @@ -135,9 +139,12 @@ cdef class SimplexTreeMulti: :returns: The simplicial complex filtration value. :rtype: float """ - return self.get_ptr().simplex_filtration(simplex) + # filtration_vector = self.get_ptr().simplex_filtration(simplex) + # filtrations = np.array_split(filtration_vector, len(filtration_vector) // self.num_parameters) + # return filtrations.squeeze() # If 1 critical returns only 1 filtration + return self.get_ptr().simplex_filtration(simplex) # Same output type, better - def assign_filtration(self, simplex, cnp.ndarray[float, ndim=1] filtration): + def assign_filtration(self, simplex, filtration:list[float]|np.ndarray): """This function assigns a new filtration value to a given N-simplex. @@ -158,7 +165,7 @@ cdef class SimplexTreeMulti: assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0 self.get_ptr().assign_simplex_filtration(simplex, filtration) - + def num_vertices(self)->int: """This function returns the number of vertices of the simplicial complex. @@ -167,7 +174,6 @@ cdef class SimplexTreeMulti: :rtype: int """ return self.get_ptr().num_vertices() - def num_simplices(self)->int: """This function returns the number of simplices of the simplicial complex. @@ -177,7 +183,6 @@ cdef class SimplexTreeMulti: """ return self.get_ptr().num_simplices() - def dimension(self)->dimension_type: """This function returns the dimension of the simplicial complex. @@ -194,7 +199,6 @@ cdef class SimplexTreeMulti: methods). """ return self.get_ptr().dimension() - def upper_bound_dimension(self)->dimension_type: """This function returns a valid dimension upper bound of the simplicial complex. @@ -257,7 +261,7 @@ cdef class SimplexTreeMulti: old_filtration = np.array(self.filtration(simplex)) old_filtrations = np.array_split(old_filtration, old_filtration.shape[0] // num_parameters) filtration = np.array(filtration) - for f in old_filtration: + for f in old_filtrations: if np.all(f >= filtration) or np.all(f <= filtration): return False new_filtration = np.concatenate([old_filtration, filtration], axis = 0) @@ -599,7 +603,7 @@ cdef class SimplexTreeMulti: """ # TODO : find a way to do multiple edge collapses without python conversions. - assert self.get_ptr().get_number_of_parameters() == 2 + assert self.num_parameters == 2 if self.dimension() > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !") from tqdm import tqdm if num <= 0: @@ -698,7 +702,7 @@ cdef class SimplexTreeMulti: self.get_ptr().set_key(simplex, key) return - + def to_scc(self, path="scc_dataset.txt", progress:bool=True, overwrite:bool=False, ignore_last_generators:bool=True, strip_comments:bool=False, reverse_block:bool=True, rivet_compatible=False)->None: """ Create a file with the scc2020 standard, representing the n-filtration of the simplextree. Link : https://bitbucket.org/mkerber/chain_complex_format/src/master/ @@ -781,6 +785,9 @@ cdef class SimplexTreeMulti: return def get_filtration_grid(self, resolution, box=None, grid_strategy:str="regular"): + """ + Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical + """ if resolution is None: warn("Provide a grid on which to squeeze !") return @@ -800,7 +807,7 @@ cdef class SimplexTreeMulti: def grid_squeeze(self, box = None, resolution = None, filtration_grid:np.ndarray|list|None = None, grid_strategy:str="regular", coordinate_values:bool=False): """ - Fit the filtration of the simplextree to a grid + Fit the filtration of the simplextree to a grid. TODO : multi-critical """ if filtration_grid is None: filtration_grid = self.get_filtration_grid(resolution, grid_strategy=grid_strategy, box=box) @@ -835,7 +842,7 @@ cdef class SimplexTreeMulti: Returns ------- - self:Simplextree + self:SimplexTree """ # for s, sf in self.get_simplices(): # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)]) @@ -843,7 +850,7 @@ cdef class SimplexTreeMulti: return self - def to_gudhi(self, parameter:int=0, basepoint:None|list|np.ndarray= None): + def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None): """Converts an multi simplextree to a gudhi simplextree. Parameters ---------- @@ -874,8 +881,13 @@ cdef class SimplexTreeMulti: flatten_diag(old_ptr, new_ptr, c_basepoint, c_parameter) return new_simplextree - def resize_all_filtrations(self, num:int): #TODO : num_parameters + def set_num_parameter(self, num:int): + """ + Sets the numbers of parameters. + WARNING : it will resize all the filtrations to this size. + """ self.get_ptr().resize_all_filtrations(num) + self.get_ptr().set_number_of_parameters(num) return def __eq__(self, other:SimplexTreeMulti): @@ -884,12 +896,13 @@ cdef class SimplexTreeMulti: :rtype: bool """ return dereference(self.get_ptr()) == dereference(other.get_ptr()) - def euler_char(self, cnp.ndarray[float, ndim=2] points) -> np.ndarray: + + def euler_char(self, points:np.ndarray) -> np.ndarray: """ Computes the Euler Characteristic of the filtered complex at given (multiparameter) time Parameters ---------- - points: list[float] | list[list[float]] | cnp.ndarray + points: 2-dimensional array. List of filtration values on which to compute the euler characteristic. WARNING FIXME : the points have to have the same dimension as the simplextree. @@ -905,17 +918,18 @@ cdef class SimplexTreeMulti: # assert len(points.shape) in [1,2] # if len(points.shape) == 1: # points = [points] + assert points.ndim == 2 return np.array(self.get_ptr().euler_char(points), dtype=int) -cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil: # Calls the c++ simplextree +cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil: return (new Simplex_tree_multi_interface(dereference(stree.get_ptr()))) -def from_gudhi(simplextree, num_parameters:int=2)->SimplexTreeMulti: +def _simplextree_multify(simplextree:SimplexTree, num_parameters:int=2)->SimplexTreeMulti: """Converts a gudhi simplextree to a multi simplextree. Parameters ---------- From 8ab7de049461d0c60d616c37686dceb1f8f2b3c1 Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Wed, 15 Feb 2023 15:02:11 +0100 Subject: [PATCH 03/18] Doc, cleaning --- src/python/gudhi/simplex_tree_multi.pyx | 107 +++++++++--------------- 1 file changed, 39 insertions(+), 68 deletions(-) diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index 6755064718..341ca23bc6 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -394,28 +394,28 @@ cdef class SimplexTreeMulti: """ self.get_ptr().remove_maximal_simplex(simplex) - def prune_above_filtration(self, filtration)->bool: - """Prune above filtration value given as parameter. + # def prune_above_filtration(self, filtration)->bool: + # """Prune above filtration value given as parameter. - :param filtration: Maximum threshold value. - :type filtration: float - :returns: The filtration modification information. - :rtype: bool + # :param filtration: Maximum threshold value. + # :type filtration: float + # :returns: The filtration modification information. + # :rtype: bool - .. note:: + # .. note:: - Note that the dimension of the simplicial complex may be lower - after calling - :func:`prune_above_filtration` - than it was before. However, - :func:`upper_bound_dimension` - will return the old value, which remains a - valid upper bound. If you care, you can call - :func:`dimension` - method to recompute the exact dimension. - """ - return self.get_ptr().prune_above_filtration(filtration) + # Note that the dimension of the simplicial complex may be lower + # after calling + # :func:`prune_above_filtration` + # than it was before. However, + # :func:`upper_bound_dimension` + # will return the old value, which remains a + # valid upper bound. If you care, you can call + # :func:`dimension` + # method to recompute the exact dimension. + # """ + # return self.get_ptr().prune_above_filtration(filtration) def expansion(self, max_dim)->SimplexTreeMulti: """Expands the simplex tree containing only its one skeleton @@ -717,6 +717,12 @@ cdef class SimplexTreeMulti: Shows the progress bar. overwrite:bool = False If true, will overwrite the previous file if it already exists. + ignore_last_generators:bool=True + If true, does not write the final generators to the file. Rivet ignores them. + reverse_block:bool=True + Some obscure programs reverse the inside-block order. + rivet_compatible:bool=False + Returns a firep (old scc2020) format instead. Only Rivet uses this. Returns ------- @@ -784,9 +790,22 @@ cdef class SimplexTreeMulti: file.close() return - def get_filtration_grid(self, resolution, box=None, grid_strategy:str="regular"): + def get_filtration_grid(self, resolution:list[int]|np.ndarray, box=None, grid_strategy:str="regular"): """ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical + + Parameters + ---------- + resolution: list[int] + resolution of the grid, for each parameter + box=None : pair[list[float]] + Grid bounds. format : [low bound, high bound] + If None is given, will use the filtration bounds of the simplextree. + grid_strategy="regular" : string + Either "regular" or "quantile". + Returns + ------- + List of filtration values, for each parameter, defining the grid. """ if resolution is None: warn("Provide a grid on which to squeeze !") @@ -918,6 +937,7 @@ cdef class SimplexTreeMulti: # assert len(points.shape) in [1,2] # if len(points.shape) == 1: # points = [points] + points = np.asarray(points) assert points.ndim == 2 return np.array(self.get_ptr().euler_char(points), dtype=int) @@ -948,52 +968,3 @@ def _simplextree_multify(simplextree:SimplexTree, num_parameters:int=2)->Simplex with nogil: multify(old_ptr, new_ptr, c_num_parameters) return st - - - -# def from_firep(path:str, enfore_1critical=True): ## Not finished yet -# st = SimplexTreeMulti() -# contains_pv = lambda line : ';' in line -# is_comment = lambda line : line[0] == "#" -# def get_splx_filtration(line:str): -# line += " " -# filtration, splx = line.split(";") -# splx = [truc for truc in splx.split(" ") if truc != ""] -# boundary = [truc for truc in splx.split(" ") if truc != ""] -# splx = convert(splx) -# return filtration, splx -# n_inserted_splxs = [0]*2 -# def convert(splx): -# if len(splx) <= 0: return [n_inserted_splxs[0]] -# dim:int = len(splx)-1 -# new_splx = [] -# if dim == 1: -# k = n_inserted_splxs[0] -# for s in splx: -# new_splx.append(k-s-1) -# return new_splx -# if dim > 2: -# warn("OSKOUR") -# return new_splx -# k = sum(n_inserted_splxs) -# for s in splx: -# new_splx.append(k-s-1) -# return new_splx - -# t,s,r = [-1]*3 -# with open(path, "r") as f: -# passed == false -# for line in f.readlines()[::-1]: -# if is_comment(line): continue -# if not contains_pv: break -# filtration, splx = get_splx_filtration(line) -# if not st.insert(splx, filtration): -# old_filtration = st.filtration(splx) -# st.assign_filtration(splx,old_filtration + filtration) -# else: -# n_inserted_splxs[max(len(splx)-1,0)] += 1 -# return st - - - - From 8e6ef266c038619b68fb495ae9fccc9f973dc95d Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Wed, 15 Feb 2023 16:18:28 +0100 Subject: [PATCH 04/18] Warning remove: Comment OpenMP parallel pragmas (need to rewrite this with tbb) --- src/python/include/Simplex_tree_interface_multi.h | 4 ++-- src/python/include/utils/box.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index 1faccb6c2c..ad7218b8e3 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -18,7 +18,7 @@ //#include #include #include "Simplex_tree_multi.h" -#include +// #include #include #include @@ -291,7 +291,7 @@ class Simplex_tree_interface : public Simplex_tree { return false; return true; }; -#pragma omp parallel for +// #pragma omp parallel for for (unsigned int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel auto &euler_char_at_point = out[i]; // #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR diff --git a/src/python/include/utils/box.h b/src/python/include/utils/box.h index 1fb517188a..7407cd103b 100644 --- a/src/python/include/utils/box.h +++ b/src/python/include/utils/box.h @@ -81,7 +81,7 @@ inline Box::Box(const std::pair &box) template inline void Box::inflate(T delta) { -#pragma omp simd +// #pragma omp simd for (unsigned int i = 0; i < bottomCorner_.size(); i++){ bottomCorner_[i] -= delta; upperCorner_[i] += delta; From 9525fe72c8cf3565480a80e660d5414d7fdc7d53 Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Fri, 17 Feb 2023 15:22:56 +0100 Subject: [PATCH 05/18] Removed unnecessary files, multi_filtration class, insert_batch, cleanup --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 6 +- .../Simplex_tree_node_explicit_storage.h | 7 +- src/python/gudhi/simplex_tree_multi.pxd | 4 +- src/python/gudhi/simplex_tree_multi.pyx | 210 ++++++++++---- .../include/Simplex_tree_interface_multi.h | 40 +-- src/python/include/Simplex_tree_multi.h | 119 ++++---- .../box.h | 36 ++- .../finitely_critical_filtrations.h | 65 +++++ .../line.h | 36 +-- src/python/include/utils/debug.h | 125 --------- src/python/include/utils/utilities.h | 262 ------------------ 11 files changed, 332 insertions(+), 578 deletions(-) rename src/python/include/{utils => finitely_critical_filtrations}/box.h (75%) create mode 100644 src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h rename src/python/include/{utils => finitely_critical_filtrations}/line.h (67%) delete mode 100644 src/python/include/utils/debug.h delete mode 100644 src/python/include/utils/utilities.h diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index e726287829..fc5a878b63 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1757,14 +1757,14 @@ class Simplex_tree { //MULTIPERS STUFF public: - void set_number_of_parameters(unsigned int num){ + void set_number_of_parameters(int num){ number_of_parameters_ = num; } - unsigned int get_number_of_parameters() const{ + int get_number_of_parameters() const{ return number_of_parameters_; } private: - unsigned int number_of_parameters_; + int number_of_parameters_; }; // Print a Simplex_tree in os. diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h index 2c434b3be6..1a54649991 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h @@ -31,14 +31,11 @@ struct Simplex_tree_node_explicit_storage : SimplexTree::Filtration_simplex_base typedef typename SimplexTree::Filtration_value Filtration_value; typedef typename SimplexTree::Simplex_key Simplex_key; - Simplex_tree_node_explicit_storage(Siblings * sib, - Filtration_value filtration) //MULTIPERS : init to 0 not possible + Simplex_tree_node_explicit_storage(Siblings * sib = nullptr, + Filtration_value filtration = {}) : children_(sib) { this->assign_filtration(filtration); } - Simplex_tree_node_explicit_storage() // Empty constructor necessary - : children_(nullptr) { - } /* * Assign children to the node diff --git a/src/python/gudhi/simplex_tree_multi.pxd b/src/python/gudhi/simplex_tree_multi.pxd index f97f1f3748..5fece5764e 100644 --- a/src/python/gudhi/simplex_tree_multi.pxd +++ b/src/python/gudhi/simplex_tree_multi.pxd @@ -70,7 +70,7 @@ cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": vector[pair[simplex_type, filtration_type]] get_cofaces(vector[int] simplex, int dimension) nogil void expansion(int max_dim) nogil except + void remove_maximal_simplex(simplex_type simplex) nogil - bool prune_above_filtration(filtration_type filtration) nogil + # bool prune_above_filtration(filtration_type filtration) nogil # bool make_filtration_non_decreasing() nogil # void compute_extended_filtration() nogil Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) nogil except + @@ -90,7 +90,7 @@ cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": void expansion_with_blockers_callback(int dimension, blocker_func_t user_func, void *user_data) ## MULTIPERS STUFF - void reset_keys() nogil + void set_keys_to_enumerate() nogil int get_key(const simplex_type) nogil void set_key(simplex_type, int) nogil void fill_lowerstar(vector[double], int) nogil diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index 341ca23bc6..d836fe762a 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -5,6 +5,7 @@ # Copyright (C) 2016 Inria # # Modification(s): +# - 2023 David Loiseaux : Conversions with standard simplextree, scc2020 format, edge collapses, euler characteristic, grid filtrations. # - 2022/11 Hannah Schreiber / David Loiseaux : adapt for multipersistence. # - YYYY/MM Author: Description of the modification @@ -14,10 +15,21 @@ __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" __license__ = "MIT" - +from libc.stdint cimport intptr_t, int32_t, int64_t from cython.operator import dereference, preincrement from libc.stdint cimport intptr_t from libc.stdint cimport uintptr_t + + +ctypedef fused some_int: + int32_t + int64_t + +ctypedef fused some_float: + float + double + + from typing import Any cimport numpy as cnp @@ -28,7 +40,7 @@ cimport gudhi.simplex_tree_multi cimport cython from gudhi import SimplexTree ## Small hack for typing -from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated + from warnings import warn @@ -36,10 +48,10 @@ from warnings import warn ctypedef double value_type cdef extern from "Simplex_tree_multi.h" namespace "Gudhi": - void multify(const uintptr_t, const uintptr_t, const unsigned int) nogil + void multify(const uintptr_t, const uintptr_t, const unsigned int) nogil except + void flatten(const uintptr_t, const uintptr_t, const unsigned int) nogil void flatten_diag(const uintptr_t, const uintptr_t, const vector[value_type], int) nogil - void squeeze_filtration(uintptr_t, const vector[vector[value_type]]&, bool) nogil + void squeeze_filtration(uintptr_t, const vector[vector[value_type]]&, bool) nogil except + vector[vector[value_type]] get_filtration_values(uintptr_t) nogil @@ -54,8 +66,8 @@ cdef class SimplexTreeMulti: Tree: An Efficient Data Structure for General Simplicial Complexes. Algorithmica, pages 1–22, 2014. - This class is a filtered, with keys, and non contiguous vertices version - of the simplex tree. + This class is a multi-filtered, with keys, and non contiguous vertices version + of the simplex tree. """ # unfortunately 'cdef public Simplex_tree_multi_interface* thisptr' is not possible # Use intptr_t instead to cast the pointer @@ -69,17 +81,18 @@ cdef class SimplexTreeMulti: # cdef Simplex_tree_persistence_interface * pcohptr # Fake constructor that does nothing but documenting the constructor def __init__(self, other = None, num_parameters:int=2): - """SimplexTree constructor. - - :param other: If `other` is `None` (default value), an empty `SimplexTree` is created. - If `other` is a `SimplexTree`, the `SimplexTree` is constructed from a deep copy of `other`. - :type other: SimplexTree (Optional) + """SimplexTreeMulti constructor. + + :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created. + If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`. + If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`. + :type other: SimplexTree or SimplexTreeMulti (Optional) + :param num_parameters: The number of parameter of the multi-parameter filtration. + :type num_parameters: int :returns: An empty or a copy simplex tree. - :rtype: SimplexTree + :rtype: SimplexTreeMulti - :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`. - :note: If the `SimplexTree` is a copy, the persistence information is not copied. If you need it in the clone, - you have to call :func:`compute_persistence` on it even if you had already computed it in the original. + :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`. """ # The real cython constructor @@ -118,7 +131,7 @@ cdef class SimplexTreeMulti: def copy(self)->SimplexTreeMulti: """ :returns: A simplex tree that is a deep copy of itself. - :rtype: SimplexTree + :rtype: SimplexTreeMulti :note: The persistence information is not copied. If you need it in the clone, you have to call :func:`compute_persistence` on it even if you had already computed it in the original. @@ -130,28 +143,26 @@ cdef class SimplexTreeMulti: def __deepcopy__(self): return self.copy() - def filtration(self, simplex)->filtration_type: + def filtration(self, simplex:list[int]|np.ndarray)->filtration_type: """This function returns the filtration value for a given N-simplex in this simplicial complex, or +infinity if it is not in the complex. :param simplex: The N-simplex, represented by a list of vertex. :type simplex: list of int - :returns: The simplicial complex filtration value. - :rtype: float + :returns: The simplicial complex multi-critical filtration value. + :rtype: numpy array of shape (-1, num_parameters) """ - # filtration_vector = self.get_ptr().simplex_filtration(simplex) - # filtrations = np.array_split(filtration_vector, len(filtration_vector) // self.num_parameters) - # return filtrations.squeeze() # If 1 critical returns only 1 filtration - return self.get_ptr().simplex_filtration(simplex) # Same output type, better + filtration_vector = np.array(self.get_ptr().simplex_filtration(simplex)) + return filtration_vector.reshape((-1, self.num_parameters)) - def assign_filtration(self, simplex, filtration:list[float]|np.ndarray): - """This function assigns a new filtration value to a + def assign_filtration(self, simplex:list[int]|np.ndarray, filtration:list[float]|np.ndarray): + """This function assigns a new multi-critical filtration value to a given N-simplex. :param simplex: The N-simplex, represented by a list of vertex. :type simplex: list of int - :param filtration: The new filtration value. - :type filtration: float + :param filtration: The new filtration(s) value(s), concatenated. + :type filtration: list[float] or np.ndarray[float, ndim=1] .. note:: Beware that after this operation, the structure may not be a valid @@ -252,22 +263,67 @@ cdef class SimplexTreeMulti: otherwise (whatever its original filtration value). :rtype: bool """ - # TODO C++ + # TODO C++, to be compatible with insert_batch and multicritical filtrations num_parameters = self.get_ptr().get_number_of_parameters() if filtration is None: filtration = np.array([-np.inf]*num_parameters, dtype = np.float) simplex_already_exists = not self.get_ptr().insert(simplex, filtration) if simplex_already_exists: - old_filtration = np.array(self.filtration(simplex)) - old_filtrations = np.array_split(old_filtration, old_filtration.shape[0] // num_parameters) - filtration = np.array(filtration) + old_filtrations = self.filtration(simplex) for f in old_filtrations: if np.all(f >= filtration) or np.all(f <= filtration): return False - new_filtration = np.concatenate([old_filtration, filtration], axis = 0) + new_filtration = np.concatenate([*old_filtrations, filtration], axis = 0) self.assign_filtration(simplex, new_filtration) return True return True + + @cython.boundscheck(False) + @cython.wraparound(False) + def insert_batch(self,some_int[:,:] vertex_array, some_float[:,:] filtrations): + """Inserts k-simplices given by a sparse array in a format similar + to `torch.sparse `_. + The n-th simplex has vertices `vertex_array[0,n]`, ..., + `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`. + /!\ Only compatible with 1-critical filtrations. If a simplex is repeated, + only one filtration value will be taken into account. + + :param vertex_array: the k-simplices to insert. + :type vertex_array: numpy.array of shape (k+1,n) + :param filtrations: the filtration values. + :type filtrations: numpy.array of shape (n,num_parameters) + """ + # TODO : multi-critical + cdef vector[int] vertices = np.unique(vertex_array) + cdef Py_ssize_t k = vertex_array.shape[0] + cdef Py_ssize_t n = vertex_array.shape[1] + assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations' + assert filtrations.shape[1] == self.num_parameters + cdef Py_ssize_t i + cdef Py_ssize_t j + cdef vector[int] v + cdef vector[double] w + cdef int n_parameters = self.num_parameters + with nogil: + # Without this, it we end up inserting vertic could be slow ifes in a bad order (flat_map). + # NaN currently does the wrong thing + # self.get_ptr().insert_batch_vertices(vertices, INFINITY) ## TODO + +# if k !=1: # Ensure all vertices are already insered first. +# for vertex in vertices: +# if not self.get_ptr().find_simplex([vertex]): +# warn("Failed. Insert all vertices first !") +# return + + for i in range(n): + for j in range(k): + v.push_back(vertex_array[j, i]) + for j in range(n_parameters): + w.push_back(filtrations[i,j]) + self.get_ptr().insert(v, w) + v.clear() + w.clear() + return self def get_simplices(self): """This function returns a generator with simplices and their given @@ -454,6 +510,23 @@ cdef class SimplexTreeMulti: # return self.get_ptr().make_filtration_non_decreasing() if start_dimension <= 0: start_dimension = 1 +# cdef Simplex_tree_skeleton_iterator it +# cdef Simplex_tree_skeleton_iterator end +## cdef Simplex_tree_simplex_handle sh = dereference(it) +# cdef pair[vector[int], double] simplex_filtration +# cdef int dim +# cdef int c_start_dimension = start_dimension +# cdef int c_stop_dimension = self.dimension()+1 +# cdef cnp.ndarray[float, ] +# with nogil: +# for dim in range(c_start_dimension, c_stop_dimension): +# it = self.get_ptr().get_skeleton_iterator_begin(dim) +# end = self.get_ptr().get_skeleton_iterator_end(dim) +# while it != end: +# simplex_filtration = self.get_ptr().get_simplex_and_filtration(dereference(it)) +# if simplex_filtration.size() == dim+1: +# # TODO, +# preincrement(it) for dim in range(start_dimension, self.dimension()+1): for splx, f in self.get_skeleton(dim): if len(splx) != dim + 1: continue @@ -573,10 +646,11 @@ cdef class SimplexTreeMulti: # self.compute_persistence(homology_coeff_field, min_persistence, persistence_dim_max) # return self.pcohptr.get_persistence() - def get_edge_list(self): - return self.get_ptr().get_edge_list() +## This function is only meant for the edge collapse interface. +# def get_edge_list(self): +# return self.get_ptr().get_edge_list() - def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False)->SimplexTreeMulti: + def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False): """(Strong) collapse of 1 critical clique complex, compatible with 2-parameter filtration. Parameters @@ -602,6 +676,7 @@ cdef class SimplexTreeMulti: A simplex tree that has the same homology over this bifiltration. """ + from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated # TODO : find a way to do multiple edge collapses without python conversions. assert self.num_parameters == 2 if self.dimension() > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !") @@ -632,11 +707,23 @@ cdef class SimplexTreeMulti: else: n = len(edges) # n = edges.size() + reduced_tree = SimplexTreeMulti(num_parameters=self.num_parameters) - for splx, f in self.get_skeleton(0): # Adds vertices back - reduced_tree.insert(splx, f) - for e, (f1, f2) in edges: # Adds reduced edges back # TODO : with insert_batch - reduced_tree.insert(e, [f1,f2]) + + ## Adds vertices back, with good filtration + vertices = np.array([splx for splx, f in self.get_skeleton(0)], dtype=int).T + vertices_filtration = np.array([f for splx, f in self.get_skeleton(0)], dtype=float) + reduced_tree.insert_batch(vertices, vertices_filtration) + + ## Adds edges again + edges_filtration = np.array([f for e,f in edges]) + edges = np.array([e for e, _ in edges], dtype=int).T + reduced_tree.insert_batch(edges, edges_filtration) + +# for splx, f in self.get_skeleton(0): # Adds vertices back +# reduced_tree.insert(splx, f) +# for e, (f1, f2) in edges: # Adds reduced edges back # TODO : with insert_batch +# reduced_tree.insert(e, [f1,f2]) self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable) self.expansion(max_dimension) # Expands back the simplextree to the original dimension. # self.make_filtration_non_decreasing(2) @@ -663,8 +750,7 @@ cdef class SimplexTreeMulti: from os import remove if exists(path): if not(overwrite): - print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") - return + raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") remove(path) file = open(path, "a") file.write("--datatype bifiltration\n") @@ -692,11 +778,11 @@ cdef class SimplexTreeMulti: def num_parameters(self)->int: return self.get_ptr().get_number_of_parameters() def get_simplices_of_dimension(self, dim:int): - return self.get_ptr().get_simplices_of_dimension(dim) + return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int) def key(self, simplex:list|np.ndarray): return self.get_ptr().get_key(simplex) - def reset_keys(self)->None: - self.get_ptr().reset_keys() + def set_keys_to_enumerate(self)->None: + self.get_ptr().set_keys_to_enumerate() return def set_key(self,simplex:list|np.ndarray, key:int)->None: self.get_ptr().set_key(simplex, key) @@ -728,15 +814,14 @@ cdef class SimplexTreeMulti: ------- Nothing """ - ### GUDHI BUGFIX - self.reset_keys() + ### initialize keys + self.set_keys_to_enumerate() ### File from os.path import exists from os import remove if exists(path): if not(overwrite): - print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") - return + raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") remove(path) file = open(path, "a") file.write("scc2020\n") if not rivet_compatible else file.write("firep\n") @@ -778,7 +863,7 @@ cdef class SimplexTreeMulti: if not strip_comments: file.write(f"# Block of dimension {dim}\n") if reverse_block: tsr[dim].reverse() for splx in tsr[dim]: # for simplices of dimension - F = self.filtration(splx) + F = np.concatenate(self.filtration(splx), axis=0) nbirth = (int)(len(F)//num_parameters) for i in range(nbirth): simplex_filtration = F[i*num_parameters:(i+1)*num_parameters] @@ -824,12 +909,16 @@ cdef class SimplexTreeMulti: return filtration_grid - def grid_squeeze(self, box = None, resolution = None, filtration_grid:np.ndarray|list|None = None, grid_strategy:str="regular", coordinate_values:bool=False): + def grid_squeeze(self, filtration_grid:np.ndarray|list, coordinate_values:bool=False): """ - Fit the filtration of the simplextree to a grid. TODO : multi-critical + Fit the filtration of the simplextree to a grid. + + :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method. + :type filtration_grid: list[list[float]] + :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid. + :type coordinate_values: bool """ - if filtration_grid is None: - filtration_grid = self.get_filtration_grid(resolution, grid_strategy=grid_strategy, box=box) + #TODO : multi-critical cdef vector[vector[value_type]] c_filtration_grid = filtration_grid cdef intptr_t ptr = self.thisptr @@ -861,7 +950,7 @@ cdef class SimplexTreeMulti: Returns ------- - self:SimplexTree + self:SimplexTreeMulti """ # for s, sf in self.get_simplices(): # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)]) @@ -882,7 +971,7 @@ cdef class SimplexTreeMulti: There are no safeguard yet, it WILL crash if asking for a parameter that is not filled. Returns ------- - A gudhi simplextree with chosen 1D filtration. + A SimplexTree with chosen 1D filtration. """ # FIXME : deal with multicritical filtrations import gudhi as gd @@ -916,19 +1005,19 @@ cdef class SimplexTreeMulti: """ return dereference(self.get_ptr()) == dereference(other.get_ptr()) - def euler_char(self, points:np.ndarray) -> np.ndarray: + def euler_char(self, points:np.ndarray|list) -> np.ndarray: """ Computes the Euler Characteristic of the filtered complex at given (multiparameter) time Parameters ---------- points: 2-dimensional array. List of filtration values on which to compute the euler characteristic. - WARNING FIXME : the points have to have the same dimension as the simplextree. Returns ------- The list of euler characteristic values """ + # TODO : muticritical, compute it from a coordinate simplextree, tbb. # if len(points) == 0: # return cnp.empty() # if type(points[0]) is float: @@ -937,10 +1026,9 @@ cdef class SimplexTreeMulti: # assert len(points.shape) in [1,2] # if len(points.shape) == 1: # points = [points] - points = np.asarray(points) - assert points.ndim == 2 - return np.array(self.get_ptr().euler_char(points), dtype=int) - + cdef cnp.ndarray[double, ndim=2] c_points = np.asarray(points) + assert c_points.shape[1] == self.num_parameters + return np.asarray(self.get_ptr().euler_char(c_points)) cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil: diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index ad7218b8e3..d7def96b18 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -19,6 +19,7 @@ #include #include "Simplex_tree_multi.h" // #include +#include "finitely_critical_filtrations/finitely_critical_filtrations.h" #include #include @@ -45,8 +46,6 @@ class Simplex_tree_interface : public Simplex_tree { using Boundary_simplex_iterator = typename Base::Boundary_simplex_iterator; typedef bool (*blocker_func_t)(Simplex simplex, void *user_data); using euler_chars_type = std::vector; - using point_type = std::vector; - using points_type = std::vector; public: @@ -218,8 +217,8 @@ class Simplex_tree_interface : public Simplex_tree { // ######################## MULTIPERS STUFF - void reset_keys(){ - unsigned int count = 0; + void set_keys_to_enumerate(){ + int count = 0; for (auto sh : Base::filtration_simplex_range()) Base::assign_key(sh, count++); } @@ -256,7 +255,7 @@ class Simplex_tree_interface : public Simplex_tree { simplex_list.push_back(simplex); } } - simplex_list.shrink_to_fit(); +/* simplex_list.shrink_to_fit();*/ return simplex_list; } using edge_list = std::vector, std::pair>>; @@ -272,33 +271,34 @@ class Simplex_tree_interface : public Simplex_tree { simplex_list.push_back({simplex, {f[0], f[1]}}); } } - simplex_list.shrink_to_fit(); +/* simplex_list.shrink_to_fit();*/ return simplex_list; } - euler_chars_type euler_char(const std::vector> &point_list){ // TODO multi-critical - const unsigned int npts = point_list.size(); + euler_chars_type euler_char(const std::vector> &point_list){ // TODO multi-critical + const int npts = point_list.size(); if (npts == 0){ return {}; } - const unsigned int nparameters = point_list[0].size(); euler_chars_type out(point_list.size(), 0.); - auto is_greater = [nparameters](const point_type &a, const point_type &b){ //french greater - for (unsigned int i = 0; i< nparameters; i++) - if( a[i] < b[i]) - return false; - return true; - }; + // auto is_greater = [nparameters](const point_type &a, const point_type &b){ //french greater + // for (int i = 0; i< nparameters; i++) + // if( a[i] < b[i]) + // return false; + // return true; + // }; // #pragma omp parallel for - for (unsigned int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel + for (int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel auto &euler_char_at_point = out[i]; // #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR for(const auto &SimplexHandle : Base::complex_simplex_range()){ - const auto &pt = point_list[i]; - const auto &filtration = Base::filtration(SimplexHandle); - if (is_greater(pt, filtration)){ + const Finitely_critical_multi_filtration &pt = *(Finitely_critical_multi_filtration*)(&point_list[i]); + const std::vector& filtration_ = Base::filtration(SimplexHandle); + const Finitely_critical_multi_filtration &filtration = *(Finitely_critical_multi_filtration*)(&filtration_); + // if (is_greater(pt, filtration)){ + if (filtration <= pt){ int sign = Base::dimension(SimplexHandle) %2 ? -1 : 1; euler_char_at_point += sign; } @@ -309,7 +309,7 @@ class Simplex_tree_interface : public Simplex_tree { void resize_all_filtrations(int num){ //TODO : that is for 1 critical filtrations if (num < 0) return; for(const auto &SimplexHandle : Base::complex_simplex_range()){ - std::vector new_filtration_value = Base::filtration(SimplexHandle); + std::vector new_filtration_value = Base::filtration(SimplexHandle); new_filtration_value.resize(num); Base::assign_filtration(SimplexHandle, new_filtration_value); } diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index a6b9cda2ec..8fe981d60e 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -14,8 +14,10 @@ #include #include -#include "utils/box.h" -#include "utils/line.h" +#include "finitely_critical_filtrations/finitely_critical_filtrations.h" +#include "finitely_critical_filtrations/line.h" + + @@ -25,38 +27,31 @@ namespace Gudhi { * Maximum number of simplices to compute persistence is std::numeric_limits::max() * (about 4 billions of simplices). */ -using value_type = double; + struct Simplex_tree_options_multidimensional_filtration { public: typedef linear_indexing_tag Indexing_tag; typedef int Vertex_handle; - typedef std::vector Filtration_value; + typedef double value_type; + using Filtration_value = std::vector; // Cannot put Finitely_critical_multi_filtration, cython doesn't know how to convert inheritence typedef std::uint32_t Simplex_key; static const bool store_key = true; static const bool store_filtration = true; static const bool contiguous_vertices = false; }; -using option_multi = Simplex_tree_options_multidimensional_filtration; -using option_std = Simplex_tree_options_full_featured; -bool operator<(const std::vector& v1, const std::vector& v2) -{ - bool isSame = true; - if (v1.size() != v2.size()) isSame = false; - for (unsigned int i = 0; i < std::min(v1.size(), v2.size()); ++i){ - if (v1[i] > v2[i]) return false; - if (isSame && v1[i] != v2[i]) isSame = false; - } - if (isSame) return false; - return true; -} +using options_multi = Simplex_tree_options_multidimensional_filtration; +using options_std = Simplex_tree_options_full_featured; +using multi_filtration_type = std::vector; +using multi_filtration_grid = std::vector; + -void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const unsigned int dimension){ - Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(newsplxptr);; +void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension){ + Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(newsplxptr); if (dimension <= 0) - {std::cout << "Empty filtration\n"; return ;} - std::vector f(dimension); + {std::cerr << "Empty filtration\n"; throw ;} + Finitely_critical_multi_filtration f(dimension); for (auto &simplex_handle : st.complex_simplex_range()){ std::vector simplex; for (auto vertex : st.simplex_vertex_range(simplex_handle)) @@ -65,31 +60,31 @@ void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const unsigned st_multi.insert_simplex(simplex,f); } } -void flatten(const uintptr_t splxptr, const uintptr_t newsplxptr, const unsigned int dimension = 0){ - Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - +void flatten(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension = 0){ + Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - value_type f = st_multi.filtration(simplex_handle)[dimension]; + Simplex_tree_options_multidimensional_filtration::value_type f = dimension >= 0 ? st_multi.filtration(simplex_handle)[dimension] : 0; st.insert_simplex(simplex,f); } } -void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ - Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - utils::Line l(basepoint); +template +void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ + Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + Gudhi::Line l(basepoint); for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - std::vector f = st_multi.filtration(simplex_handle); + std::vector f = st_multi.filtration(simplex_handle); if (dimension <0) dimension = 0; - value_type new_filtration = l.push_forward(f)[dimension]; + options_multi::value_type new_filtration = l.push_forward(f)[dimension]; st.insert_simplex(simplex,new_filtration); } @@ -97,17 +92,16 @@ void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std } -using filtration_grid = std::vector>; -using grid_value = std::vector; + template -std::vector find_coordinates(const grid_value &x, const filtration_grid &grid){ - // TODO: optimize with dichotomy +std::vector find_coordinates(const std::vector &x, const multi_filtration_grid &grid){ + // TODO: optimize with, e.g., dichotomy std::vector coordinates(grid.size()); - for (unsigned int parameter = 0; parameter< grid.size(); parameter++){ + for (int parameter = 0; parameter< (int)grid.size(); parameter++){ const auto& filtration = grid[parameter]; const auto& to_project = x[parameter]; - grid_value distance_vector(filtration.size()); - for (unsigned int i = 0; i < filtration.size(); i++){ + std::vector distance_vector(filtration.size()); + for (int i = 0; i < (int)filtration.size(); i++){ distance_vector[i] = std::abs(to_project - filtration[i]); } coordinates[parameter] = std::distance(distance_vector.begin(), std::min_element(distance_vector.begin(), distance_vector.end())); @@ -117,36 +111,36 @@ std::vector find_coordinates(const grid_value &x, const filtration_gri // TODO integer filtrations, does this help with performance ? // projects filtrations values to the grid. If coordinate_values is set to true, the filtration values are the coordinates of this grid -void squeeze_filtration(uintptr_t splxptr, const std::vector> &grid, bool coordinate_values=true){ +void squeeze_filtration(uintptr_t splxptr, const multi_filtration_grid &grid, bool coordinate_values=true){ - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - unsigned int num_parameters = st_multi.get_number_of_parameters(); - if (grid.size() != num_parameters){ + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + int num_parameters = st_multi.get_number_of_parameters(); + if ((int)grid.size() != num_parameters){ std::cerr << "Bad grid !" << std::endl; - return; + throw; } for (const auto &simplex_handle : st_multi.complex_simplex_range()){ - std::vector simplex_filtration = st_multi.filtration(simplex_handle); + std::vector simplex_filtration = st_multi.filtration(simplex_handle); if (coordinate_values) - st_multi.assign_filtration(simplex_handle, find_coordinates(simplex_filtration, grid)); + st_multi.assign_filtration(simplex_handle, find_coordinates(simplex_filtration, grid)); else{ auto coordinates = find_coordinates(simplex_filtration, grid); - grid_value squeezed_filtration(num_parameters); - for (unsigned int parameter = 0; parameter < num_parameters; parameter++) + std::vector squeezed_filtration(num_parameters); + for (int parameter = 0; parameter < num_parameters; parameter++) squeezed_filtration[parameter] = grid[parameter][coordinates[parameter]]; st_multi.assign_filtration(simplex_handle, squeezed_filtration); } } return; } -std::vector> get_filtration_values(const uintptr_t splxptr){ - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - unsigned int num_parameters = st_multi.get_number_of_parameters(); - std::vector> out(num_parameters, std::vector(st_multi.num_simplices())); - unsigned int count = 0; +multi_filtration_grid get_filtration_values(const uintptr_t splxptr){ + Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + int num_parameters = st_multi.get_number_of_parameters(); + multi_filtration_grid out(num_parameters, multi_filtration_type(st_multi.num_simplices())); + int count = 0; for (const auto &simplex_handle : st_multi.complex_simplex_range()){ const auto filtration = st_multi.filtration(simplex_handle); - for (unsigned int parameter=0; parameter < num_parameters; parameter++){ + for (int parameter=0; parameter < num_parameters; parameter++){ out[parameter][count] = filtration[parameter]; } count++; @@ -160,24 +154,21 @@ std::vector> get_filtration_values(const uintptr_t splxp namespace std { template<> -class numeric_limits > +class numeric_limits { public: - static std::vector infinity() throw(){ - return std::vector(1, numeric_limits::infinity()); + static Gudhi::multi_filtration_type infinity() throw(){ + return Gudhi::multi_filtration_type(1, std::numeric_limits::infinity()); }; - static std::vector quiet_NaN() throw(){ - return std::vector(1, numeric_limits::quiet_NaN()); + static Gudhi::multi_filtration_type quiet_NaN() throw(){ + return Gudhi::multi_filtration_type(1, numeric_limits::quiet_NaN()); }; }; -} // namespace std - - - +} #endif // SIMPLEX_TREE_MULTI_H_ diff --git a/src/python/include/utils/box.h b/src/python/include/finitely_critical_filtrations/box.h similarity index 75% rename from src/python/include/utils/box.h rename to src/python/include/finitely_critical_filtrations/box.h index 7407cd103b..cf202c660f 100644 --- a/src/python/include/utils/box.h +++ b/src/python/include/finitely_critical_filtrations/box.h @@ -18,11 +18,9 @@ #include #include -// #include #include #include #include -// #include "gudhi/Simplex_tree.h" @@ -33,28 +31,28 @@ * @brief Holds the square box on which to compute. */ -namespace utils{ +namespace Gudhi{ template class Box { - using corner_type = std::vector; + using point_type = std::vector; public: Box(); - Box(const corner_type& bottomCorner, const corner_type& upperCorner); - Box(const std::pair& box); + Box(const point_type& bottomCorner, const point_type& upperCorner); + Box(const std::pair& box); void inflate(T delta); - const corner_type& get_bottom_corner() const; - const corner_type& get_upper_corner() const; + const point_type& get_bottom_corner() const; + const point_type& get_upper_corner() const; bool contains(const point_type& point) const; void infer_from_filters(const std::vector> &Filters_list); bool is_trivial() const ; private: - corner_type bottomCorner_; - corner_type upperCorner_; + point_type bottomCorner_; + point_type upperCorner_; }; template @@ -62,7 +60,7 @@ inline Box::Box() {} template -inline Box::Box(const corner_type &bottomCorner, const corner_type &upperCorner) +inline Box::Box(const point_type &bottomCorner, const point_type &upperCorner) : bottomCorner_(bottomCorner), upperCorner_(upperCorner) { @@ -72,7 +70,7 @@ inline Box::Box(const corner_type &bottomCorner, const corner_type &upperCorn } template -inline Box::Box(const std::pair &box) +inline Box::Box(const std::pair &box) : bottomCorner_(box.first), upperCorner_(box.second) {} @@ -82,7 +80,7 @@ template inline void Box::inflate(T delta) { // #pragma omp simd - for (unsigned int i = 0; i < bottomCorner_.size(); i++){ + for (int i = 0; i < bottomCorner_.size(); i++){ bottomCorner_[i] -= delta; upperCorner_[i] += delta; } @@ -90,14 +88,14 @@ inline void Box::inflate(T delta) template inline void Box::infer_from_filters(const std::vector> &Filters_list){ - unsigned int dimension = Filters_list.size(); - unsigned int nsplx = Filters_list[0].size(); + int dimension = Filters_list.size(); + int nsplx = Filters_list[0].size(); std::vector lower(dimension); std::vector upper(dimension); - for (unsigned int i =0; i < dimension; i++){ + for (int i = 0; i < dimension; i++){ T min = Filters_list[i][0]; T max = Filters_list[i][0]; - for (unsigned int j=1; j::contains(const std::vector &point) const { if (point.size() != bottomCorner_.size()) return false; - for (unsigned int i = 0; i < point.size(); i++){ + for (int i = 0; i < (int)point.size(); i++){ if (point[i] < bottomCorner_[i]) return false; if (point[i] > upperCorner_[i]) return false; } @@ -147,7 +145,7 @@ std::ostream& operator<<(std::ostream& os, const Box& box) return os; } -} // namespace utils +} // namespace Gudhi #endif // BOX_H_INCLUDED diff --git a/src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h b/src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h new file mode 100644 index 0000000000..8c677db8cc --- /dev/null +++ b/src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h @@ -0,0 +1,65 @@ +#pragma once + + +namespace Gudhi{ + +template +class Finitely_critical_multi_filtration : public std::vector { + // Class to prevent doing illegal stuff with the standard library, e.g., compare two vectors +public: + // using std::vector :: vector; // inherit constructor + explicit Finitely_critical_multi_filtration(std::vector * v) : std::vector(v) {}; // I'm not sure if it does a copy ? + explicit Finitely_critical_multi_filtration(int n) : std::vector(n) {}; + explicit Finitely_critical_multi_filtration(int n, T value) : std::vector(n,value) {}; + + //TODO : multicritical -> iterator over filtrations +private: + +}; +template +bool operator<(const Finitely_critical_multi_filtration& v1, const Finitely_critical_multi_filtration& v2) +{ + bool isSame = true; + if (v1.size() != v2.size()){ + std::cerr << "Filtrations are not of the same size ! (" << v1.size() << " " << v2.size() << ")."; + throw; + } + int n = v1.size(); + for (int i = 0; i < n; ++i){ + if (v1[i] > v2[i]) return false; + if (isSame && v1[i] != v2[i]) isSame = false; + } + if (isSame) return false; + return true; +} + + + +template +bool operator<=(const Finitely_critical_multi_filtration& v1, const Finitely_critical_multi_filtration& v2) +{ + if (v1.size() != v2.size()){ + std::cerr << "Filtrations are not of the same size ! (" << v1.size() << " " << v2.size() << ")."; + throw; + } + int n = v1.size(); + for (int i = 0; i < n; ++i){ + if (v1[i] > v2[i]) return false; + } + return true; +} + +template +Finitely_critical_multi_filtration& operator-=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_substract){ + std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); + return result; +} + + +template +Finitely_critical_multi_filtration& operator+=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_add){ + std::transform(result.begin(), result.end(), to_add.begin(),result.begin(), std::plus()); + return result; +} + +} // namespace Gudhi diff --git a/src/python/include/utils/line.h b/src/python/include/finitely_critical_filtrations/line.h similarity index 67% rename from src/python/include/utils/line.h rename to src/python/include/finitely_critical_filtrations/line.h index cba301ecae..c975d8a4aa 100644 --- a/src/python/include/utils/line.h +++ b/src/python/include/finitely_critical_filtrations/line.h @@ -16,22 +16,22 @@ #ifndef LINE_FILTRATION_TRANSLATION_H_INCLUDED #define LINE_FILTRATION_TRANSLATION_H_INCLUDED -#include "utilities.h" #include "box.h" +#include "finitely_critical_filtrations.h" -namespace utils{ - using point_type = std::vector; - template +namespace Gudhi{ + + template class Line { - + using point_type = std::vector; public: Line(); Line(point_type x); Line(point_type x, point_type v); point_type push_forward(point_type x) const; point_type push_back(point_type x) const; - dimension_type get_dim() const; + int get_dim() const; std::pair get_bounds(const Box &box) const; @@ -54,37 +54,39 @@ namespace utils{ this->direction_.swap(v); } template - point_type Line::push_forward(point_type x) const{ //TODO remove copy - x -= basepoint_; - value_type t=negInf; + std::vector Line::push_forward(std::vector x) const{ //TODO remove copy + Finitely_critical_multi_filtration& y = *(Finitely_critical_multi_filtration*)(&x); + const Finitely_critical_multi_filtration& basepoint = *(Finitely_critical_multi_filtration*)(&basepoint_); + y -= basepoint; + T t = - std::numeric_limits::infinity();; for (unsigned int i = 0; idirection_.size() > i ? direction_[i] : 1; + T dir = this->direction_.size() > i ? direction_[i] : 1; t = std::max(t, x[i]/dir); } - point_type out(basepoint_.size()); + std::vector out(basepoint_.size()); for (unsigned int i = 0; i < out.size(); i++) out[i] = basepoint_[i] + t * (this->direction_.size() > i ? direction_[i] : 1) ; return out; } template - point_type Line::push_back(point_type x) const{ + std::vector Line::push_back(std::vector x) const{ x -= basepoint_; - value_type t=inf; + T t = std::numeric_limits::infinity(); for (unsigned int i = 0; idirection_.size() > i ? direction_[i] : 1; + T dir = this->direction_.size() > i ? direction_[i] : 1; t = std::min(t, x[i]/dir); } - point_type out(basepoint_.size()); + std::vector out(basepoint_.size()); for (unsigned int i = 0; i < out.size(); i++) out[i] = basepoint_[i] + t * (this->direction_.size() > i ? direction_[i] : 1) ; return out; } template - dimension_type Line::get_dim() const{ + int Line::get_dim() const{ return basepoint_.size(); } template - std::pair Line::get_bounds(const Box &box) const{ + std::pair, std::vector> Line::get_bounds(const Box &box) const{ return {this->push_forward(box.get_bottom_corner()), this->push_back(box.get_upper_corner())}; } diff --git a/src/python/include/utils/debug.h b/src/python/include/utils/debug.h deleted file mode 100644 index 19c804739a..0000000000 --- a/src/python/include/utils/debug.h +++ /dev/null @@ -1,125 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): David Loiseaux - * - * Copyright (C) 2021 Inria - * - * Modification(s): - * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence class, renaming and cleanup. - */ -/** - * @file debug.h - * @author David Loiseaux, - * @brief Display functions for debug purposes - */ - - -#ifndef DEBUG_H_INCLUDED -#define DEBUG_H_INCLUDED - -#include -#include -#include -#include - -#include - -namespace utils { - -using clk = std::chrono::high_resolution_clock; -using tp = clk::time_point; - -constexpr bool debug = false; - - -class Timer -{ -public: - Timer() : activated_(false) {} - Timer(const std::string &string, bool verbose) - : timer_(clk::now()), activated_(verbose) - { - if(verbose){ - std::cout << string << std::flush; - } - } - ~Timer(){ - if (activated_) - { - std::chrono::duration elapsed = - std::chrono::duration_cast>( - clk::now() - timer_); - std::cout << " Done ! It took "<< elapsed.count() - << " seconds." << std::endl; - } - } - -private: - tp timer_; - bool activated_; -}; - - - - -template -void disp_vect(std::vector v){ - for(uint i=0; i< v.size(); i++){ - std::cout << v[i] << " "; - } - std::cout << std::endl; -} - -template -void disp_vect(std::list v){ - while(!v.empty()){ - std::cout << v.front() << " "; - v.pop_front(); - } - std::cout << std::endl; -} - -template -void disp_vect(std::vector > v){ - for(unsigned int i=0; i< v.size(); i++){ - std::cout << "(" << v[i].first << " " << v[i].second <<") "; - } -} - -template -void disp_vect(std::vector> v, bool show_small = true){ - for(uint i = 0; i < v.size(); i++){ - if(v[i].size() <= 1 && !show_small) continue; - std::cout << "("; - for (uint j = 0; j < v[i].size(); j++){ - std::cout << v[i][j]; - if(j < v[i].size() - 1) std::cout << " "; - } - std::cout << ") "; - } - std::cout << std::endl; -} - - -} //namespace Debug -namespace std{ - template - std::ostream& operator<<(std::ostream& stream, const std::vector truc){ - stream << "["; - for(unsigned int i = 0; i < truc.size()-1; i++){ - stream << truc[i] << ", "; - } - if(!truc.empty()) stream << truc.back(); - stream << "]"; - return stream; - } -} - - - - - - -#endif // DEBUG_H_INCLUDED - - diff --git a/src/python/include/utils/utilities.h b/src/python/include/utils/utilities.h deleted file mode 100644 index b754f01787..0000000000 --- a/src/python/include/utils/utilities.h +++ /dev/null @@ -1,262 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): David Loiseaux, Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef UTILITIES_H -#define UTILITIES_H - -#include -#include -#include -#include - - -namespace utils { - -constexpr bool verbose = false; - -using index = unsigned int; -using value_type = double; -using filtration_type = std::vector; -using multifiltration_type = std::vector; -using dimension_type = int; -using persistence_pair = std::pair; -using boundary_type = std::vector; -using boundary_matrix = std::vector; -using permutation_type = std::vector; -using point_type = std::vector; -using corner_type = std::vector; -using corners_type = std::pair>, std::vector>>; -using bar = std::pair; -using multipers_barcode = std::vector>; -const value_type inf = std::numeric_limits::infinity(); -const value_type negInf = -1 * inf; -using interval_type = std::pair; - -template -bool is_smaller(const std::vector& x, const std::vector& y) -{ - for (unsigned int i = 0; i < std::min(x.size(), y.size()); i++) - if (x[i] > y[i]) return false; - return true; -} -template -bool is_greater(const std::vector& x, const std::vector& y) -{ - for (unsigned int i = 0; i < std::min(x.size(), y.size()); i++) - if (x[i] < y[i]) return false; - return true; -} - - - - -struct Bar{ - Bar() : dim(-1), birth(-1), death(-1) - {} - - Bar(dimension_type dim, int birth, int death) - : dim(dim), birth(birth), death(death) - {} - - dimension_type dim; - int birth; - int death; -}; - -using barcode_type = std::vector; - -struct Diagram_point{ - Diagram_point() : dim(-1), birth(-1), death(-1) - {} - - Diagram_point(dimension_type dim, - value_type birth, - value_type death) - : dim(dim), birth(birth), death(death) - {} - - dimension_type dim; - value_type birth; - value_type death; -}; -using diagram_type = std::vector; - -struct MultiDiagram_point{ -public: - MultiDiagram_point() : dim(-1), birth({}), death({}) - {} - - MultiDiagram_point(dimension_type dim, - corner_type birth, - corner_type death) - : dim(dim), birth(birth), death(death) - {} - dimension_type get_dimension() const {return dim;} - corner_type get_birth() const {return birth;} - corner_type get_death() const {return death;} -private: - dimension_type dim; - corner_type birth; - corner_type death; - -}; -struct MultiDiagram{ // for python interface -public: - using iterator = std::vector::const_iterator; - MultiDiagram(){} - MultiDiagram(std::vector& m) : multiDiagram(m) - {} - std::vector get_points(const dimension_type dimension = -1) const{ // dump for python interface - std::vector out; - out.reserve(multiDiagram.size()); - for (const MultiDiagram_point &pt : multiDiagram){ - if (dimension == -1 || pt.get_dimension() == dimension){ - if (pt.get_birth().size() > 0 && pt.get_death().size() > 0 && pt.get_birth()[0] != utils::inf ) - out.push_back({pt.get_birth(), pt.get_death()}); - } - } - out.shrink_to_fit(); - return out; - } - std::vector> to_multipers(const dimension_type dimension = -1) const{ // dump for python interface - std::vector> out; - out.reserve(multiDiagram.size()); - for (const MultiDiagram_point &pt : multiDiagram){ - if (pt.get_dimension() == dimension){ - const auto &b = pt.get_birth(); - const auto &d = pt.get_death(); - out.push_back({b[0], d[0],b[1], d[1]}); - } - } - out.shrink_to_fit(); - return out; - } - iterator begin() const {return this->multiDiagram.begin();} - iterator end() const {return this->multiDiagram.end();} - unsigned int size() {return this->multiDiagram.size();} - void set(std::vector& m) {this-> multiDiagram.swap(m);} - std::vector& getref() {return this->multiDiagram;} - MultiDiagram_point& operator[](unsigned int i) {return this->multiDiagram[i];} - MultiDiagram_point& at(const unsigned int i){ - return multiDiagram[i]; - } -private: - std::vector multiDiagram; - -}; -struct MultiDiagrams{ -public: - using iterator = std::vector::const_iterator; - using nciterator = std::vector::iterator; - MultiDiagrams() {} - MultiDiagrams(unsigned int size) : multiDiagrams(size) {} - std::vector>> to_multipers(){ - unsigned int nsummands = this->multiDiagrams.front().size(); - unsigned int nlines = this->multiDiagrams.size(); - // std::vector>> out(nsummands, std::vector>(nlines, std::vector(5))); - std::vector>> out(nsummands); - for (unsigned int i = 0; i < nsummands; i++){ - out[i].reserve(nlines); - for(unsigned int j = 0; j < nlines; j++){ - const MultiDiagram_point &pt = this->multiDiagrams[j][i]; - if(_is_inf(pt.get_birth()) || _is_negInf(pt.get_death())) - out[i].push_back({0, 0, 0, 0,static_cast(j)}); - else - out[i].push_back({pt.get_birth()[0], pt.get_death()[0], pt.get_birth()[1], pt.get_death()[1],static_cast(j)}); - } - out[i].shrink_to_fit(); - } - return out; - } - using __for_python_plot_type = std::pair>,std::vector>; - __for_python_plot_type _for_python_plot(dimension_type dimension=-1, double min_persistence=0){ - __for_python_plot_type out; - auto& bars = out.first; - auto& summand_idx= out.second; - bars.reserve(this->multiDiagrams.size() * this->multiDiagrams[0].size()*2); - summand_idx.reserve(this->multiDiagrams.size() * this->multiDiagrams[0].size()); - for (const MultiDiagram& multiDiagram : this->multiDiagrams){ - unsigned int count = 0; - for (const MultiDiagram_point& bar : multiDiagram){ - const auto& birth = bar.get_birth(); - const auto& death = bar.get_death(); - if ( (dimension == -1 || bar.get_dimension() == dimension) && (!_is_inf(birth) && (death[0] > birth[0] + min_persistence)) ) { - bars.push_back({birth[0], death[0]}); - bars.push_back({birth[1], death[1]}); - summand_idx.push_back(count); - } - count++; - } - } - return out; - } - MultiDiagram& operator[](const unsigned int i){ - return multiDiagrams[i]; - } - MultiDiagram& at(const unsigned int i){ - return multiDiagrams[i]; - } - iterator begin() const {return this->multiDiagrams.begin();} // cython bug : iterators like bc in bcs crash) - iterator end() const {return this->multiDiagrams.end();} - - using barcodes = std::vector>; - barcodes get_points(){ - unsigned int nsummands = this->multiDiagrams.front().size(); - unsigned int nlines = this->multiDiagrams.size(); - // std::vector>> out(nsummands, std::vector>(nlines, std::vector(5))); - barcodes out(nlines, std::vector(nsummands)); - for (unsigned int i = 0; i < nlines; i++){ - for(unsigned int j = 0; j < nsummands; j++){ - const MultiDiagram_point &pt = this->multiDiagrams[i][j]; - out[i][j] = {pt.get_birth(), pt.get_death()}; - } - } - return out; - } - - unsigned int size() const {return this->multiDiagrams.size();} -private: - std::vector multiDiagrams; - inline bool _is_inf(const std::vector &truc) const{ - for (const auto coord : truc) - if (coord != inf) return false; - return true; - } - inline bool _is_negInf(const std::vector &truc) const{ - for (const auto coord : truc) - if (coord != negInf) return false; - return true; - } -}; -} //namespace utils - - - - -namespace std{ - template - void sort(vector &to_sort, bool(*compare)(const T&, const T&)){ - std::sort(to_sort.begin(), to_sort.end(), compare); - } - template - std::vector& operator-=(std::vector &result, const std::vector &to_substract){ - std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); - return result; - } - template - std::vector& operator+=(std::vector &result, const std::vector &to_add){ - std::transform(result.begin(), result.end(), to_add.begin(),result.begin(), std::plus()); - return result; - } - -} // namespace std - - -#endif // UTILITIES_H From 3ab6d2cb55cbcf784b1ea876996cb94ed696c720 Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Fri, 17 Feb 2023 15:37:26 +0100 Subject: [PATCH 06/18] Better filtration_domination citation --- src/python/gudhi/simplex_tree_multi.pyx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index d836fe762a..688071f555 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -651,7 +651,8 @@ cdef class SimplexTreeMulti: # return self.get_ptr().get_edge_list() def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False): - """(Strong) collapse of 1 critical clique complex, compatible with 2-parameter filtration. + """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574). + It uses the code from the github repository https://github.com/aj-alonso/filtration_domination . Parameters ---------- @@ -660,9 +661,9 @@ cdef class SimplexTreeMulti: num:int The number of collapses to do. strong:bool - Whether to use strong collapses or collapses (slower, but may remove more edges) + Whether to use strong collapses or standard collapses (slower, but may remove more edges) full:bool - Collapses the maximum number of edges if true, i.e., will do at most 100 strong collapses and 100 non-strong collapses afterward. + Collapses the maximum number of edges if true, i.e., will do (at most) 100 strong collapses and (at most) 100 non-strong collapses afterward. progress:bool If true, shows the progress of the number of collapses. @@ -672,8 +673,8 @@ cdef class SimplexTreeMulti: - This is for 1 critical simplices, with 2 parameter persistence. Returns ------- - self:SimplexTree - A simplex tree that has the same homology over this bifiltration. + self:SimplexTreeMulti + A (smaller) simplex tree that has the same homology over this bifiltration. """ from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated From a603ef766f8a9b5d2b78b7cd02255b022db52161 Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Fri, 10 Mar 2023 12:10:17 +0100 Subject: [PATCH 07/18] cleaner Filtration_type class --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 2 +- src/python/gudhi/simplex_tree_multi.pyx | 8 +- .../include/Simplex_tree_interface_multi.h | 18 +-- src/python/include/Simplex_tree_multi.h | 15 +- .../finitely_critical_filtrations.h | 65 --------- .../box.h | 42 +++--- .../finitely_critical_filtrations.h | 129 ++++++++++++++++++ .../line.h | 24 ++-- 8 files changed, 186 insertions(+), 117 deletions(-) delete mode 100644 src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h rename src/python/include/{finitely_critical_filtrations => multi_filtrations}/box.h (71%) create mode 100644 src/python/include/multi_filtrations/finitely_critical_filtrations.h rename src/python/include/{finitely_critical_filtrations => multi_filtrations}/line.h (77%) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 3ac77516ff..ecd814fa71 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -133,7 +133,7 @@ class Simplex_tree { Key_simplex_base; struct Filtration_simplex_base_real { - Filtration_simplex_base_real() : filt_(0) {} + Filtration_simplex_base_real() : filt_{} {} void assign_filtration(Filtration_value f) { filt_ = f; } Filtration_value filtration() const { return filt_; } private: diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index 688071f555..ded88d0ba3 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -32,9 +32,9 @@ ctypedef fused some_float: from typing import Any -cimport numpy as cnp +# cimport numpy as cnp import numpy as np -cnp.import_array() +# cnp.import_array() cimport gudhi.simplex_tree_multi cimport cython @@ -926,7 +926,7 @@ cdef class SimplexTreeMulti: cdef bool c_coordinate_values = coordinate_values with nogil: squeeze_filtration(ptr, c_filtration_grid, c_coordinate_values) - return filtration_grid + return self def filtration_bounds(self): """ @@ -1027,7 +1027,7 @@ cdef class SimplexTreeMulti: # assert len(points.shape) in [1,2] # if len(points.shape) == 1: # points = [points] - cdef cnp.ndarray[double, ndim=2] c_points = np.asarray(points) + c_points = np.asarray(points) assert c_points.shape[1] == self.num_parameters return np.asarray(self.get_ptr().euler_char(c_points)) diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index d7def96b18..0ae7d0af2c 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -19,7 +19,7 @@ #include #include "Simplex_tree_multi.h" // #include -#include "finitely_critical_filtrations/finitely_critical_filtrations.h" +#include "multi_filtrations/finitely_critical_filtrations.h" #include #include @@ -32,13 +32,14 @@ namespace Gudhi { template class Simplex_tree_interface : public Simplex_tree { public: + using Python_filtration_type = std::vector; // TODO : std::conditional using Base = Simplex_tree; using Filtration_value = typename Base::Filtration_value; using Vertex_handle = typename Base::Vertex_handle; using Simplex_handle = typename Base::Simplex_handle; using Insertion_result = typename std::pair; using Simplex = std::vector; - using Simplex_and_filtration = std::pair; + using Simplex_and_filtration = std::pair; using Filtered_simplices = std::vector; using Skeleton_simplex_iterator = typename Base::Skeleton_simplex_iterator; using Complex_simplex_iterator = typename Base::Complex_simplex_iterator; @@ -47,7 +48,6 @@ class Simplex_tree_interface : public Simplex_tree { typedef bool (*blocker_func_t)(Simplex simplex, void *user_data); using euler_chars_type = std::vector; - public: Extended_filtration_data efd; @@ -92,7 +92,7 @@ class Simplex_tree_interface : public Simplex_tree { return (result.second); } - Filtration_value simplex_filtration(const Simplex& simplex) { + Python_filtration_type simplex_filtration(const Simplex& simplex) { return Base::filtration(Base::find(simplex)); } @@ -275,11 +275,12 @@ class Simplex_tree_interface : public Simplex_tree { return simplex_list; } - euler_chars_type euler_char(const std::vector> &point_list){ // TODO multi-critical + euler_chars_type euler_char(std::vector> &point_list){ // TODO multi-critical const int npts = point_list.size(); if (npts == 0){ return {}; } + using Gudhi::multi_filtrations::Finitely_critical_multi_filtration; euler_chars_type out(point_list.size(), 0.); @@ -294,9 +295,10 @@ class Simplex_tree_interface : public Simplex_tree { auto &euler_char_at_point = out[i]; // #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR for(const auto &SimplexHandle : Base::complex_simplex_range()){ - const Finitely_critical_multi_filtration &pt = *(Finitely_critical_multi_filtration*)(&point_list[i]); - const std::vector& filtration_ = Base::filtration(SimplexHandle); - const Finitely_critical_multi_filtration &filtration = *(Finitely_critical_multi_filtration*)(&filtration_); + // const Finitely_critical_multi_filtration &pt = *(Finitely_critical_multi_filtration*)(&point_list[i]); + options_multi::Filtration_value filtration = Base::filtration(SimplexHandle); + // const Finitely_critical_multi_filtration &filtration = *(Finitely_critical_multi_filtration*)(&filtration_); + Finitely_critical_multi_filtration pt(point_list[i]); // if (is_greater(pt, filtration)){ if (filtration <= pt){ int sign = Base::dimension(SimplexHandle) %2 ? -1 : 1; diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index 8fe981d60e..a0bae8a374 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -14,8 +14,8 @@ #include #include -#include "finitely_critical_filtrations/finitely_critical_filtrations.h" -#include "finitely_critical_filtrations/line.h" +#include "multi_filtrations/finitely_critical_filtrations.h" +#include "multi_filtrations/line.h" @@ -33,25 +33,28 @@ struct Simplex_tree_options_multidimensional_filtration { typedef linear_indexing_tag Indexing_tag; typedef int Vertex_handle; typedef double value_type; - using Filtration_value = std::vector; // Cannot put Finitely_critical_multi_filtration, cython doesn't know how to convert inheritence + using Filtration_value = Gudhi::multi_filtrations::Finitely_critical_multi_filtration; // Cannot put Finitely_critical_multi_filtration, cython doesn't know how to convert inheritence typedef std::uint32_t Simplex_key; static const bool store_key = true; static const bool store_filtration = true; static const bool contiguous_vertices = false; }; + + using options_multi = Simplex_tree_options_multidimensional_filtration; using options_std = Simplex_tree_options_full_featured; using multi_filtration_type = std::vector; using multi_filtration_grid = std::vector; + void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension){ Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(newsplxptr); if (dimension <= 0) {std::cerr << "Empty filtration\n"; throw ;} - Finitely_critical_multi_filtration f(dimension); + options_multi::Filtration_value f(dimension); for (auto &simplex_handle : st.complex_simplex_range()){ std::vector simplex; for (auto vertex : st.simplex_vertex_range(simplex_handle)) @@ -76,7 +79,7 @@ template void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - Gudhi::Line l(basepoint); + Gudhi::multi_filtrations::Line l(basepoint); for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) @@ -87,8 +90,6 @@ void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std options_multi::value_type new_filtration = l.push_forward(f)[dimension]; st.insert_simplex(simplex,new_filtration); } - - } diff --git a/src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h b/src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h deleted file mode 100644 index 8c677db8cc..0000000000 --- a/src/python/include/finitely_critical_filtrations/finitely_critical_filtrations.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - - -namespace Gudhi{ - -template -class Finitely_critical_multi_filtration : public std::vector { - // Class to prevent doing illegal stuff with the standard library, e.g., compare two vectors -public: - // using std::vector :: vector; // inherit constructor - explicit Finitely_critical_multi_filtration(std::vector * v) : std::vector(v) {}; // I'm not sure if it does a copy ? - explicit Finitely_critical_multi_filtration(int n) : std::vector(n) {}; - explicit Finitely_critical_multi_filtration(int n, T value) : std::vector(n,value) {}; - - //TODO : multicritical -> iterator over filtrations -private: - -}; -template -bool operator<(const Finitely_critical_multi_filtration& v1, const Finitely_critical_multi_filtration& v2) -{ - bool isSame = true; - if (v1.size() != v2.size()){ - std::cerr << "Filtrations are not of the same size ! (" << v1.size() << " " << v2.size() << ")."; - throw; - } - int n = v1.size(); - for (int i = 0; i < n; ++i){ - if (v1[i] > v2[i]) return false; - if (isSame && v1[i] != v2[i]) isSame = false; - } - if (isSame) return false; - return true; -} - - - -template -bool operator<=(const Finitely_critical_multi_filtration& v1, const Finitely_critical_multi_filtration& v2) -{ - if (v1.size() != v2.size()){ - std::cerr << "Filtrations are not of the same size ! (" << v1.size() << " " << v2.size() << ")."; - throw; - } - int n = v1.size(); - for (int i = 0; i < n; ++i){ - if (v1[i] > v2[i]) return false; - } - return true; -} - -template -Finitely_critical_multi_filtration& operator-=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_substract){ - std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); - return result; -} - - -template -Finitely_critical_multi_filtration& operator+=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_add){ - std::transform(result.begin(), result.end(), to_add.begin(),result.begin(), std::plus()); - return result; -} - -} // namespace Gudhi diff --git a/src/python/include/finitely_critical_filtrations/box.h b/src/python/include/multi_filtrations/box.h similarity index 71% rename from src/python/include/finitely_critical_filtrations/box.h rename to src/python/include/multi_filtrations/box.h index cf202c660f..22f127b26d 100644 --- a/src/python/include/finitely_critical_filtrations/box.h +++ b/src/python/include/multi_filtrations/box.h @@ -22,7 +22,7 @@ #include #include - +#include "finitely_critical_filtrations.h" @@ -31,13 +31,13 @@ * @brief Holds the square box on which to compute. */ -namespace Gudhi{ +namespace Gudhi::multi_filtrations{ template class Box { - using point_type = std::vector; + using point_type = Finitely_critical_multi_filtration; public: Box(); Box(const point_type& bottomCorner, const point_type& upperCorner); @@ -47,7 +47,7 @@ class Box const point_type& get_bottom_corner() const; const point_type& get_upper_corner() const; bool contains(const point_type& point) const; - void infer_from_filters(const std::vector> &Filters_list); + void infer_from_filters(const std::vector &Filters_list); bool is_trivial() const ; private: @@ -65,8 +65,9 @@ inline Box::Box(const point_type &bottomCorner, const point_type &upperCorner upperCorner_(upperCorner) { assert(bottomCorner.size() == upperCorner.size() - && is_smaller(bottomCorner, upperCorner) - && "This box is trivial !"); + // && is_smaller(bottomCorner, upperCorner) + && bottomCorner <= upperCorner + && "This box is trivial !"); } template @@ -80,14 +81,16 @@ template inline void Box::inflate(T delta) { // #pragma omp simd - for (int i = 0; i < bottomCorner_.size(); i++){ - bottomCorner_[i] -= delta; - upperCorner_[i] += delta; - } + // for (int i = 0; i < bottomCorner_.size(); i++){ + // bottomCorner_[i] -= delta; + // upperCorner_[i] += delta; + // } + bottomCorner_ -= delta; + upperCorner_ += delta; } template -inline void Box::infer_from_filters(const std::vector> &Filters_list){ +inline void Box::infer_from_filters(const std::vector &Filters_list){ int dimension = Filters_list.size(); int nsplx = Filters_list[0].size(); std::vector lower(dimension); @@ -111,28 +114,29 @@ inline bool Box::is_trivial() const { } template -inline const std::vector &Box::get_bottom_corner() const +inline const typename Box::point_type &Box::get_bottom_corner() const { return bottomCorner_; } template -inline const std::vector &Box::get_upper_corner() const +inline const typename Box::point_type &Box::get_upper_corner() const { return upperCorner_; } template -inline bool Box::contains(const std::vector &point) const +inline bool Box::contains(const point_type &point) const { if (point.size() != bottomCorner_.size()) return false; - for (int i = 0; i < (int)point.size(); i++){ - if (point[i] < bottomCorner_[i]) return false; - if (point[i] > upperCorner_[i]) return false; - } + // for (int i = 0; i < (int)point.size(); i++){ + // if (point[i] < bottomCorner_[i]) return false; + // if (point[i] > upperCorner_[i]) return false; + // } - return true; + // return true; + return bottomCorner_ <= point && point <= upperCorner_; } template diff --git a/src/python/include/multi_filtrations/finitely_critical_filtrations.h b/src/python/include/multi_filtrations/finitely_critical_filtrations.h new file mode 100644 index 0000000000..9e7fb3be3b --- /dev/null +++ b/src/python/include/multi_filtrations/finitely_critical_filtrations.h @@ -0,0 +1,129 @@ +#pragma once +#include +#include + +namespace Gudhi::multi_filtrations{ + +template +class Finitely_critical_multi_filtration : public std::vector { + // Class to prevent doing illegal stuff with the standard library, e.g., compare two vectors +public: + // explicit Finitely_critical_multi_filtration(std::vector& v) : ptr_(&v) { + // }; // Conversion + Finitely_critical_multi_filtration() : std::vector() {}; + Finitely_critical_multi_filtration(int n) : std::vector(n) {}; + Finitely_critical_multi_filtration(int n, T value) : std::vector(n,value) {}; + Finitely_critical_multi_filtration(std::initializer_list init) : std::vector(init) {}; + Finitely_critical_multi_filtration(const std::vector& v) : std::vector(v) {}; + + operator std::vector&() const { + return *this; + } + std::vector get_vector() const{ + return static_cast>(*this); + } + + //TODO : multicritical -> iterator over filtrations + + // LESS THAN OPERATORS + friend bool operator<(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) + { + bool isSame = true; + // if (a.size() != b.size()){ + + + // if (a.size()>1 && b.size() >1){ + // std::cerr << "Filtrations are not of the same size ! (" << a.size() << " " << b.size() << ")."; + // throw; + // } + // // {inf, inf, ...} can be stored as {inf} + // if (b[0] == std::numeric_limits::infinity()) //TODO FIXME, we have to check every coord of b instead of 1 + // return a[0] != std::numeric_limits::infinity(); + // return false; + // } + int n = std::min(a.size(), b.size()); + for (int i = 0; i < n; ++i){ + if (a[i] > b[i]) return false; + if (isSame && a[i] != b[i]) isSame = false; + } + if (isSame) return false; + return true; + } + friend bool operator<=(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) + { + // if (a.size() != b.size()){ + // if (a.size()>1 && b.size() >1){ + // std::cerr << "Filtrations are not of the same size ! (" << a.size() << " " << b.size() << ")."; + // throw; + // } + // // {inf, inf, ...} can be stored as {inf} + // if (b[0] == std::numeric_limits::infinity()) //TODO FIXME, we have to check every coord of b instead of 1 + // return a[0] != std::numeric_limits::infinity(); + // return false; + // } + int n = std::min(a.size(), b.size()); + for (int i = 0; i < n; ++i){ + if (a[i] > b[i]) return false; + } + return true; + } + + + + //GREATER THAN OPERATORS + friend bool operator>(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) + { + return b=(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) + { + return b<=a; // C'est honteux. + } + + Finitely_critical_multi_filtration& operator=(const Finitely_critical_multi_filtration& a){ + std::vector::operator=(a); + return *this; + } + + std::vector& _convert_back(){ + return *this; + } + + + + friend Finitely_critical_multi_filtration& operator-=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_substract){ + std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); + return result; + } + friend Finitely_critical_multi_filtration& operator+=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_add){ + std::transform(result.begin(), result.end(), to_add.begin(),result.begin(), std::plus()); + return result; + } + + friend Finitely_critical_multi_filtration& operator-=(Finitely_critical_multi_filtration &result, const T &to_substract){ + // std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); + for (auto & truc : result){ + truc -= to_substract; + } + return result; + } + friend Finitely_critical_multi_filtration& operator+=(Finitely_critical_multi_filtration &result, const T &to_add){ + for (auto & truc : result){ + truc += to_add; + } + return result; + } + + static std::vector> to_python(const std::vector>& to_convert){ + + return std::vector>(to_convert.begin(), to_convert.end()); + } + + static std::vector> from_python(const std::vector>& to_convert){ + return std::vector>(to_convert.begin(), to_convert.end());; + } + +}; + + +} // namespace Gudhi diff --git a/src/python/include/finitely_critical_filtrations/line.h b/src/python/include/multi_filtrations/line.h similarity index 77% rename from src/python/include/finitely_critical_filtrations/line.h rename to src/python/include/multi_filtrations/line.h index c975d8a4aa..e4a939036c 100644 --- a/src/python/include/finitely_critical_filtrations/line.h +++ b/src/python/include/multi_filtrations/line.h @@ -19,13 +19,15 @@ #include "box.h" #include "finitely_critical_filtrations.h" -namespace Gudhi{ +namespace Gudhi::multi_filtrations{ + template class Line { - using point_type = std::vector; + public: + using point_type = Finitely_critical_multi_filtration; Line(); Line(point_type x); Line(point_type x, point_type v); @@ -54,29 +56,27 @@ namespace Gudhi{ this->direction_.swap(v); } template - std::vector Line::push_forward(std::vector x) const{ //TODO remove copy - Finitely_critical_multi_filtration& y = *(Finitely_critical_multi_filtration*)(&x); - const Finitely_critical_multi_filtration& basepoint = *(Finitely_critical_multi_filtration*)(&basepoint_); - y -= basepoint; + typename Line::point_type Line::push_forward(point_type x) const { //TODO remove copy + x -= basepoint_; T t = - std::numeric_limits::infinity();; - for (unsigned int i = 0; idirection_.size() > i ? direction_[i] : 1; t = std::max(t, x[i]/dir); } - std::vector out(basepoint_.size()); + point_type out(basepoint_.size()); for (unsigned int i = 0; i < out.size(); i++) out[i] = basepoint_[i] + t * (this->direction_.size() > i ? direction_[i] : 1) ; return out; } template - std::vector Line::push_back(std::vector x) const{ + typename Line::point_type Line::push_back(point_type x) const{ x -= basepoint_; T t = std::numeric_limits::infinity(); for (unsigned int i = 0; idirection_.size() > i ? direction_[i] : 1; t = std::min(t, x[i]/dir); } - std::vector out(basepoint_.size()); + point_type out(basepoint_.size()); for (unsigned int i = 0; i < out.size(); i++) out[i] = basepoint_[i] + t * (this->direction_.size() > i ? direction_[i] : 1) ; return out; @@ -86,11 +86,9 @@ namespace Gudhi{ return basepoint_.size(); } template - std::pair, std::vector> Line::get_bounds(const Box &box) const{ + std::pair::point_type, typename Line::point_type> Line::get_bounds(const Box &box) const{ return {this->push_forward(box.get_bottom_corner()), this->push_back(box.get_upper_corner())}; } - - } #endif // LINE_FILTRATION_TRANSLATION_H_INCLUDED From a7d1609faa80ba48937e6c81b31e5ff7ff8d74d6 Mon Sep 17 00:00:00 2001 From: DavidLapous Date: Sun, 26 Mar 2023 17:13:37 +0200 Subject: [PATCH 08/18] fixed make_filtration_non_decreasing, disabled simplex insert filtration overwrite (mutlicritical filtrations are not well supported yet), optimizations --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 44 ++++- src/python/gudhi/simplex_tree_multi.pxd | 23 ++- src/python/gudhi/simplex_tree_multi.pyx | 187 ++++++++++-------- .../include/Simplex_tree_interface_multi.h | 12 +- src/python/include/Simplex_tree_multi.h | 72 ++++--- src/python/include/multi_filtrations/box.h | 15 ++ .../finitely_critical_filtrations.h | 21 +- 7 files changed, 254 insertions(+), 120 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 0883c52101..6d524d2677 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -38,7 +38,6 @@ #include #include // for greater<> #include -#include // Inf #include #include // for std::max #include // for std::uint32_t @@ -816,7 +815,7 @@ class Simplex_tree { */ template> std::pair insert_simplex_and_subfaces(const InputVertexRange& Nsimplex, - Filtration_value filtration = 0) { + Filtration_value filtration = {}) { auto first = std::begin(Nsimplex); auto last = std::end(Nsimplex); @@ -856,8 +855,28 @@ class Simplex_tree { Simplex_handle simplex_one = insertion_result.first; bool one_is_new = insertion_result.second; if (!one_is_new) { - if (filtration(simplex_one) > filt) { - assign_filtration(simplex_one, filt); + if (!(filtration(simplex_one) <= filt)) { // TODO : For multipersistence, it's not clear what should be the default, especially for multicritical filtrations + if constexpr (SimplexTreeOptions::is_multi_parameter){ + if (filt < filtration(simplex_one)){ + // assign_filtration(simplex_one, filt); + // I don't really like this behavior. + // It prevents inserting simplices by default at the smallest possible position after its childrens. + // e.g., if (python) we type : st.insert([0], [1,0]), st.insert([1], [0,1]), st.insert([0,1]) + // we may want st.filtration([0,1]) to be [1,1]. (maybe after a make_filtration_decreasing from user) + // Furthermore, this may more sense as default value -> 0 can erase filtration values of childrens... + } + else{ // As multicritical filtrations are not well supported yet, its not safe to concatenate yet. + // two incomparables filtrations values -> we concatenate + // std::cout << "incomparable -> concatenate" << std::endl; + // Filtration_value new_filtration = filtration(simplex_one); + // new_filtration.insert_new(filt); // we assume then that Filtration_value has a insert_new method. + // assign_filtration(simplex_one, new_filtration); + } + } + else{ + assign_filtration(simplex_one, filt); + } + } else { // FIXME: this interface makes no sense, and it doesn't seem to be tested. insertion_result.first = null_simplex(); @@ -1432,12 +1451,22 @@ class Simplex_tree { for (auto& simplex : boost::adaptors::reverse(sib->members())) { // Find the maximum filtration value in the border Boundary_simplex_range boundary = boundary_simplex_range(&simplex); - Boundary_simplex_iterator max_border = std::max_element(std::begin(boundary), std::end(boundary), + typename SimplexTreeOptions::Filtration_value max_filt_border_value {}; + if constexpr (SimplexTreeOptions::is_multi_parameter){ + // in that case, we assume that Filtration_value has a `push_to` member to handle this. + max_filt_border_value = typename SimplexTreeOptions::Filtration_value(this->number_of_parameters_); + for (auto &sh : boundary){ + max_filt_border_value.push_to(filtration(sh)); // pushes the value of max_filt_border_value to reach simplex' filtration + } + } + else{ + Boundary_simplex_iterator max_border = std::max_element(std::begin(boundary), std::end(boundary), [](Simplex_handle sh1, Simplex_handle sh2) { return filtration(sh1) < filtration(sh2); }); - - Filtration_value max_filt_border_value = filtration(*max_border); + max_filt_border_value = filtration(*max_border); + } + // Replacing if(f=max)) would mean that if f is NaN, we replace it with the max of the children. // That seems more useful than keeping NaN. if (!(simplex.second.filtration() >= max_filt_border_value)) { @@ -1965,6 +1994,7 @@ struct Simplex_tree_options_full_featured { static const bool store_key = true; static const bool store_filtration = true; static const bool contiguous_vertices = false; + static const bool is_multi_parameter = false; }; /** Model of SimplexTreeOptions, faster than `Simplex_tree_options_full_featured` but note the unsafe diff --git a/src/python/gudhi/simplex_tree_multi.pxd b/src/python/gudhi/simplex_tree_multi.pxd index 5fece5764e..13f72df131 100644 --- a/src/python/gudhi/simplex_tree_multi.pxd +++ b/src/python/gudhi/simplex_tree_multi.pxd @@ -19,14 +19,25 @@ __copyright__ = "Copyright (C) 2016 Inria" __license__ = "MIT" ctypedef int dimension_type -ctypedef vector[double] point_type -ctypedef double filtration_value_type -ctypedef vector[double] filtration_type +ctypedef float value_type +ctypedef vector[value_type] filtration_type ctypedef vector[int] simplex_type ctypedef vector[simplex_type] simplex_list ctypedef vector[pair[pair[int,int], pair[double, double]]] edge_list ctypedef vector[int] euler_char_list + + + +cdef extern from "multi_filtrations/finitely_critical_filtrations.h" namespace "Gudhi::multi_filtrations": + cdef cppclass Finitely_critical_multi_filtration "Gudhi::multi_filtrations::Finitely_critical_multi_filtration": + Finitely_critical_multi_filtration() nogil except + + Finitely_critical_multi_filtration(vector[value_type]) except + + Finitely_critical_multi_filtration& operator=(const Finitely_critical_multi_filtration&) + filtration_type& get_vector() nogil + int size() nogil + + cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": cdef cppclass Simplex_tree_options_multidimensional_filtration: pass @@ -71,7 +82,7 @@ cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": void expansion(int max_dim) nogil except + void remove_maximal_simplex(simplex_type simplex) nogil # bool prune_above_filtration(filtration_type filtration) nogil - # bool make_filtration_non_decreasing() nogil + bool make_filtration_non_decreasing() nogil except + # void compute_extended_filtration() nogil Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) nogil except + void reset_filtration(filtration_type filtration, int dimension) nogil @@ -93,10 +104,12 @@ cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": void set_keys_to_enumerate() nogil int get_key(const simplex_type) nogil void set_key(simplex_type, int) nogil - void fill_lowerstar(vector[double], int) nogil + void fill_lowerstar(vector[value_type], int) nogil simplex_list get_simplices_of_dimension(int) nogil edge_list get_edge_list() nogil euler_char_list euler_char(vector[filtration_type]) nogil void resize_all_filtrations(int) nogil void set_number_of_parameters(int) nogil int get_number_of_parameters() nogil + + diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index ded88d0ba3..f85edd3f4c 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -32,27 +32,27 @@ ctypedef fused some_float: from typing import Any -# cimport numpy as cnp +cimport numpy as cnp import numpy as np -# cnp.import_array() +cnp.import_array() -cimport gudhi.simplex_tree_multi +from gudhi.simplex_tree_multi cimport * cimport cython from gudhi import SimplexTree ## Small hack for typing +from typing import Iterable from warnings import warn -ctypedef double value_type cdef extern from "Simplex_tree_multi.h" namespace "Gudhi": void multify(const uintptr_t, const uintptr_t, const unsigned int) nogil except + void flatten(const uintptr_t, const uintptr_t, const unsigned int) nogil void flatten_diag(const uintptr_t, const uintptr_t, const vector[value_type], int) nogil void squeeze_filtration(uintptr_t, const vector[vector[value_type]]&, bool) nogil except + - vector[vector[value_type]] get_filtration_values(uintptr_t) nogil + vector[vector[vector[value_type]]] get_filtration_values(uintptr_t, const vector[int]&) nogil cdef bool callback(vector[int] simplex, void *blocker_func): @@ -143,7 +143,7 @@ cdef class SimplexTreeMulti: def __deepcopy__(self): return self.copy() - def filtration(self, simplex:list[int]|np.ndarray)->filtration_type: + def filtration(self, simplex:list|np.ndarray)->filtration_type: """This function returns the filtration value for a given N-simplex in this simplicial complex, or +infinity if it is not in the complex. @@ -155,7 +155,7 @@ cdef class SimplexTreeMulti: filtration_vector = np.array(self.get_ptr().simplex_filtration(simplex)) return filtration_vector.reshape((-1, self.num_parameters)) - def assign_filtration(self, simplex:list[int]|np.ndarray, filtration:list[float]|np.ndarray): + def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray): """This function assigns a new multi-critical filtration value to a given N-simplex. @@ -263,20 +263,18 @@ cdef class SimplexTreeMulti: otherwise (whatever its original filtration value). :rtype: bool """ - # TODO C++, to be compatible with insert_batch and multicritical filtrations num_parameters = self.get_ptr().get_number_of_parameters() - if filtration is None: - filtration = np.array([-np.inf]*num_parameters, dtype = np.float) - simplex_already_exists = not self.get_ptr().insert(simplex, filtration) - if simplex_already_exists: - old_filtrations = self.filtration(simplex) - for f in old_filtrations: - if np.all(f >= filtration) or np.all(f <= filtration): - return False - new_filtration = np.concatenate([*old_filtrations, filtration], axis = 0) - self.assign_filtration(simplex, new_filtration) - return True - return True + if filtration is None: filtration = np.array([-np.inf]*num_parameters, dtype = float) + # simplex_already_exists = not self.get_ptr().insert(simplex, filtration) + # if simplex_already_exists: + # old_filtrations = self.filtration(simplex) + # for f in old_filtrations: + # if np.all(f >= filtration) or np.all(f <= filtration): + # return False + # new_filtration = np.concatenate([*old_filtrations, filtration], axis = 0) + # self.assign_filtration(simplex, new_filtration) + # return True + return self.get_ptr().insert(simplex, filtration) @cython.boundscheck(False) @cython.wraparound(False) @@ -294,15 +292,15 @@ cdef class SimplexTreeMulti: :type filtrations: numpy.array of shape (n,num_parameters) """ # TODO : multi-critical - cdef vector[int] vertices = np.unique(vertex_array) + # cdef vector[int] vertices = np.unique(vertex_array) cdef Py_ssize_t k = vertex_array.shape[0] cdef Py_ssize_t n = vertex_array.shape[1] assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations' - assert filtrations.shape[1] == self.num_parameters + assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters" cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v - cdef vector[double] w + cdef vector[value_type] w cdef int n_parameters = self.num_parameters with nogil: # Without this, it we end up inserting vertic could be slow ifes in a bad order (flat_map). @@ -324,7 +322,13 @@ cdef class SimplexTreeMulti: v.clear() w.clear() return self - + # @staticmethod + # cdef pair[simplex_type,vector[double]] _pair_simplex_filtration_to_python(pair[simplex_type,Finitely_critical_multi_filtration]& truc): + # cdef pair[simplex_type,vector[double]] out + # with nogil: + # out.first.swap(truc.first) + # out.second.swap(truc.second.get_vector()) + # return out def get_simplices(self): """This function returns a generator with simplices and their given filtration values. @@ -335,9 +339,16 @@ cdef class SimplexTreeMulti: cdef Simplex_tree_multi_simplices_iterator it = self.get_ptr().get_simplices_iterator_begin() cdef Simplex_tree_multi_simplices_iterator end = self.get_ptr().get_simplices_iterator_end() cdef Simplex_tree_multi_simplex_handle sh = dereference(it) - + # cdef pair[simplex_type,Finitely_critical_multi_filtration] out_ + # while it != end: + # out_ = self.get_ptr().get_simplex_and_filtration(dereference(it)) + # out = (out_.first,out_.second.get_vector()) + # yield out + # preincrement(it) + cdef pair[simplex_type,filtration_type] out while it != end: yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out) preincrement(it) def get_filtration(self): @@ -494,12 +505,11 @@ cdef class SimplexTreeMulti: current_dim = self.dimension() with nogil: self.get_ptr().expansion(maxdim) - - # This is a fix for multipersistence. FIXME expansion in c++ - self.make_filtration_non_decreasing(start_dimension=current_dim+1) + # This is a fix for multipersistence. FIXME expansion in c++ + self.get_ptr().make_filtration_non_decreasing() return self - def make_filtration_non_decreasing(self, start_dimension:int=1)->SimplexTreeMulti: # FIXME TODO code in c++ + def make_filtration_non_decreasing(self)->bool: # FIXME TODO code in c++ """This function ensures that each simplex has a higher filtration value than its faces by increasing the filtration values. @@ -507,7 +517,13 @@ cdef class SimplexTreeMulti: False if the filtration was already non-decreasing. :rtype: bool """ - # return self.get_ptr().make_filtration_non_decreasing() + cdef bool out + with nogil: + out = self.get_ptr().make_filtration_non_decreasing() + return out + + ## DEPRECATED + def make_filtration_non_decreasing_old(self, start_dimension:int = 1): if start_dimension <= 0: start_dimension = 1 # cdef Simplex_tree_skeleton_iterator it @@ -645,10 +661,14 @@ cdef class SimplexTreeMulti: # """ # self.compute_persistence(homology_coeff_field, min_persistence, persistence_dim_max) # return self.pcohptr.get_persistence() - + + ## This function is only meant for the edge collapse interface. -# def get_edge_list(self): -# return self.get_ptr().get_edge_list() + def get_edge_list(self): + cdef edge_list out; + with nogil: + out = self.get_ptr().get_edge_list() + return out def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False): """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574). @@ -669,7 +689,7 @@ cdef class SimplexTreeMulti: WARNING ------- - - This will destroy all of the k-simplices, with k>=2. Be sure to use this with a clique complex, if you want to preserve the homology strictly above dimension 1. + - This will destroy all of the k-simplices, with k>=2. Be sure to use this with a clique complex, if you want to preserve the homology >= dimension 1. - This is for 1 critical simplices, with 2 parameter persistence. Returns ------- @@ -687,27 +707,28 @@ cdef class SimplexTreeMulti: max_dimension = self.dimension() if max_dimension is None else max_dimension # edge_list = std::vector, std::pair>> # cdef vector[pair[pair[int,int],pair[value_type,value_type]]] - edges = self.get_ptr().get_edge_list() + edges = self.get_edge_list() # cdef int n = edges.size() n = len(edges) if full: num = 100 - for i in tqdm(range(num), total=num, desc="Removing edges", disable=not(progress)): - if strong: - edges = remove_strongly_filtration_dominated(edges) # nogil ? - else: - edges = remove_filtration_dominated(edges) - # Prevents doing useless collapses - if len(edges) >= n: - if full and strong: - strong = False + with tqdm(range(num), total=num, desc="Removing edges", disable=not(progress)) as I: + for i in I: + if strong: + edges = remove_strongly_filtration_dominated(edges) # nogil ? + else: + edges = remove_filtration_dominated(edges) + # Prevents doing useless collapses + if len(edges) >= n: + if full and strong: + strong = False + n = len(edges) + # n = edges.size() # len(edges) + else : + break + else: n = len(edges) - # n = edges.size() # len(edges) - else : - break - else: - n = len(edges) - # n = edges.size() + # n = edges.size() reduced_tree = SimplexTreeMulti(num_parameters=self.num_parameters) @@ -717,14 +738,9 @@ cdef class SimplexTreeMulti: reduced_tree.insert_batch(vertices, vertices_filtration) ## Adds edges again - edges_filtration = np.array([f for e,f in edges]) - edges = np.array([e for e, _ in edges], dtype=int).T + edges_filtration = np.asarray([f for e,f in edges], dtype=float) + edges = np.asarray([e for e, _ in edges], dtype=int).T reduced_tree.insert_batch(edges, edges_filtration) - -# for splx, f in self.get_skeleton(0): # Adds vertices back -# reduced_tree.insert(splx, f) -# for e, (f1, f2) in edges: # Adds reduced edges back # TODO : with insert_batch -# reduced_tree.insert(e, [f1,f2]) self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable) self.expansion(max_dimension) # Expands back the simplextree to the original dimension. # self.make_filtration_non_decreasing(2) @@ -836,7 +852,7 @@ cdef class SimplexTreeMulti: file.write(f"{num_parameters}\n") if not strip_comments: file.write("# Sizes of generating sets\n") ## WRITES TSR VARIABLES - tsr:list[int]= [0]*(self.dimension()+1) # dimension --- 0 + tsr:list= [0]*(self.dimension()+1) # dimension --- 0 for splx,f in self.get_simplices(): dim = len(splx)-1 tsr[dim] += (int)(len(f) // num_parameters) @@ -845,7 +861,7 @@ cdef class SimplexTreeMulti: ## Adds the boundaries to the dictionnary + tsr dict_splx_to_firep_number = {} - tsr:list[list[int]] = [[] for _ in range(len(tsr))] # tsr stores simplices vertices, according to dimension, and the dictionnary + tsr:list = [[] for _ in range(len(tsr))] # tsr stores simplices vertices, according to dimension, and the dictionnary for dim in range(self.dimension(),-1 , -1): # range(2,-1,-1): for splx,F in self.get_skeleton(dim): if len(splx) != dim+1: continue @@ -876,7 +892,15 @@ cdef class SimplexTreeMulti: file.close() return - def get_filtration_grid(self, resolution:list[int]|np.ndarray, box=None, grid_strategy:str="regular"): + + + def _get_filtration_values(self, degrees:Iterable[int]): + cdef c_degrees = degrees + cdef intptr_t ptr = self.thisptr + cdef vector[vector[vector[value_type]]] out = get_filtration_values(ptr, c_degrees) + return [np.asarray(filtration, dtype=float) for filtration in out] + + def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, q:float=0.01, grid_strategy:str="regular"): """ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical @@ -894,20 +918,26 @@ cdef class SimplexTreeMulti: List of filtration values, for each parameter, defining the grid. """ if resolution is None: - warn("Provide a grid on which to squeeze !") - return - if box is None: - box = self.filtration_bounds() + resolution = [50]*len(self.num_parameters) + if degrees is None: + degrees = range(self.dimension()+1) + + if grid_strategy == "quantile": + filtrations_values = np.concatenate(self._get_filtration_values(degrees), axis=1) + filtration_grid = [ + np.quantile(filtration, np.linspace(0,1,num=res)) + for filtration, res in zip(filtrations_values, resolution) + ] + return filtration_grid + + box = self.filtration_bounds(degrees = degrees, q=q, split_dimension=False) assert(len(box[0]) == len(box[1]) == len(resolution) == self.num_parameters, f"Number of parameter not concistent. box: {len(box[0])}, resolution:{len(resolution)}, simplex tree:{self.num_parameters}") + if grid_strategy == "regular": - filtration_grid = np.array([np.linspace(*np.asarray(box)[:,i], num=resolution[i]) for i in range(self.num_parameters)]) - elif grid_strategy == "quantile": - filtrations_values = np.asarray(get_filtration_values(self.thisptr)) - filtration_grid = [np.quantile(filtration, np.linspace(0,1,num=res)) for filtration, res in zip(filtrations_values, resolution)] ## WARNING if multicritical cannot be turned into an array - else: - warn("Invalid grid strategy. Available ones are regular, and (todo) quantile") - return - return filtration_grid + return [np.linspace(*np.asarray(box)[:,i], num=resolution[i]) for i in range(self.num_parameters)] + + warn("Invalid grid strategy. Available ones are regular, and (todo) quantile") + return def grid_squeeze(self, filtration_grid:np.ndarray|list, coordinate_values:bool=False): @@ -928,14 +958,17 @@ cdef class SimplexTreeMulti: squeeze_filtration(ptr, c_filtration_grid, c_coordinate_values) return self - def filtration_bounds(self): + def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float=0, split_dimension:bool=False): """ Returns the filtrations bounds. """ - #TODO : check - low = np.min([f for s,F in self.get_simplices() for f in np.array_split(F, len(F) // self.num_parameters)], axis=0) - high = np.max([f for s,F in self.get_simplices() for f in np.array_split(F, len(F) // self.num_parameters)], axis=0) - return np.asarray([low,high]) + assert 0<= q <= 1 + degrees = range(self.dimension()+1) if degrees is None else degrees + filtrations_values = self._get_filtration_values(degrees) ## degree, parameter, pt + boxes = np.array([np.quantile(filtration, [q, 1-q], axis=1) for filtration in filtrations_values],dtype=float) + if split_dimension: return boxes + return np.asarray([np.min(boxes, axis=(0,1)), np.max(boxes, axis=(0,1))]) # box, birth/death, filtration + diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index 0ae7d0af2c..ac4354fe91 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -231,10 +231,11 @@ class Simplex_tree_interface : public Simplex_tree { Base::assign_key(Base::find(simplex), key); return; } - void fill_lowerstar(std::vector filtration, int axis){ + void fill_lowerstar(std::vector filtration, int axis){ + using value_type=options_multi::value_type; for (auto &SimplexHandle : Base::complex_simplex_range()){ - std::vector current_birth = Base::filtration(SimplexHandle); - double to_assign = -1*std::numeric_limits::infinity(); + std::vector current_birth = Base::filtration(SimplexHandle); + value_type to_assign = -1*std::numeric_limits::infinity(); for (auto vertex : Base::simplex_vertex_range(SimplexHandle)){ to_assign = std::max(filtration[vertex], to_assign); } @@ -262,7 +263,7 @@ class Simplex_tree_interface : public Simplex_tree { edge_list get_edge_list(){ edge_list simplex_list; simplex_list.reserve(Base::num_simplices()); - for (auto &simplexHandle : Base::skeleton_simplex_range(2)){ + for (auto &simplexHandle : Base::skeleton_simplex_range(1)){ if (Base::dimension(simplexHandle) == 1){ std::pair simplex; auto it = Base::simplex_vertex_range(simplexHandle).begin(); @@ -274,7 +275,7 @@ class Simplex_tree_interface : public Simplex_tree { /* simplex_list.shrink_to_fit();*/ return simplex_list; } - +// DEPRECATED, USE COORDINATE SIMPLEX TREE euler_chars_type euler_char(std::vector> &point_list){ // TODO multi-critical const int npts = point_list.size(); if (npts == 0){ @@ -316,7 +317,6 @@ class Simplex_tree_interface : public Simplex_tree { Base::assign_filtration(SimplexHandle, new_filtration_value); } } - }; diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index a0bae8a374..2a6c5b7a71 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -32,12 +32,13 @@ struct Simplex_tree_options_multidimensional_filtration { public: typedef linear_indexing_tag Indexing_tag; typedef int Vertex_handle; - typedef double value_type; - using Filtration_value = Gudhi::multi_filtrations::Finitely_critical_multi_filtration; // Cannot put Finitely_critical_multi_filtration, cython doesn't know how to convert inheritence + typedef float value_type; + using Filtration_value = Gudhi::multi_filtrations::Finitely_critical_multi_filtration; typedef std::uint32_t Simplex_key; static const bool store_key = true; static const bool store_filtration = true; static const bool contiguous_vertices = false; + static const bool is_multi_parameter = true; }; @@ -48,13 +49,16 @@ using multi_filtration_type = std::vector; using multi_filtration_grid = std::vector; - -void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension){ - Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(newsplxptr); +template +Simplex_tree& get_simplextree_from_pointer(const uintptr_t splxptr){ //DANGER + Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); + return st; +} +template +void multify(Simplex_tree<_options_std> &st, Simplex_tree<_options_multi> &st_multi, const int dimension){ if (dimension <= 0) {std::cerr << "Empty filtration\n"; throw ;} - options_multi::Filtration_value f(dimension); + typename _options_multi::Filtration_value f(dimension); for (auto &simplex_handle : st.complex_simplex_range()){ std::vector simplex; for (auto vertex : st.simplex_vertex_range(simplex_handle)) @@ -63,35 +67,50 @@ void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dime st_multi.insert_simplex(simplex,f); } } -void flatten(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension = 0){ - Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); + +void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension){ //for python + auto &st = get_simplextree_from_pointer(splxptr); + auto &st_multi = get_simplextree_from_pointer(newsplxptr); + multify(st, st_multi, dimension); +} + +template +void flatten(Simplex_tree<_options_std> &st, Simplex_tree<_options_multi> &st_multi, const int dimension = 0){ for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - Simplex_tree_options_multidimensional_filtration::value_type f = dimension >= 0 ? st_multi.filtration(simplex_handle)[dimension] : 0; + typename _options_multi::value_type f = dimension >= 0 ? st_multi.filtration(simplex_handle)[dimension] : 0; st.insert_simplex(simplex,f); } } +void flatten(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension = 0){ // for python + auto &st = get_simplextree_from_pointer(newsplxptr); + auto &st_multi = get_simplextree_from_pointer(splxptr); + flatten(st, st_multi, dimension); +} -template -void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ - Simplex_tree &st = *(Gudhi::Simplex_tree*)(newsplxptr); - Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - Gudhi::multi_filtrations::Line l(basepoint); +template +void flatten_diag(Simplex_tree<_options_std> &st, Simplex_tree<_options_multi> &st_multi, const std::vector basepoint, int dimension){ + Gudhi::multi_filtrations::Line l(basepoint); for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - std::vector f = st_multi.filtration(simplex_handle); + std::vector f = st_multi.filtration(simplex_handle); if (dimension <0) dimension = 0; - options_multi::value_type new_filtration = l.push_forward(f)[dimension]; + typename _options_multi::value_type new_filtration = l.push_forward(f)[dimension]; st.insert_simplex(simplex,new_filtration); } } +void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ // for python + auto &st = get_simplextree_from_pointer(newsplxptr); + auto &st_multi = get_simplextree_from_pointer(splxptr); + flatten_diag(st,st_multi,basepoint, dimension); +} + template @@ -123,7 +142,7 @@ void squeeze_filtration(uintptr_t splxptr, const multi_filtration_grid &grid, bo for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex_filtration = st_multi.filtration(simplex_handle); if (coordinate_values) - st_multi.assign_filtration(simplex_handle, find_coordinates(simplex_filtration, grid)); + st_multi.assign_filtration(simplex_handle, find_coordinates(simplex_filtration, grid)); else{ auto coordinates = find_coordinates(simplex_filtration, grid); std::vector squeezed_filtration(num_parameters); @@ -134,17 +153,24 @@ void squeeze_filtration(uintptr_t splxptr, const multi_filtration_grid &grid, bo } return; } -multi_filtration_grid get_filtration_values(const uintptr_t splxptr){ +std::vector get_filtration_values(const uintptr_t splxptr, const std::vector °rees){ Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); int num_parameters = st_multi.get_number_of_parameters(); - multi_filtration_grid out(num_parameters, multi_filtration_type(st_multi.num_simplices())); + std::vector out(degrees.size(), multi_filtration_grid(num_parameters)); + std::vector degree_index(degrees.size()); int count = 0; + for (auto degree : degrees){ + degree_index[degree] = count; count++; + out[degree_index[degree]].reserve(st_multi.num_simplices()); + } + for (const auto &simplex_handle : st_multi.complex_simplex_range()){ const auto filtration = st_multi.filtration(simplex_handle); + const auto degree = st_multi.dimension(simplex_handle); + if (std::find(degrees.begin(), degrees.end(), degree) == degrees.end()) continue; for (int parameter=0; parameter < num_parameters; parameter++){ - out[parameter][count] = filtration[parameter]; + out[degree_index[degree]][parameter].push_back(filtration[parameter]); } - count++; } return out; diff --git a/src/python/include/multi_filtrations/box.h b/src/python/include/multi_filtrations/box.h index 22f127b26d..0ee7113ad2 100644 --- a/src/python/include/multi_filtrations/box.h +++ b/src/python/include/multi_filtrations/box.h @@ -40,12 +40,15 @@ class Box using point_type = Finitely_critical_multi_filtration; public: Box(); + Box(T a, T b, T c, T d) : bottomCorner_({a,b}), upperCorner_({c,d}) {}; Box(const point_type& bottomCorner, const point_type& upperCorner); Box(const std::pair& box); void inflate(T delta); const point_type& get_bottom_corner() const; const point_type& get_upper_corner() const; + point_type& get_bottom_corner(); + point_type& get_upper_corner(); bool contains(const point_type& point) const; void infer_from_filters(const std::vector &Filters_list); bool is_trivial() const ; @@ -125,6 +128,18 @@ inline const typename Box::point_type &Box::get_upper_corner() const return upperCorner_; } +template +inline typename Box::point_type &Box::get_bottom_corner() +{ + return bottomCorner_; +} + +template +inline typename Box::point_type &Box::get_upper_corner() +{ + return upperCorner_; +} + template inline bool Box::contains(const point_type &point) const { diff --git a/src/python/include/multi_filtrations/finitely_critical_filtrations.h b/src/python/include/multi_filtrations/finitely_critical_filtrations.h index 9e7fb3be3b..297fca1383 100644 --- a/src/python/include/multi_filtrations/finitely_critical_filtrations.h +++ b/src/python/include/multi_filtrations/finitely_critical_filtrations.h @@ -1,6 +1,9 @@ -#pragma once +#ifndef FINITELY_CRITICAL_FILTRATIONS_H_ +#define FINITELY_CRITICAL_FILTRATIONS_H_ + #include #include +#include namespace Gudhi::multi_filtrations{ @@ -11,10 +14,11 @@ class Finitely_critical_multi_filtration : public std::vector { // explicit Finitely_critical_multi_filtration(std::vector& v) : ptr_(&v) { // }; // Conversion Finitely_critical_multi_filtration() : std::vector() {}; - Finitely_critical_multi_filtration(int n) : std::vector(n) {}; + Finitely_critical_multi_filtration(int n) : std::vector(n, -std::numeric_limits::infinity()) {}; // minus infinity by default Finitely_critical_multi_filtration(int n, T value) : std::vector(n,value) {}; Finitely_critical_multi_filtration(std::initializer_list init) : std::vector(init) {}; Finitely_critical_multi_filtration(const std::vector& v) : std::vector(v) {}; + operator std::vector&() const { return *this; @@ -122,8 +126,21 @@ class Finitely_critical_multi_filtration : public std::vector { static std::vector> from_python(const std::vector>& to_convert){ return std::vector>(to_convert.begin(), to_convert.end());; } + void push_to(const Finitely_critical_multi_filtration& x){ + if (this->size() != x.size()) + {std::cerr << "Does only work with 1-critical filtrations ! Sizes " << this->size() << " and " << x.size() << "are different !" << std::endl; return;} + for (unsigned int i = 0; i < x.size(); i++) + this->at(i) = this->at(i) > x[i] ? this->at(i) : x[i]; + } + // Warning, this function assumes that the comparisons checks have already been made ! + void insert_new(Finitely_critical_multi_filtration to_concatenate){ + this->insert( + this->end(), std::move_iterator(to_concatenate.begin()), std::move_iterator(to_concatenate.end()) + ); + } }; } // namespace Gudhi +#endif // FINITELY_CRITICAL_FILTRATIONS_H_ From 18054a366367cff9ede2a71ca92eba95c50c7e25 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Wed, 6 Sep 2023 11:13:43 +0200 Subject: [PATCH 09/18] update --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 42 +- .../Simplex_tree_node_explicit_storage.h | 2 +- src/python/gudhi/edge_collapse.py | 29 + src/python/gudhi/simplex_tree_multi.pxd | 52 +- src/python/gudhi/simplex_tree_multi.pyi | 716 +++++++++++++++++ src/python/gudhi/simplex_tree_multi.pyx | 719 +++++++++++------- .../include/Simplex_tree_interface_multi.h | 250 +++--- src/python/include/Simplex_tree_multi.h | 110 ++- src/python/include/multi_filtrations/box.h | 6 + .../finitely_critical_filtrations.h | 34 +- 10 files changed, 1449 insertions(+), 511 deletions(-) create mode 100644 src/python/gudhi/edge_collapse.py create mode 100644 src/python/gudhi/simplex_tree_multi.pyi diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 1ca5542eb0..66b9c10f7c 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -47,6 +47,7 @@ #include #include // for greater<> #include +#include // Inf #include #include // for std::max #include // for std::uint32_t @@ -150,15 +151,16 @@ class Simplex_tree { struct Filtration_simplex_base_real { Filtration_simplex_base_real() : filt_{} {} - void assign_filtration(Filtration_value f) { filt_ = f; } - Filtration_value filtration() const { return filt_; } + void assign_filtration(const Filtration_value& f) { filt_ = f; } + // Filtration_value filtration() const { return filt_; } + Filtration_value& filtration() { return filt_; } private: Filtration_value filt_; }; struct Filtration_simplex_base_dummy { Filtration_simplex_base_dummy() {} void assign_filtration(Filtration_value GUDHI_CHECK_code(f)) { GUDHI_CHECK(f == 0, "filtration value specified for a complex that does not store them"); } - Filtration_value filtration() const { return 0; } + Filtration_value& filtration() { return inf_; } }; typedef typename std::conditional::type Filtration_simplex_base; @@ -572,7 +574,7 @@ class Simplex_tree { * * Same as `filtration()`, but does not handle `null_simplex()`. */ - static Filtration_value filtration_(Simplex_handle sh) { + static const Filtration_value& filtration_(Simplex_handle sh) { GUDHI_CHECK (sh != null_simplex(), "null simplex"); return sh->second.filtration(); } @@ -600,18 +602,18 @@ class Simplex_tree { * Called on the null_simplex, it returns infinity. * If SimplexTreeOptions::store_filtration is false, returns 0. */ - static Filtration_value filtration(Simplex_handle sh) { + static Filtration_value& filtration(Simplex_handle sh){ if (sh != null_simplex()) { return sh->second.filtration(); } else { - return std::numeric_limits::infinity(); + return inf_; } } /** \brief Sets the filtration value of a simplex. * \exception std::invalid_argument In debug mode, if sh is a null_simplex. */ - void assign_filtration(Simplex_handle sh, Filtration_value fv) { + void assign_filtration(Simplex_handle sh, const Filtration_value& fv) { GUDHI_CHECK(sh != null_simplex(), std::invalid_argument("Simplex_tree::assign_filtration - cannot assign filtration on null_simplex")); sh->second.assign_filtration(fv); @@ -804,7 +806,7 @@ class Simplex_tree { */ template > std::pair insert_simplex_raw(const RandomVertexHandleRange& simplex, - Filtration_value filtration) { + const Filtration_value& filtration) { Siblings * curr_sib = &root_; std::pair res_insert; auto vi = simplex.begin(); @@ -870,7 +872,7 @@ class Simplex_tree { * .end() return input iterators, with 'value_type' Vertex_handle. */ template> std::pair insert_simplex(const InputVertexRange & simplex, - Filtration_value filtration = 0) { + const Filtration_value& filtration = {}) { auto first = std::begin(simplex); auto last = std::end(simplex); @@ -928,7 +930,7 @@ class Simplex_tree { std::pair rec_insert_simplex_and_subfaces_sorted(Siblings* sib, ForwardVertexIterator first, ForwardVertexIterator last, - Filtration_value filt) { + const Filtration_value& filt) { // An alternative strategy would be: // - try to find the complete simplex, if found (and low filtration) exit // - insert all the vertices at once in sib @@ -1031,11 +1033,13 @@ class Simplex_tree { * * Any insertion, deletion or change of filtration value invalidates this cache, * which can be cleared with clear_filtration(). */ - void initialize_filtration() { + void initialize_filtration(bool ignore_infinite_values = false) { filtration_vect_.clear(); filtration_vect_.reserve(num_simplices()); - for (Simplex_handle sh : complex_simplex_range()) + for (Simplex_handle sh : complex_simplex_range()){ + if (ignore_infinite_values && filtration(sh) == std::numeric_limits::infinity()) continue; filtration_vect_.push_back(sh); + } /* We use stable_sort here because with libstdc++ it is faster than sort. * is_before_in_filtration is now a total order, but we used to call @@ -1307,7 +1311,7 @@ class Simplex_tree { * The complex does not need to be empty before calling this function. However, if a vertex is * already present, its filtration value is not modified, unlike with other insertion functions. */ template - void insert_batch_vertices(VertexRange const& vertices, Filtration_value filt = 0) { + void insert_batch_vertices(VertexRange const& vertices, const Filtration_value& filt ={}) { auto verts = vertices | boost::adaptors::transformed([&](auto v){ return Dit_value_t(v, Node(&root_, filt)); }); root_.members_.insert(boost::begin(verts), boost::end(verts)); @@ -1387,7 +1391,7 @@ class Simplex_tree { static void intersection(std::vector >& intersection, Dictionary_it begin1, Dictionary_it end1, Dictionary_it begin2, Dictionary_it end2, - Filtration_value filtration_) { + const Filtration_value& filtration_) { if (begin1 == end1 || begin2 == end2) return; // ----->> while (true) { @@ -1623,7 +1627,7 @@ class Simplex_tree { * than it was before. However, `upper_bound_dimension()` will return the old value, which remains a valid upper * bound. If you care, you can call `dimension()` to recompute the exact dimension. */ - bool prune_above_filtration(Filtration_value filtration) { + bool prune_above_filtration(const Filtration_value& filtration) { if (std::numeric_limits::has_infinity && filtration == std::numeric_limits::infinity()) return false; // ---->> bool modified = rec_prune_above_filtration(root(), filtration); @@ -1633,7 +1637,7 @@ class Simplex_tree { } private: - bool rec_prune_above_filtration(Siblings* sib, Filtration_value filt) { + bool rec_prune_above_filtration(Siblings* sib, const Filtration_value& filt) { auto&& list = sib->members(); auto last = std::remove_if(list.begin(), list.end(), [this,filt](Dit_value_t& simplex) { if (simplex.second.filtration() <= filt) return false; @@ -2043,7 +2047,7 @@ class Simplex_tree { * @param[in] filt_value The new filtration value. * @param[in] min_dim The minimal dimension. Default value is 0. */ - void reset_filtration(Filtration_value filt_value, int min_dim = 0) { + void reset_filtration(const Filtration_value& filt_value, int min_dim = 0) { rec_reset_filtration(&root_, filt_value, min_dim); clear_filtration(); // Drop the cache. } @@ -2054,7 +2058,7 @@ class Simplex_tree { * @param[in] filt_value The new filtration value. * @param[in] min_depth The minimal depth. */ - void rec_reset_filtration(Siblings * sib, Filtration_value filt_value, int min_depth) { + void rec_reset_filtration(Siblings * sib, const Filtration_value& filt_value, int min_depth) { for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh) { if (min_depth <= 0) { sh->second.assign_filtration(filt_value); @@ -2228,6 +2232,7 @@ class Simplex_tree { int get_number_of_parameters() const{ return number_of_parameters_; } + inline static Filtration_value inf_ = std::numeric_limits::infinity(); private: int number_of_parameters_; }; @@ -2300,7 +2305,6 @@ struct Simplex_tree_options_fast_persistence { static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = false; static const bool is_multi_parameter = false; - }; /** Model of SimplexTreeOptions, faster cofaces than `Simplex_tree_options_full_featured`, note the diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h index 8918dcac3a..cb3205f816 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h @@ -41,7 +41,7 @@ struct GUDHI_EMPTY_BASE_CLASS_OPTIMIZATION Simplex_tree_node_explicit_storage : typedef typename SimplexTree::Simplex_key Simplex_key; Simplex_tree_node_explicit_storage(Siblings * sib = nullptr, - Filtration_value filtration = {}) + const Filtration_value& filtration = {}) : children_(sib) { this->assign_filtration(filtration); } diff --git a/src/python/gudhi/edge_collapse.py b/src/python/gudhi/edge_collapse.py new file mode 100644 index 0000000000..f4c9edd65d --- /dev/null +++ b/src/python/gudhi/edge_collapse.py @@ -0,0 +1,29 @@ +from tqdm import tqdm + +def _collapse_edge_list(edges, num:int=0, full:bool=False, strong:bool=False, progress:bool=False): + """ + Given an edge list defining a 1 critical 2 parameter 1 dimensional simplicial complex, simplificates this filtered simplicial complex, using filtration-domination's edge collapser. + """ + from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated + n = len(edges) + if full: + num = 100 + with tqdm(range(num), total=num, desc="Removing edges", disable=not(progress)) as I: + for i in I: + if strong: + edges = remove_strongly_filtration_dominated(edges) # nogil ? + else: + edges = remove_filtration_dominated(edges) + # Prevents doing useless collapses + if len(edges) >= n: + if full and strong: + strong = False + n = len(edges) + # n = edges.size() # len(edges) + else : + break + else: + n = len(edges) + # n = edges.size() + return edges + diff --git a/src/python/gudhi/simplex_tree_multi.pxd b/src/python/gudhi/simplex_tree_multi.pxd index 13f72df131..2c11eaad5d 100644 --- a/src/python/gudhi/simplex_tree_multi.pxd +++ b/src/python/gudhi/simplex_tree_multi.pxd @@ -20,94 +20,100 @@ __license__ = "MIT" ctypedef int dimension_type ctypedef float value_type -ctypedef vector[value_type] filtration_type +ctypedef Finitely_critical_multi_filtration filtration_type +ctypedef vector[value_type] python_filtration_type ## TODO: move constructor for C++ filtration type ? ctypedef vector[int] simplex_type ctypedef vector[simplex_type] simplex_list -ctypedef vector[pair[pair[int,int], pair[double, double]]] edge_list +ctypedef vector[pair[pair[int,int], pair[double, double]]] edge_list ctypedef vector[int] euler_char_list +ctypedef pair[simplex_type, value_type*] simplex_filtration_type cdef extern from "multi_filtrations/finitely_critical_filtrations.h" namespace "Gudhi::multi_filtrations": cdef cppclass Finitely_critical_multi_filtration "Gudhi::multi_filtrations::Finitely_critical_multi_filtration": - Finitely_critical_multi_filtration() nogil except + + Finitely_critical_multi_filtration() except + nogil Finitely_critical_multi_filtration(vector[value_type]) except + Finitely_critical_multi_filtration& operator=(const Finitely_critical_multi_filtration&) filtration_type& get_vector() nogil int size() nogil + void push_back(const value_type&) nogil + void clear() nogil + value_type& operator[](int) + cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": cdef cppclass Simplex_tree_options_multidimensional_filtration: pass - cdef cppclass Simplex_tree_multi_simplex_handle "Gudhi::Simplex_tree_interface::Simplex_handle": + cdef cppclass Simplex_tree_multi_simplex_handle "Gudhi::Simplex_tree_interface_multi::Simplex_handle": pass - cdef cppclass Simplex_tree_multi_simplices_iterator "Gudhi::Simplex_tree_interface::Complex_simplex_iterator": + cdef cppclass Simplex_tree_multi_simplices_iterator "Gudhi::Simplex_tree_interface_multi::Complex_simplex_iterator": Simplex_tree_multi_simplices_iterator() nogil Simplex_tree_multi_simplex_handle& operator*() nogil Simplex_tree_multi_simplices_iterator operator++() nogil bint operator!=(Simplex_tree_multi_simplices_iterator) nogil - cdef cppclass Simplex_tree_multi_skeleton_iterator "Gudhi::Simplex_tree_interface::Skeleton_simplex_iterator": + cdef cppclass Simplex_tree_multi_skeleton_iterator "Gudhi::Simplex_tree_interface_multi::Skeleton_simplex_iterator": Simplex_tree_multi_skeleton_iterator() nogil Simplex_tree_multi_simplex_handle& operator*() nogil Simplex_tree_multi_skeleton_iterator operator++() nogil bint operator!=(Simplex_tree_multi_skeleton_iterator) nogil - cdef cppclass Simplex_tree_multi_boundary_iterator "Gudhi::Simplex_tree_interface::Boundary_simplex_iterator": + cdef cppclass Simplex_tree_multi_boundary_iterator "Gudhi::Simplex_tree_interface_multi::Boundary_simplex_iterator": Simplex_tree_multi_boundary_iterator() nogil Simplex_tree_multi_simplex_handle& operator*() nogil Simplex_tree_multi_boundary_iterator operator++() nogil bint operator!=(Simplex_tree_multi_boundary_iterator) nogil - cdef cppclass Simplex_tree_multi_interface "Gudhi::Simplex_tree_interface": + cdef cppclass Simplex_tree_multi_interface "Gudhi::Simplex_tree_interface_multi": Simplex_tree_multi_interface() nogil Simplex_tree_multi_interface(Simplex_tree_multi_interface&) nogil - filtration_type simplex_filtration(vector[int] simplex) nogil - void assign_simplex_filtration(vector[int] simplex, filtration_type filtration) nogil + value_type* simplex_filtration(const vector[int]& simplex) nogil + void assign_simplex_filtration(vector[int]& simplex, const filtration_type& filtration) nogil void initialize_filtration() nogil int num_vertices() nogil int num_simplices() nogil void set_dimension(int dimension) nogil dimension_type dimension() nogil dimension_type upper_bound_dimension() nogil - bool find_simplex(vector[int] simplex) nogil - bool insert(vector[int] simplex, filtration_type filtration) nogil - vector[pair[simplex_type, filtration_type]] get_star(vector[int] simplex) nogil - vector[pair[simplex_type, filtration_type]] get_cofaces(vector[int] simplex, int dimension) nogil - void expansion(int max_dim) nogil except + + bool find_simplex(vector[int]& simplex) nogil + bool insert(vector[int]& simplex, filtration_type& filtration) nogil + vector[simplex_filtration_type] get_star(const vector[int]& simplex) nogil + vector[simplex_filtration_type] get_cofaces(const vector[int]& simplex, int dimension) nogil + void expansion(int max_dim) except + nogil void remove_maximal_simplex(simplex_type simplex) nogil # bool prune_above_filtration(filtration_type filtration) nogil - bool make_filtration_non_decreasing() nogil except + + bool make_filtration_non_decreasing() except + nogil # void compute_extended_filtration() nogil - Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) nogil except + - void reset_filtration(filtration_type filtration, int dimension) nogil + # Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) except + nogil + void reset_filtration(const filtration_type& filtration, int dimension) nogil bint operator==(Simplex_tree_multi_interface) nogil # Iterators over Simplex tree - pair[simplex_type, filtration_type] get_simplex_and_filtration(Simplex_tree_multi_simplex_handle f_simplex) nogil + simplex_filtration_type get_simplex_and_filtration(Simplex_tree_multi_simplex_handle f_simplex) nogil Simplex_tree_multi_simplices_iterator get_simplices_iterator_begin() nogil Simplex_tree_multi_simplices_iterator get_simplices_iterator_end() nogil vector[Simplex_tree_multi_simplex_handle].const_iterator get_filtration_iterator_begin() nogil vector[Simplex_tree_multi_simplex_handle].const_iterator get_filtration_iterator_end() nogil Simplex_tree_multi_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil Simplex_tree_multi_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil - pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] get_boundary_iterators(vector[int] simplex) nogil except + + pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] get_boundary_iterators(vector[int] simplex) except + nogil # Expansion with blockers ctypedef bool (*blocker_func_t)(vector[int], void *user_data) void expansion_with_blockers_callback(int dimension, blocker_func_t user_func, void *user_data) ## MULTIPERS STUFF - void set_keys_to_enumerate() nogil + void set_keys_to_enumerate() nogil const int get_key(const simplex_type) nogil void set_key(simplex_type, int) nogil - void fill_lowerstar(vector[value_type], int) nogil + void fill_lowerstar(const vector[value_type]&, int) nogil simplex_list get_simplices_of_dimension(int) nogil edge_list get_edge_list() nogil - euler_char_list euler_char(vector[filtration_type]) nogil + # euler_char_list euler_char(const vector[filtration_type]&) nogil void resize_all_filtrations(int) nogil void set_number_of_parameters(int) nogil int get_number_of_parameters() nogil diff --git a/src/python/gudhi/simplex_tree_multi.pyi b/src/python/gudhi/simplex_tree_multi.pyi new file mode 100644 index 0000000000..0c56229016 --- /dev/null +++ b/src/python/gudhi/simplex_tree_multi.pyi @@ -0,0 +1,716 @@ +import numpy as np +from gudhi.simplex_tree import SimplexTree ## Small hack for typing +from multipers.multiparameter_module_approximation import PyModule +from typing import Iterable +from tqdm import tqdm + + +# SimplexTree python interface +def class SimplexTreeMulti: + """The simplex tree is an efficient and flexible data structure for + representing general (filtered) simplicial complexes. The data structure + is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex + Tree: An Efficient Data Structure for General Simplicial Complexes. + Algorithmica, pages 1–22, 2014. + + This class is a multi-filtered, with keys, and non contiguous vertices version + of the simplex tree. + """ + + def __init__(self, other = None, num_parameters:int=2,default_values=[]): + """SimplexTreeMulti constructor. + + :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created. + If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`. + If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`. + :type other: SimplexTree or SimplexTreeMulti (Optional) + :param num_parameters: The number of parameter of the multi-parameter filtration. + :type num_parameters: int + :returns: An empty or a copy simplex tree. + :rtype: SimplexTreeMulti + + :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`. + """ + + + + def __is_defined(self): + """Returns true if SimplexTree pointer is not NULL. + """ + pass + + # def __is_persistence_defined(self): + # """Returns true if Persistence pointer is not NULL. + # """ + # return self.pcohptr != NULL + + def copy(self)->SimplexTreeMulti: + """ + :returns: A simplex tree that is a deep copy of itself. + :rtype: SimplexTreeMulti + + :note: The persistence information is not copied. If you need it in the clone, you have to call + :func:`compute_persistence` on it even if you had already computed it in the original. + """ + ... + + def __deepcopy__(self): + ... + + def filtration(self, simplex:list|np.ndarray)->np.ndarray: + """This function returns the filtration value for a given N-simplex in + this simplicial complex, or +infinity if it is not in the complex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :returns: The simplicial complex multi-critical filtration value. + :rtype: numpy array of shape (-1, num_parameters) + """ + ... + + def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->None: + """This function assigns a new multi-critical filtration value to a + given N-simplex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :param filtration: The new filtration(s) value(s), concatenated. + :type filtration: list[float] or np.ndarray[float, ndim=1] + + .. note:: + Beware that after this operation, the structure may not be a valid + filtration anymore, a simplex could have a lower filtration value + than one of its faces. Callers are responsible for fixing this + (with more :meth:`assign_filtration` or + :meth:`make_filtration_non_decreasing` for instance) before calling + any function that relies on the filtration property, like + :meth:`persistence`. + """ + ... + + def __getitem__(self, simplex): + ... + + + @property + def num_vertices(self)->int: + """This function returns the number of vertices of the simplicial + complex. + + :returns: The simplicial complex number of vertices. + :rtype: int + """ + ... + + @property + def num_simplices(self)->int: + """This function returns the number of simplices of the simplicial + complex. + + :returns: the simplicial complex number of simplices. + :rtype: int + """ + ... + + @property + def dimension(self)->int: + """This function returns the dimension of the simplicial complex. + + :returns: the simplicial complex dimension. + :rtype: int + + .. note:: + + This function is not constant time because it can recompute + dimension if required (can be triggered by + :func:`remove_maximal_simplex` + or + :func:`prune_above_filtration` + methods). + """ + ... + def upper_bound_dimension(self)->int: + """This function returns a valid dimension upper bound of the + simplicial complex. + + :returns: an upper bound on the dimension of the simplicial complex. + :rtype: int + """ + return self.get_ptr().upper_bound_dimension() + + def set_dimension(self, dimension)->None: + """This function sets the dimension of the simplicial complex. + + :param dimension: The new dimension value. + :type dimension: int + + .. note:: + + This function must be used with caution because it disables + dimension recomputation when required + (this recomputation can be triggered by + :func:`remove_maximal_simplex` + or + :func:`prune_above_filtration` + ). + """ + ... + + # def find(self, simplex)->bool: + # """This function returns if the N-simplex was found in the simplicial + # complex or not. + + # :param simplex: The N-simplex to find, represented by a list of vertex. + # :type simplex: list of int + # :returns: true if the simplex was found, false otherwise. + # :rtype: bool + # """ + # return self.get_ptr().find_simplex(simplex) + def __contains__(self, simplex)->bool: + """This function returns if the N-simplex was found in the simplicial + complex or not. + + :param simplex: The N-simplex to find, represented by a list of vertex. + :type simplex: list of int + :returns: true if the simplex was found, false otherwise. + :rtype: bool + """ + ... + + def insert(self, simplex, filtration:list|np.ndarray|None=None)->bool: + """This function inserts the given N-simplex and its subfaces with the + given filtration value (default value is '0.0'). If some of those + simplices are already present with a higher filtration value, their + filtration value is lowered. + + :param simplex: The N-simplex to insert, represented by a list of + vertex. + :type simplex: list of int + :param filtration: The filtration value of the simplex. + :type filtration: float + :returns: true if the simplex was not yet in the complex, false + otherwise (whatever its original filtration value). + :rtype: bool + """ + ... + + @cython.boundscheck(False) + @cython.wraparound(False) + def insert_batch(self, some_int[:,:] vertex_array, some_float[:,:] filtrations)->SimplexTreeMulti: + """Inserts k-simplices given by a sparse array in a format similar + to `torch.sparse `_. + The n-th simplex has vertices `vertex_array[0,n]`, ..., + `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`. + /!\ Only compatible with 1-critical filtrations. If a simplex is repeated, + only one filtration value will be taken into account. + + :param vertex_array: the k-simplices to insert. + :type vertex_array: numpy.array of shape (k+1,n) + :param filtrations: the filtration values. + :type filtrations: numpy.array of shape (n,num_parameters) + """ + # TODO : multi-critical + # cdef vector[int] vertices = np.unique(vertex_array) + ... + + + @cython.boundscheck(False) + @cython.wraparound(False) + def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti: + """Assign k-simplices given by a sparse array in a format similar + to `torch.sparse `_. + The n-th simplex has vertices `vertex_array[0,n]`, ..., + `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`. + /!\ Only compatible with 1-critical filtrations. If a simplex is repeated, + only one filtration value will be taken into account. + + :param vertex_array: the k-simplices to assign. + :type vertex_array: numpy.array of shape (k+1,n) + :param filtrations: the filtration values. + :type filtrations: numpy.array of shape (n,num_parameters) + """ + ... + + + + def get_simplices(self): + """This function returns a generator with simplices and their given + filtration values. + + :returns: The simplices. + :rtype: generator with tuples(simplex, filtration) + """ + ... + + def get_filtration(self): + """This function returns a generator with simplices and their given + filtration values sorted by increasing filtration values. + + :returns: The simplices sorted by increasing filtration values. + :rtype: generator with tuples(simplex, filtration) + """ + ... + + def get_skeleton(self, dimension): + """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension. + + :param dimension: The skeleton dimension value. + :type dimension: int + :returns: The (simplices of the) skeleton of a maximum dimension. + :rtype: generator with tuples(simplex, filtration) + """ + ... + + def get_star(self, simplex): + """This function returns the star of a given N-simplex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :returns: The (simplices of the) star of a simplex. + :rtype: list of tuples(simplex, filtration) + """ + ... + + def get_cofaces(self, simplex, codimension): + """This function returns the cofaces of a given N-simplex with a + given codimension. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + :param codimension: The codimension. If codimension = 0, all cofaces + are returned (equivalent of get_star function) + :type codimension: int + :returns: The (simplices of the) cofaces of a simplex + :rtype: list of tuples(simplex, filtration) + """ + ... + + def get_boundaries(self, simplex): + """This function returns a generator with the boundaries of a given N-simplex. + If you do not need the filtration values, the boundary can also be obtained as + :code:`itertools.combinations(simplex,len(simplex)-1)`. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int. + :returns: The (simplices of the) boundary of a simplex + :rtype: generator with tuples(simplex, filtration) + """ + ... + + def remove_maximal_simplex(self, simplex): + """This function removes a given maximal N-simplex from the simplicial + complex. + + :param simplex: The N-simplex, represented by a list of vertex. + :type simplex: list of int + + .. note:: + + The dimension of the simplicial complex may be lower after calling + remove_maximal_simplex than it was before. However, + :func:`upper_bound_dimension` + method will return the old value, which + remains a valid upper bound. If you care, you can call + :func:`dimension` + to recompute the exact dimension. + """ + ... + + # def prune_above_filtration(self, filtration)->bool: + # """Prune above filtration value given as parameter. + + # :param filtration: Maximum threshold value. + # :type filtration: float + # :returns: The filtration modification information. + # :rtype: bool + + + # .. note:: + + # Note that the dimension of the simplicial complex may be lower + # after calling + # :func:`prune_above_filtration` + # than it was before. However, + # :func:`upper_bound_dimension` + # will return the old value, which remains a + # valid upper bound. If you care, you can call + # :func:`dimension` + # method to recompute the exact dimension. + # """ + # return self.get_ptr().prune_above_filtration(filtration) + + def expansion(self, int max_dim)->SimplexTreeMulti: + """Expands the simplex tree containing only its one skeleton + until dimension max_dim. + + The expanded simplicial complex until dimension :math:`d` + attached to a graph :math:`G` is the maximal simplicial complex of + dimension at most :math:`d` admitting the graph :math:`G` as + :math:`1`-skeleton. + The filtration value assigned to a simplex is the maximal filtration + value of one of its edges. + + The simplex tree must contain no simplex of dimension bigger than + 1 when calling the method. + + :param max_dim: The maximal dimension. + :type max_dim: int + """ + ... + + def make_filtration_non_decreasing(self)->bool: + """This function ensures that each simplex has a higher filtration + value than its faces by increasing the filtration values. + + :returns: True if any filtration value was modified, + False if the filtration was already non-decreasing. + :rtype: bool + """ + ... + + def reset_filtration(self, filtration, min_dim = 0): + """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the + simplex tree when `min_dim = 0`. + `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to + make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing` + afterwards for instance). + + :param filtration: New threshold value. + :type filtration: float. + :param min_dim: The minimal dimension. Default value is 0. + :type min_dim: int. + """ + ... + + + + # def extend_filtration(self): + # """ Extend filtration for computing extended persistence. This function only uses the filtration values at the + # 0-dimensional simplices, and computes the extended persistence diagram induced by the lower-star filtration + # computed with these values. + # + # .. note:: + # + # Note that after calling this function, the filtration values are actually modified within the simplex tree. + # The function :func:`extended_persistence` retrieves the original values. + # + # .. note:: + # + # Note that this code creates an extra vertex internally, so you should make sure that the simplex tree does + # not contain a vertex with the largest possible value (i.e., 4294967295). + # + # This `notebook `_ + # explains how to compute an extension of persistence called extended persistence. + # """ + # self.get_ptr().compute_extended_filtration() + + # def extended_persistence(self, homology_coeff_field=11, min_persistence=0): + # """This function retrieves good values for extended persistence, and separate the diagrams into the Ordinary, + # Relative, Extended+ and Extended- subdiagrams. + # + # :param homology_coeff_field: The homology coefficient field. Must be a prime number. Default value is 11. Max is 46337. + # :type homology_coeff_field: int + # :param min_persistence: The minimum persistence value (i.e., the absolute value of the difference between the + # persistence diagram point coordinates) to take into account (strictly greater than min_persistence). + # Default value is 0.0. Sets min_persistence to -1.0 to see all values. + # :type min_persistence: float + # :returns: A list of four persistence diagrams in the format described in :func:`persistence`. The first one is + # Ordinary, the second one is Relative, the third one is Extended+ and the fourth one is Extended-. + # See https://link.springer.com/article/10.1007/s10208-008-9027-z and/or section 2.2 in + # https://link.springer.com/article/10.1007/s10208-017-9370-z for a description of these subtypes. + # + # .. note:: + # + # This function should be called only if :func:`extend_filtration` has been called first! + # + # .. note:: + # + # The coordinates of the persistence diagram points might be a little different than the + # original filtration values due to the internal transformation (scaling to [-2,-1]) that is + # performed on these values during the computation of extended persistence. + # + # This `notebook `_ + # explains how to compute an extension of persistence called extended persistence. + # """ + # cdef vector[pair[int, pair[value_type, value_type]]] persistence_result + # if self.pcohptr != NULL: + # del self.pcohptr + # self.pcohptr = new Simplex_tree_persistence_interface(self.get_ptr(), False) + # self.pcohptr.compute_persistence(homology_coeff_field, -1.) + # return self.pcohptr.compute_extended_persistence_subdiagrams(min_persistence) + + # TODO : cython3 + # def expansion_with_blocker(self, max_dim, blocker_func): + # """Expands the Simplex_tree containing only a graph. Simplices corresponding to cliques in the graph are added + # incrementally, faces before cofaces, unless the simplex has dimension larger than `max_dim` or `blocker_func` + # returns `True` for this simplex. + + # The function identifies a candidate simplex whose faces are all already in the complex, inserts it with a + # filtration value corresponding to the maximum of the filtration values of the faces, then calls `blocker_func` + # with this new simplex (represented as a list of int). If `blocker_func` returns `True`, the simplex is removed, + # otherwise it is kept. The algorithm then proceeds with the next candidate. + + # .. warning:: + # Several candidates of the same dimension may be inserted simultaneously before calling `blocker_func`, so + # if you examine the complex in `blocker_func`, you may hit a few simplices of the same dimension that have + # not been vetted by `blocker_func` yet, or have already been rejected but not yet removed. + + # :param max_dim: Expansion maximal dimension value. + # :type max_dim: int + # :param blocker_func: Blocker oracle. + # :type blocker_func: Callable[[List[int]], bool] + # """ + # self.get_ptr().expansion_with_blockers_callback(max_dim, callback, blocker_func) + + # def persistence(self, homology_coeff_field=11, min_persistence=0, persistence_dim_max = False): + # """This function computes and returns the persistence of the simplicial complex. + # + # :param homology_coeff_field: The homology coefficient field. Must be a + # prime number. Default value is 11. Max is 46337. + # :type homology_coeff_field: int + # :param min_persistence: The minimum persistence value to take into + # account (strictly greater than min_persistence). Default value is + # 0.0. + # Set min_persistence to -1.0 to see all values. + # :type min_persistence: float + # :param persistence_dim_max: If true, the persistent homology for the + # maximal dimension in the complex is computed. If false, it is + # ignored. Default is false. + # :type persistence_dim_max: bool + # :returns: The persistence of the simplicial complex. + # :rtype: list of pairs(dimension, pair(birth, death)) + # """ + # self.compute_persistence(homology_coeff_field, min_persistence, persistence_dim_max) + # return self.pcohptr.get_persistence() + + + +## This function is only meant for the edge collapse interface. + def get_edge_list(self): + ... + + def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False)->SimplexTreeMulti: + """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574). + It uses the code from the github repository https://github.com/aj-alonso/filtration_domination . + + Parameters + ---------- + max_dimension:int + Max simplicial dimension of the complex. Unless specified, keeps the same dimension. + num:int + The number of collapses to do. + strong:bool + Whether to use strong collapses or standard collapses (slower, but may remove more edges) + full:bool + Collapses the maximum number of edges if true, i.e., will do (at most) 100 strong collapses and (at most) 100 non-strong collapses afterward. + progress:bool + If true, shows the progress of the number of collapses. + + WARNING + ------- + - This will destroy all of the k-simplices, with k>=2. Be sure to use this with a clique complex, if you want to preserve the homology >= dimension 1. + - This is for 1 critical simplices, with 2 parameter persistence. + Returns + ------- + self:SimplexTreeMulti + A (smaller) simplex tree that has the same homology over this bifiltration. + + """ + # TODO : find a way to do multiple edge collapses without python conversions. + ... + + def _reconstruct_from_edge_list(self, edges, swap:bool=True, expand_dimension:int=None)->SimplexTreeMulti: + """ + Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses + + Input + ----- + - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination + - swap : bool + If true, will swap self and the collapsed simplextrees. + - expand_dim : int + expands back the simplextree to this dimension + Ouput + ----- + The reduced SimplexTreeMulti having only these edges. + """ + ... + + @property + def num_parameters(self)->int: + ... + def get_simplices_of_dimension(self, dim:int)->np.ndarray: + ... + def key(self, simplex:list|np.ndarray): + ... + def set_keys_to_enumerate(self)->None: + ... + def set_key(self,simplex:list|np.ndarray, key:int)->None: + ... + + + def to_scc(self, path="scc_dataset.txt", progress:bool=True, overwrite:bool=False, ignore_last_generators:bool=True, strip_comments:bool=False, reverse_block:bool=True, rivet_compatible=False)->None: + """ Create a file with the scc2020 standard, representing the n-filtration of the simplextree. + Link : https://bitbucket.org/mkerber/chain_complex_format/src/master/ + + Parameters + ---------- + path:str + path of the file. + ignore_last_generators:bool = True + If false, will include the filtration values of the last free persistence module. + progress:bool = True + Shows the progress bar. + overwrite:bool = False + If true, will overwrite the previous file if it already exists. + ignore_last_generators:bool=True + If true, does not write the final generators to the file. Rivet ignores them. + reverse_block:bool=True + Some obscure programs reverse the inside-block order. + rivet_compatible:bool=False + Returns a firep (old scc2020) format instead. Only Rivet uses this. + + Returns + ------- + Nothing + """ + ... + + def to_rivet(self, path="rivet_dataset.txt", degree:int|None = None, progress:bool=False, overwrite:bool=False, xbins:int|None=None, ybins:int|None=None)->None: + """ Create a file that can be imported by rivet, representing the filtration of the simplextree. + + Parameters + ---------- + path:str + path of the file. + degree:int + The homological degree to ask rivet to compute. + progress:bool = True + Shows the progress bar. + overwrite:bool = False + If true, will overwrite the previous file if it already exists. + Returns + ------- + Nothing + """ + ... + + + + def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False)->Iterable[np.ndarray]: + # cdef vector[int] c_degrees = degrees + ... + + @staticmethod + def _reduce_grid(filtrations_values,resolutions=None, strategy:str="exact", bool unique=True, some_float _q_factor=1., drop_quantiles=[0,0]): + ... + + def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:str="exact")->Iterable[np.ndarray]: + """ + Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical + + Parameters + ---------- + resolution: list[int] + resolution of the grid, for each parameter + box=None : pair[list[float]] + Grid bounds. format : [low bound, high bound] + If None is given, will use the filtration bounds of the simplextree. + grid_strategy="regular" : string + Either "regular", "quantile", or "exact". + Returns + ------- + List of filtration values, for each parameter, defining the grid. + """ + ... + + + + def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, coordinate_values:bool=True, force=False, **filtration_grid_kwargs)->SimplexTreeMulti: + """ + Fit the filtration of the simplextree to a grid. + + :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method. + :type filtration_grid: list[list[float]] + :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid. + :type coordinate_values: bool + """ + ... + + @property + def _is_squeezed(self)->bool: + ... + + def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray: + """ + Returns the filtrations bounds of the finite filtration values. + """ + ... + + + + + def fill_lowerstar(self, F, parameter:int)->SimplexTreeMulti: + """ Fills the `dimension`th filtration by the lower-star filtration defined by F. + + Parameters + ---------- + F:1d array + The density over the vertices, that induces a lowerstar filtration. + parameter:int + Which filtration parameter to fill. /!\ python starts at 0. + + Returns + ------- + self:SimplexTreeMulti + """ + # for s, sf in self.get_simplices(): + # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)]) + ... + + def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None)->SimplexTree: + """Converts an multi simplextree to a gudhi simplextree. + Parameters + ---------- + parameter:int = 0 + The parameter to keep. WARNING will crash if the multi simplextree is not well filled. + basepoint:None + Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint. + WARNING + ------- + There are no safeguard yet, it WILL crash if asking for a parameter that is not filled. + Returns + ------- + A SimplexTree with chosen 1D filtration. + """ + ... + + def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]: + """ + Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree. + + Input + ----- + - Array of shape (num_linear_forms, num_parameters) + + Output + ------ + - List of projected (gudhi) simplextrees. + """ + ... + + + def set_num_parameter(self, num:int): + """ + Sets the numbers of parameters. + WARNING : it will resize all the filtrations to this size. + """ + ... + + def __eq__(self, other:SimplexTreeMulti): + """Test for structural equality + :returns: True if the 2 simplex trees are equal, False otherwise. + :rtype: bool + """ + ... + \ No newline at end of file diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index f85edd3f4c..8cb8b61a10 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -19,16 +19,19 @@ from libc.stdint cimport intptr_t, int32_t, int64_t from cython.operator import dereference, preincrement from libc.stdint cimport intptr_t from libc.stdint cimport uintptr_t +from libcpp.map cimport map ctypedef fused some_int: int32_t int64_t + int ctypedef fused some_float: float double +ctypedef vector[pair[pair[int,int],pair[value_type,value_type]]] edge_list_type from typing import Any @@ -38,9 +41,9 @@ cnp.import_array() from gudhi.simplex_tree_multi cimport * cimport cython -from gudhi import SimplexTree ## Small hack for typing +from gudhi.simplex_tree import SimplexTree ## Small hack for typing from typing import Iterable - +from tqdm import tqdm @@ -48,11 +51,12 @@ from warnings import warn cdef extern from "Simplex_tree_multi.h" namespace "Gudhi": - void multify(const uintptr_t, const uintptr_t, const unsigned int) nogil except + - void flatten(const uintptr_t, const uintptr_t, const unsigned int) nogil - void flatten_diag(const uintptr_t, const uintptr_t, const vector[value_type], int) nogil - void squeeze_filtration(uintptr_t, const vector[vector[value_type]]&, bool) nogil except + - vector[vector[vector[value_type]]] get_filtration_values(uintptr_t, const vector[int]&) nogil + void multify_from_ptr(const uintptr_t, const uintptr_t, const unsigned int, const vector[value_type]&) except + nogil + void flatten_from_ptr(const uintptr_t, const uintptr_t, const unsigned int) nogil + void linear_projection_from_ptr(const uintptr_t, const uintptr_t, const vector[value_type]&) nogil + void flatten_diag_from_ptr(const uintptr_t, const uintptr_t, const vector[value_type], int) nogil + void squeeze_filtration(uintptr_t, const vector[vector[value_type]]&, bool) except + nogil + vector[vector[vector[value_type]]] get_filtration_values(uintptr_t, const vector[int]&) except + nogil cdef bool callback(vector[int] simplex, void *blocker_func): @@ -72,7 +76,7 @@ cdef class SimplexTreeMulti: # unfortunately 'cdef public Simplex_tree_multi_interface* thisptr' is not possible # Use intptr_t instead to cast the pointer cdef public intptr_t thisptr - + cdef public vector[vector[value_type]] filtration_grid # Get the pointer casted as it should be cdef Simplex_tree_multi_interface* get_ptr(self) nogil: @@ -80,7 +84,7 @@ cdef class SimplexTreeMulti: # cdef Simplex_tree_persistence_interface * pcohptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, other = None, num_parameters:int=2): + def __init__(self, other = None, num_parameters:int=2,default_values=[]): """SimplexTreeMulti constructor. :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created. @@ -96,27 +100,31 @@ cdef class SimplexTreeMulti: """ # The real cython constructor - def __cinit__(self, other = None, num_parameters:int=2): #TODO doc - if not other is None: + def __cinit__(self, other = None, int num_parameters=2, + default_values=[-np.inf], # I'm not sure why `[]` does not work. Cython bug ? + ): #TODO doc + cdef vector[value_type] c_default_values=default_values + if other is not None: if isinstance(other, SimplexTreeMulti): self.thisptr = _get_copy_intptr(other) + num_parameters = other.num_parameters + self.filtration_grid = other.filtration_grid elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree self.thisptr = (new Simplex_tree_multi_interface()) - multify(other.thisptr, self.thisptr, num_parameters) + multify_from_ptr(other.thisptr, self.thisptr, num_parameters, c_default_values) else: - raise TypeError("`other` argument requires to be of type `SimplexTree`, or `None`.") + raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.") else: self.thisptr = (new Simplex_tree_multi_interface()) self.get_ptr().set_number_of_parameters(num_parameters) - - # TODO : set number of parameters outside the constructor ? + self.filtration_grid=[[]*num_parameters] def __dealloc__(self): cdef Simplex_tree_multi_interface* ptr = self.get_ptr() if ptr != NULL: del ptr # if self.pcohptr != NULL: - # del self.pcohptr + # del self.pcohptr def __is_defined(self): """Returns true if SimplexTree pointer is not NULL. @@ -136,14 +144,13 @@ cdef class SimplexTreeMulti: :note: The persistence information is not copied. If you need it in the clone, you have to call :func:`compute_persistence` on it even if you had already computed it in the original. """ - stree = SimplexTreeMulti(num_parameters=self.num_parameters) - stree.thisptr = _get_copy_intptr(self) + stree = SimplexTreeMulti(self,num_parameters=self.num_parameters) return stree def __deepcopy__(self): return self.copy() - def filtration(self, simplex:list|np.ndarray)->filtration_type: + def filtration(self, simplex:list|np.ndarray)->np.ndarray: """This function returns the filtration value for a given N-simplex in this simplicial complex, or +infinity if it is not in the complex. @@ -152,10 +159,9 @@ cdef class SimplexTreeMulti: :returns: The simplicial complex multi-critical filtration value. :rtype: numpy array of shape (-1, num_parameters) """ - filtration_vector = np.array(self.get_ptr().simplex_filtration(simplex)) - return filtration_vector.reshape((-1, self.num_parameters)) + return self[simplex] - def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray): + def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->None: """This function assigns a new multi-critical filtration value to a given N-simplex. @@ -174,9 +180,15 @@ cdef class SimplexTreeMulti: :meth:`persistence`. """ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0 - self.get_ptr().assign_simplex_filtration(simplex, filtration) + self.get_ptr().assign_simplex_filtration(simplex, Finitely_critical_multi_filtration(filtration)) + def __getitem__(self, simplex): + cdef vector[int] csimplex = simplex + cdef value_type[:] filtration_view = self.get_ptr().simplex_filtration(csimplex) + return np.asarray(filtration_view) + + @property def num_vertices(self)->int: """This function returns the number of vertices of the simplicial complex. @@ -185,6 +197,8 @@ cdef class SimplexTreeMulti: :rtype: int """ return self.get_ptr().num_vertices() + + @property def num_simplices(self)->int: """This function returns the number of simplices of the simplicial complex. @@ -194,7 +208,8 @@ cdef class SimplexTreeMulti: """ return self.get_ptr().num_simplices() - def dimension(self)->dimension_type: + @property + def dimension(self)->int: """This function returns the dimension of the simplicial complex. :returns: the simplicial complex dimension. @@ -210,7 +225,7 @@ cdef class SimplexTreeMulti: methods). """ return self.get_ptr().dimension() - def upper_bound_dimension(self)->dimension_type: + def upper_bound_dimension(self)->int: """This function returns a valid dimension upper bound of the simplicial complex. @@ -237,7 +252,17 @@ cdef class SimplexTreeMulti: """ self.get_ptr().set_dimension(dimension) - def find(self, simplex)->bool: + # def find(self, simplex)->bool: + # """This function returns if the N-simplex was found in the simplicial + # complex or not. + + # :param simplex: The N-simplex to find, represented by a list of vertex. + # :type simplex: list of int + # :returns: true if the simplex was found, false otherwise. + # :rtype: bool + # """ + # return self.get_ptr().find_simplex(simplex) + def __contains__(self, simplex)->bool: """This function returns if the N-simplex was found in the simplicial complex or not. @@ -263,22 +288,16 @@ cdef class SimplexTreeMulti: otherwise (whatever its original filtration value). :rtype: bool """ + # TODO C++, to be compatible with insert_batch and multicritical filtrations num_parameters = self.get_ptr().get_number_of_parameters() - if filtration is None: filtration = np.array([-np.inf]*num_parameters, dtype = float) - # simplex_already_exists = not self.get_ptr().insert(simplex, filtration) - # if simplex_already_exists: - # old_filtrations = self.filtration(simplex) - # for f in old_filtrations: - # if np.all(f >= filtration) or np.all(f <= filtration): - # return False - # new_filtration = np.concatenate([*old_filtrations, filtration], axis = 0) - # self.assign_filtration(simplex, new_filtration) - # return True - return self.get_ptr().insert(simplex, filtration) + assert filtration is None or len(filtration) % num_parameters == 0 + if filtration is None: + filtration = np.array([-np.inf]*num_parameters, dtype = float) + return self.get_ptr().insert(simplex, Finitely_critical_multi_filtration(filtration)) @cython.boundscheck(False) @cython.wraparound(False) - def insert_batch(self,some_int[:,:] vertex_array, some_float[:,:] filtrations): + def insert_batch(self, some_int[:,:] vertex_array, some_float[:,:] filtrations)->SimplexTreeMulti: """Inserts k-simplices given by a sparse array in a format similar to `torch.sparse `_. The n-th simplex has vertices `vertex_array[0,n]`, ..., @@ -300,19 +319,9 @@ cdef class SimplexTreeMulti: cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v - cdef vector[value_type] w + cdef Finitely_critical_multi_filtration w cdef int n_parameters = self.num_parameters with nogil: - # Without this, it we end up inserting vertic could be slow ifes in a bad order (flat_map). - # NaN currently does the wrong thing - # self.get_ptr().insert_batch_vertices(vertices, INFINITY) ## TODO - -# if k !=1: # Ensure all vertices are already insered first. -# for vertex in vertices: -# if not self.get_ptr().find_simplex([vertex]): -# warn("Failed. Insert all vertices first !") -# return - for i in range(n): for j in range(k): v.push_back(vertex_array[j, i]) @@ -322,13 +331,46 @@ cdef class SimplexTreeMulti: v.clear() w.clear() return self - # @staticmethod - # cdef pair[simplex_type,vector[double]] _pair_simplex_filtration_to_python(pair[simplex_type,Finitely_critical_multi_filtration]& truc): - # cdef pair[simplex_type,vector[double]] out - # with nogil: - # out.first.swap(truc.first) - # out.second.swap(truc.second.get_vector()) - # return out + + + @cython.boundscheck(False) + @cython.wraparound(False) + def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti: + """Assign k-simplices given by a sparse array in a format similar + to `torch.sparse `_. + The n-th simplex has vertices `vertex_array[0,n]`, ..., + `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`. + /!\ Only compatible with 1-critical filtrations. If a simplex is repeated, + only one filtration value will be taken into account. + + :param vertex_array: the k-simplices to assign. + :type vertex_array: numpy.array of shape (k+1,n) + :param filtrations: the filtration values. + :type filtrations: numpy.array of shape (n,num_parameters) + """ + cdef Py_ssize_t k = vertex_array.shape[0] + cdef Py_ssize_t n = vertex_array.shape[1] + assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations' + assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters" + cdef Py_ssize_t i + cdef Py_ssize_t j + cdef vector[int] v + cdef Finitely_critical_multi_filtration w + cdef int n_parameters = self.num_parameters + with nogil: + for i in range(n): + for j in range(k): + v.push_back(vertex_array[j, i]) + for j in range(n_parameters): + w.push_back(filtrations[i,j]) + self.get_ptr().assign_simplex_filtration(v, w) + v.clear() + w.clear() + if propagate: self.make_filtration_non_decreasing() + return self + + + def get_simplices(self): """This function returns a generator with simplices and their given filtration values. @@ -345,9 +387,12 @@ cdef class SimplexTreeMulti: # out = (out_.first,out_.second.get_vector()) # yield out # preincrement(it) - cdef pair[simplex_type,filtration_type] out + # cdef pair[simplex_type,filtration_type] out + cdef int num_parameters = self.get_ptr().get_number_of_parameters() while it != end: - yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair = self.get_ptr().get_simplex_and_filtration(dereference(it)) + + yield (np.asarray(pair.first, dtype=int),np.asarray( pair.second)) # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out) preincrement(it) @@ -360,9 +405,11 @@ cdef class SimplexTreeMulti: """ cdef vector[Simplex_tree_multi_simplex_handle].const_iterator it = self.get_ptr().get_filtration_iterator_begin() cdef vector[Simplex_tree_multi_simplex_handle].const_iterator end = self.get_ptr().get_filtration_iterator_end() - + cdef int num_parameters = self.get_ptr().get_number_of_parameters() while it != end: - yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + # yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair = self.get_ptr().get_simplex_and_filtration(dereference(it)) + yield (np.asarray(pair.first, dtype=int),np.asarray( pair.second)) preincrement(it) def get_skeleton(self, dimension): @@ -375,9 +422,11 @@ cdef class SimplexTreeMulti: """ cdef Simplex_tree_multi_skeleton_iterator it = self.get_ptr().get_skeleton_iterator_begin(dimension) cdef Simplex_tree_multi_skeleton_iterator end = self.get_ptr().get_skeleton_iterator_end(dimension) - + cdef int num_parameters = self.get_ptr().get_number_of_parameters() while it != end: - yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + # yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair = self.get_ptr().get_simplex_and_filtration(dereference(it)) + yield (np.asarray(pair.first, dtype=int),np.asarray( pair.second)) preincrement(it) def get_star(self, simplex): @@ -388,17 +437,19 @@ cdef class SimplexTreeMulti: :returns: The (simplices of the) star of a simplex. :rtype: list of tuples(simplex, filtration) """ - cdef simplex_type csimplex - for i in simplex: - csimplex.push_back(i) - cdef vector[pair[simplex_type, filtration_type]] star \ + cdef simplex_type csimplex = simplex + cdef int num_parameters = self.num_parameters + # for i in simplex: + # csimplex.push_back(i) + cdef vector[simplex_filtration_type] star \ = self.get_ptr().get_star(csimplex) ct = [] + for filtered_simplex in star: v = [] for vertex in filtered_simplex.first: v.append(vertex) - ct.append((v, filtered_simplex.second)) + ct.append((v, np.asarray(filtered_simplex.second))) return ct def get_cofaces(self, simplex, codimension): @@ -413,17 +464,18 @@ cdef class SimplexTreeMulti: :returns: The (simplices of the) cofaces of a simplex :rtype: list of tuples(simplex, filtration) """ - cdef vector[int] csimplex - for i in simplex: - csimplex.push_back(i) - cdef vector[pair[simplex_type, filtration_type]] cofaces \ + cdef vector[int] csimplex = simplex + cdef int num_parameters = self.num_parameters + # for i in simplex: + # csimplex.push_back(i) + cdef vector[simplex_filtration_type] cofaces \ = self.get_ptr().get_cofaces(csimplex, codimension) ct = [] for filtered_simplex in cofaces: v = [] for vertex in filtered_simplex.first: v.append(vertex) - ct.append((v, filtered_simplex.second)) + ct.append((v, np.asarray(filtered_simplex.second))) return ct def get_boundaries(self, simplex): @@ -438,10 +490,15 @@ cdef class SimplexTreeMulti: """ cdef pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] it = self.get_ptr().get_boundary_iterators(simplex) + # while it.first != it.second: + # yield self.get_ptr().get_simplex_and_filtration(dereference(it.first)) + # preincrement(it.first) + cdef int num_parameters = self.get_ptr().get_number_of_parameters() while it.first != it.second: - yield self.get_ptr().get_simplex_and_filtration(dereference(it.first)) + # yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first)) + yield (np.asarray(pair.first, dtype=int),np.asarray( pair.second)) preincrement(it.first) - def remove_maximal_simplex(self, simplex): """This function removes a given maximal N-simplex from the simplicial complex. @@ -484,7 +541,7 @@ cdef class SimplexTreeMulti: # """ # return self.get_ptr().prune_above_filtration(filtration) - def expansion(self, max_dim)->SimplexTreeMulti: + def expansion(self, int max_dim)->SimplexTreeMulti: """Expands the simplex tree containing only its one skeleton until dimension max_dim. @@ -501,15 +558,13 @@ cdef class SimplexTreeMulti: :param max_dim: The maximal dimension. :type max_dim: int """ - cdef int maxdim = max_dim - current_dim = self.dimension() with nogil: - self.get_ptr().expansion(maxdim) + self.get_ptr().expansion(max_dim) # This is a fix for multipersistence. FIXME expansion in c++ self.get_ptr().make_filtration_non_decreasing() return self - def make_filtration_non_decreasing(self)->bool: # FIXME TODO code in c++ + def make_filtration_non_decreasing(self)->bool: """This function ensures that each simplex has a higher filtration value than its faces by increasing the filtration values. @@ -521,34 +576,6 @@ cdef class SimplexTreeMulti: with nogil: out = self.get_ptr().make_filtration_non_decreasing() return out - - ## DEPRECATED - def make_filtration_non_decreasing_old(self, start_dimension:int = 1): - if start_dimension <= 0: - start_dimension = 1 -# cdef Simplex_tree_skeleton_iterator it -# cdef Simplex_tree_skeleton_iterator end -## cdef Simplex_tree_simplex_handle sh = dereference(it) -# cdef pair[vector[int], double] simplex_filtration -# cdef int dim -# cdef int c_start_dimension = start_dimension -# cdef int c_stop_dimension = self.dimension()+1 -# cdef cnp.ndarray[float, ] -# with nogil: -# for dim in range(c_start_dimension, c_stop_dimension): -# it = self.get_ptr().get_skeleton_iterator_begin(dim) -# end = self.get_ptr().get_skeleton_iterator_end(dim) -# while it != end: -# simplex_filtration = self.get_ptr().get_simplex_and_filtration(dereference(it)) -# if simplex_filtration.size() == dim+1: -# # TODO, -# preincrement(it) - for dim in range(start_dimension, self.dimension()+1): - for splx, f in self.get_skeleton(dim): - if len(splx) != dim + 1: continue - self.assign_filtration(splx, np.max([g for _,g in self.get_boundaries(splx)] + [f], axis=0)) - # FIXME adapt for multicritical filtrations # TODO : C++ - return self def reset_filtration(self, filtration, min_dim = 0): """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the @@ -562,7 +589,9 @@ cdef class SimplexTreeMulti: :param min_dim: The minimal dimension. Default value is 0. :type min_dim: int. """ - self.get_ptr().reset_filtration(filtration, min_dim) + self.get_ptr().reset_filtration(Finitely_critical_multi_filtration(filtration), min_dim) + + # def extend_filtration(self): # """ Extend filtration for computing extended persistence. This function only uses the filtration values at the @@ -619,27 +648,28 @@ cdef class SimplexTreeMulti: # self.pcohptr.compute_persistence(homology_coeff_field, -1.) # return self.pcohptr.compute_extended_persistence_subdiagrams(min_persistence) - def expansion_with_blocker(self, max_dim, blocker_func): - """Expands the Simplex_tree containing only a graph. Simplices corresponding to cliques in the graph are added - incrementally, faces before cofaces, unless the simplex has dimension larger than `max_dim` or `blocker_func` - returns `True` for this simplex. - - The function identifies a candidate simplex whose faces are all already in the complex, inserts it with a - filtration value corresponding to the maximum of the filtration values of the faces, then calls `blocker_func` - with this new simplex (represented as a list of int). If `blocker_func` returns `True`, the simplex is removed, - otherwise it is kept. The algorithm then proceeds with the next candidate. - - .. warning:: - Several candidates of the same dimension may be inserted simultaneously before calling `blocker_func`, so - if you examine the complex in `blocker_func`, you may hit a few simplices of the same dimension that have - not been vetted by `blocker_func` yet, or have already been rejected but not yet removed. - - :param max_dim: Expansion maximal dimension value. - :type max_dim: int - :param blocker_func: Blocker oracle. - :type blocker_func: Callable[[List[int]], bool] - """ - self.get_ptr().expansion_with_blockers_callback(max_dim, callback, blocker_func) + # TODO : cython3 + # def expansion_with_blocker(self, max_dim, blocker_func): + # """Expands the Simplex_tree containing only a graph. Simplices corresponding to cliques in the graph are added + # incrementally, faces before cofaces, unless the simplex has dimension larger than `max_dim` or `blocker_func` + # returns `True` for this simplex. + + # The function identifies a candidate simplex whose faces are all already in the complex, inserts it with a + # filtration value corresponding to the maximum of the filtration values of the faces, then calls `blocker_func` + # with this new simplex (represented as a list of int). If `blocker_func` returns `True`, the simplex is removed, + # otherwise it is kept. The algorithm then proceeds with the next candidate. + + # .. warning:: + # Several candidates of the same dimension may be inserted simultaneously before calling `blocker_func`, so + # if you examine the complex in `blocker_func`, you may hit a few simplices of the same dimension that have + # not been vetted by `blocker_func` yet, or have already been rejected but not yet removed. + + # :param max_dim: Expansion maximal dimension value. + # :type max_dim: int + # :param blocker_func: Blocker oracle. + # :type blocker_func: Callable[[List[int]], bool] + # """ + # self.get_ptr().expansion_with_blockers_callback(max_dim, callback, blocker_func) # def persistence(self, homology_coeff_field=11, min_persistence=0, persistence_dim_max = False): # """This function computes and returns the persistence of the simplicial complex. @@ -665,12 +695,12 @@ cdef class SimplexTreeMulti: ## This function is only meant for the edge collapse interface. def get_edge_list(self): - cdef edge_list out; + cdef edge_list out with nogil: out = self.get_ptr().get_edge_list() return out - def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False): + def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False)->SimplexTreeMulti: """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574). It uses the code from the github repository https://github.com/aj-alonso/filtration_domination . @@ -697,104 +727,60 @@ cdef class SimplexTreeMulti: A (smaller) simplex tree that has the same homology over this bifiltration. """ - from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated # TODO : find a way to do multiple edge collapses without python conversions. - assert self.num_parameters == 2 - if self.dimension() > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !") - from tqdm import tqdm if num <= 0: return self - max_dimension = self.dimension() if max_dimension is None else max_dimension - # edge_list = std::vector, std::pair>> - # cdef vector[pair[pair[int,int],pair[value_type,value_type]]] - edges = self.get_edge_list() - # cdef int n = edges.size() - n = len(edges) - if full: - num = 100 - with tqdm(range(num), total=num, desc="Removing edges", disable=not(progress)) as I: - for i in I: - if strong: - edges = remove_strongly_filtration_dominated(edges) # nogil ? - else: - edges = remove_filtration_dominated(edges) - # Prevents doing useless collapses - if len(edges) >= n: - if full and strong: - strong = False - n = len(edges) - # n = edges.size() # len(edges) - else : - break - else: - n = len(edges) - # n = edges.size() - - reduced_tree = SimplexTreeMulti(num_parameters=self.num_parameters) + assert self.num_parameters == 2, "Number of parameters has to be 2 to use edge collapses ! This is a limitation of Filtration-domination" + if self.dimension > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !") + max_dimension = self.dimension if max_dimension is None else max_dimension + + # Retrieves the edge list, and send it to filration_domination + edges = self.get_edge_list() + from multipers.edge_collapse import _collapse_edge_list + edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress) + # Retrieves the collapsed simplicial complex + self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension) + return self + def _reconstruct_from_edge_list(self, edges, swap:bool=True, expand_dimension:int=None)->SimplexTreeMulti: + """ + Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses + + Input + ----- + - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination + - swap : bool + If true, will swap self and the collapsed simplextrees. + - expand_dim : int + expands back the simplextree to this dimension + Ouput + ----- + The reduced SimplexTreeMulti having only these edges. + """ + reduced_tree = SimplexTreeMulti(num_parameters=self.num_parameters) + ## Adds vertices back, with good filtration - vertices = np.array([splx for splx, f in self.get_skeleton(0)], dtype=int).T - vertices_filtration = np.array([f for splx, f in self.get_skeleton(0)], dtype=float) - reduced_tree.insert_batch(vertices, vertices_filtration) + if self.num_vertices > 0: + vertices = np.asarray([splx for splx, f in self.get_skeleton(0)], dtype=int).T + vertices_filtration = np.asarray([f for splx, f in self.get_skeleton(0)], dtype=np.float32) + reduced_tree.insert_batch(vertices, vertices_filtration) ## Adds edges again - edges_filtration = np.asarray([f for e,f in edges], dtype=float) - edges = np.asarray([e for e, _ in edges], dtype=int).T - reduced_tree.insert_batch(edges, edges_filtration) - self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable) - self.expansion(max_dimension) # Expands back the simplextree to the original dimension. - # self.make_filtration_non_decreasing(2) - return self - - def to_rivet(self, path="rivet_dataset.txt", degree:int = 1, progress:bool=False, overwrite:bool=False, xbins:int=0, ybins:int=0)->None: - """ Create a file that can be imported by rivet, representing the filtration of the simplextree. - - Parameters - ---------- - path:str - path of the file. - degree:int - The homological degree to ask rivet to compute. - progress:bool = True - Shows the progress bar. - overwrite:bool = False - If true, will overwrite the previous file if it already exists. - Returns - ------- - Nothing - """ - from os.path import exists - from os import remove - if exists(path): - if not(overwrite): - raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") - remove(path) - file = open(path, "a") - file.write("--datatype bifiltration\n") - file.write(f"--homology {degree}\n") - file.write(f"-x {xbins}\n") - file.write(f"-y {ybins}\n") - file.write("--xlabel time of appearance\n") - file.write("--ylabel density\n\n") - from tqdm import tqdm - with tqdm(total=self.num_simplices(), position=0, disable = not(progress), desc="Writing simplex to file") as bar: - for dim in range(0,self.dimension()+1): # Not sure if dimension sort is necessary for rivet. Check ? - for s,F in self.get_skeleton(dim): - if len(s) != dim+1: continue - for i in s: - file.write(str(i) + " ") - file.write("; ") - for f in F: - file.write(str(f) + " ") - file.write("\n") - bar.update(1) - file.close() - return + if self.num_simplices - self.num_vertices > 0: + edges_filtration = np.asarray([f for e,f in edges], dtype=np.float32) + edges = np.asarray([e for e, _ in edges], dtype=int).T + reduced_tree.insert_batch(edges, edges_filtration) + if swap: + # Swaps the simplextrees pointers + self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable) + if expand_dimension is not None: + self.expansion(expand_dimension) # Expands back the simplextree to the original dimension. + return self if swap else reduced_tree @property def num_parameters(self)->int: return self.get_ptr().get_number_of_parameters() - def get_simplices_of_dimension(self, dim:int): + def get_simplices_of_dimension(self, dim:int)->np.ndarray: return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int) def key(self, simplex:list|np.ndarray): return self.get_ptr().get_key(simplex) @@ -852,7 +838,7 @@ cdef class SimplexTreeMulti: file.write(f"{num_parameters}\n") if not strip_comments: file.write("# Sizes of generating sets\n") ## WRITES TSR VARIABLES - tsr:list= [0]*(self.dimension()+1) # dimension --- 0 + tsr:list= [0]*(self.dimension+1) # dimension --- 0 for splx,f in self.get_simplices(): dim = len(splx)-1 tsr[dim] += (int)(len(f) // num_parameters) @@ -862,7 +848,7 @@ cdef class SimplexTreeMulti: ## Adds the boundaries to the dictionnary + tsr dict_splx_to_firep_number = {} tsr:list = [[] for _ in range(len(tsr))] # tsr stores simplices vertices, according to dimension, and the dictionnary - for dim in range(self.dimension(),-1 , -1): # range(2,-1,-1): + for dim in range(self.dimension,-1 , -1): # range(2,-1,-1): for splx,F in self.get_skeleton(dim): if len(splx) != dim+1: continue for b,_ in self.get_boundaries(splx): @@ -875,7 +861,7 @@ cdef class SimplexTreeMulti: if not self.key(splx) in dict_splx_to_firep_number: tsr[len(splx)-1].append(splx) ## Writes simplices of tsr to file - dim_range = range(self.dimension(),0,-1) if ignore_last_generators else range(self.dimension(),-1,-1) + dim_range = range(self.dimension,0,-1) if ignore_last_generators else range(self.dimension,-1,-1) for dim in dim_range: # writes block by block if not strip_comments: file.write(f"# Block of dimension {dim}\n") if reverse_block: tsr[dim].reverse() @@ -891,16 +877,121 @@ cdef class SimplexTreeMulti: file.write("\n") file.close() return + + def to_rivet(self, path="rivet_dataset.txt", degree:int|None = None, progress:bool=False, overwrite:bool=False, xbins:int|None=None, ybins:int|None=None)->None: + """ Create a file that can be imported by rivet, representing the filtration of the simplextree. + + Parameters + ---------- + path:str + path of the file. + degree:int + The homological degree to ask rivet to compute. + progress:bool = True + Shows the progress bar. + overwrite:bool = False + If true, will overwrite the previous file if it already exists. + Returns + ------- + Nothing + """ + ... + from os.path import exists + from os import remove + if exists(path): + if not(overwrite): + print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.") + return + remove(path) + file = open(path, "a") + file.write("--datatype bifiltration\n") + file.write(f"--homology {degree}\n") if degree is not None else None + file.write(f"-x {xbins}\n") if xbins is not None else None + file.write(f"-y {ybins}\n") if ybins is not None else None + file.write("--xlabel time of appearance\n") + file.write("--ylabel density\n\n") + from tqdm import tqdm + with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar: + for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ? + file.write(f"# block of dimension {dim}\n") + for s,F in self.get_skeleton(dim): + if len(s) != dim+1: continue + for i in s: + file.write(str(i) + " ") + file.write("; ") + for f in F: + file.write(str(f) + " ") + file.write("\n") + bar.update(1) + file.close() + return - def _get_filtration_values(self, degrees:Iterable[int]): - cdef c_degrees = degrees + def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False)->Iterable[np.ndarray]: + # cdef vector[int] c_degrees = degrees cdef intptr_t ptr = self.thisptr - cdef vector[vector[vector[value_type]]] out = get_filtration_values(ptr, c_degrees) - return [np.asarray(filtration, dtype=float) for filtration in out] + cdef vector[vector[vector[value_type]]] out + with nogil: + out = get_filtration_values(ptr, degrees) + filtrations_values = [np.asarray(filtration) for filtration in out] + # Removes infs + if inf_to_nan: + for i,f in enumerate(filtrations_values): + filtrations_values[i][f == np.inf] = np.nan + filtrations_values[i][f == - np.inf] = np.nan + return filtrations_values - def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, q:float=0.01, grid_strategy:str="regular"): + @staticmethod + def _reduce_grid(filtrations_values,resolutions=None, strategy:str="exact", bool unique=True, some_float _q_factor=1., drop_quantiles=[0,0]): + num_parameters = len(filtrations_values) + if resolutions is None and strategy not in ["exact", "precomputed"]: + raise ValueError("Resolutions must be provided for this strategy.") + elif resolutions is not None: + try: + int(resolutions) + resolutions = [resolutions]*num_parameters + except: + pass + try: + a,b=drop_quantiles + except: + a,b=drop_quantiles,drop_quantiles + + if a != 0 or b != 0: + boxes = np.asarray([np.quantile(filtration, [a, b], axis=1, method='closest_observation') for filtration in filtrations_values]) + min_filtration, max_filtration = np.min(boxes, axis=(0,1)), np.max(boxes, axis=(0,1)) # box, birth/death, filtration + filtrations_values = [ + filtration[(m np.asarray([len(f) for f in F])): + return SimplexTreeMulti._reduce_grid(filtrations_values=filtrations_values, resolutions=resolutions, strategy="quantile",_q_factor=1.5*_q_factor) + elif strategy == "regular": + F = [np.linspace(f.min(),f.max(),num=r) for f,r in zip(filtrations_values, resolutions)] + elif strategy == "regular_closest": + + F = [_todo_regular_closest(f,r, unique) for f,r in zip(filtrations_values, resolutions)] + elif strategy == "partition": + F = [_todo_partition(f,r, unique) for f,r in zip(filtrations_values, resolutions)] + elif strategy == "precomputed": + F=filtrations_values + else: + raise Exception("Invalid strategy. Pick either regular, regular_closest, partition, quantile, precomputed or exact.") + + return F + + def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:str="exact")->Iterable[np.ndarray]: """ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical @@ -912,35 +1003,27 @@ cdef class SimplexTreeMulti: Grid bounds. format : [low bound, high bound] If None is given, will use the filtration bounds of the simplextree. grid_strategy="regular" : string - Either "regular" or "quantile". + Either "regular", "quantile", or "exact". Returns ------- List of filtration values, for each parameter, defining the grid. """ - if resolution is None: - resolution = [50]*len(self.num_parameters) if degrees is None: - degrees = range(self.dimension()+1) - - if grid_strategy == "quantile": - filtrations_values = np.concatenate(self._get_filtration_values(degrees), axis=1) - filtration_grid = [ - np.quantile(filtration, np.linspace(0,1,num=res)) - for filtration, res in zip(filtrations_values, resolution) - ] - return filtration_grid + degrees = range(self.dimension+1) - box = self.filtration_bounds(degrees = degrees, q=q, split_dimension=False) - assert(len(box[0]) == len(box[1]) == len(resolution) == self.num_parameters, f"Number of parameter not concistent. box: {len(box[0])}, resolution:{len(resolution)}, simplex tree:{self.num_parameters}") - - if grid_strategy == "regular": - return [np.linspace(*np.asarray(box)[:,i], num=resolution[i]) for i in range(self.num_parameters)] + + ## preprocesses the filtration values: + filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1) + # removes duplicate + sort (nan at the end) + filtrations_values = [np.unique(filtration) for filtration in filtrations_values] + # removes nan + filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values] - warn("Invalid grid strategy. Available ones are regular, and (todo) quantile") - return + return self._reduce_grid(filtrations_values=filtrations_values, resolutions=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles) + - def grid_squeeze(self, filtration_grid:np.ndarray|list, coordinate_values:bool=False): + def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, coordinate_values:bool=True, force=False, **filtration_grid_kwargs)->SimplexTreeMulti: """ Fit the filtration of the simplextree to a grid. @@ -949,30 +1032,40 @@ cdef class SimplexTreeMulti: :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid. :type coordinate_values: bool """ + if not force and self._is_squeezed: + raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.") #TODO : multi-critical - + if filtration_grid is None: filtration_grid = self.get_filtration_grid(**filtration_grid_kwargs) cdef vector[vector[value_type]] c_filtration_grid = filtration_grid cdef intptr_t ptr = self.thisptr cdef bool c_coordinate_values = coordinate_values + self.filtration_grid = c_filtration_grid with nogil: squeeze_filtration(ptr, c_filtration_grid, c_coordinate_values) return self - def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float=0, split_dimension:bool=False): + @property + def _is_squeezed(self)->bool: + return self.num_vertices > 0 and self.filtration_grid[0].size() > 0 + + def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray: """ - Returns the filtrations bounds. + Returns the filtrations bounds of the finite filtration values. """ - assert 0<= q <= 1 - degrees = range(self.dimension()+1) if degrees is None else degrees - filtrations_values = self._get_filtration_values(degrees) ## degree, parameter, pt - boxes = np.array([np.quantile(filtration, [q, 1-q], axis=1) for filtration in filtrations_values],dtype=float) + try: + a,b =q + except: + a,b,=q,q + degrees = range(self.dimension+1) if degrees is None else degrees + filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt + boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float) if split_dimension: return boxes - return np.asarray([np.min(boxes, axis=(0,1)), np.max(boxes, axis=(0,1))]) # box, birth/death, filtration + return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration - def fill_lowerstar(self, F, parameter:int): + def fill_lowerstar(self, F, parameter:int)->SimplexTreeMulti: """ Fills the `dimension`th filtration by the lower-star filtration defined by F. Parameters @@ -988,11 +1081,14 @@ cdef class SimplexTreeMulti: """ # for s, sf in self.get_simplices(): # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)]) - self.get_ptr().fill_lowerstar(F, parameter) + cdef int c_parameter = parameter + cdef vector[value_type] c_F = F + with nogil: + self.get_ptr().fill_lowerstar(c_F, c_parameter) return self - def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None): + def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None)->SimplexTree: """Converts an multi simplextree to a gudhi simplextree. Parameters ---------- @@ -1017,12 +1113,47 @@ cdef class SimplexTreeMulti: cdef vector[value_type] c_basepoint = [] if basepoint is None else basepoint if basepoint is None: with nogil: - flatten(old_ptr, new_ptr, c_parameter) + flatten_from_ptr(old_ptr, new_ptr, c_parameter) else: with nogil: - flatten_diag(old_ptr, new_ptr, c_basepoint, c_parameter) + flatten_diag_from_ptr(old_ptr, new_ptr, c_basepoint, c_parameter) return new_simplextree + def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]: + """ + Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree. + + Input + ----- + - Array of shape (num_linear_forms, num_parameters) + + Output + ------ + - List of projected (gudhi) simplextrees. + """ + cdef Py_ssize_t num_projections = linear_forms.shape[0] + cdef Py_ssize_t num_parameters = linear_forms.shape[1] + if num_projections == 0: return [] + cdef vector[vector[value_type]] c_linear_forms = linear_forms + assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})." + + # Gudhi copies are faster than inserting simplices one by one + import gudhi as gd + flattened_simplextree = gd.SimplexTree() + cdef intptr_t multi_prt = self.thisptr + cdef intptr_t flattened_ptr = flattened_simplextree.thisptr + with nogil: + flatten_from_ptr(multi_prt, flattened_ptr, num_parameters) + out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)] + + # Fills the 1-parameter simplextrees. + cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out] + with nogil: + for i in range(num_projections): + linear_projection_from_ptr(out_ptrs[i], multi_prt, c_linear_forms[i]) + return out + + def set_num_parameter(self, num:int): """ Sets the numbers of parameters. @@ -1039,39 +1170,52 @@ cdef class SimplexTreeMulti: """ return dereference(self.get_ptr()) == dereference(other.get_ptr()) - def euler_char(self, points:np.ndarray|list) -> np.ndarray: - """ Computes the Euler Characteristic of the filtered complex at given (multiparameter) time - - Parameters - ---------- - points: 2-dimensional array. - List of filtration values on which to compute the euler characteristic. - - Returns - ------- - The list of euler characteristic values - """ - # TODO : muticritical, compute it from a coordinate simplextree, tbb. -# if len(points) == 0: -# return cnp.empty() -# if type(points[0]) is float: -# points = cnp.array([points]) -# if type(points) is cnp.ndarray: -# assert len(points.shape) in [1,2] -# if len(points.shape) == 1: -# points = [points] - c_points = np.asarray(points) - assert c_points.shape[1] == self.num_parameters - return np.asarray(self.get_ptr().euler_char(c_points)) - +# def euler_char(self, points:np.ndarray|list) -> np.ndarray: +# """ Computes the Euler Characteristic of the filtered complex at given (multiparameter) time + +# Parameters +# ---------- +# points: 2-dimensional array. +# List of filtration values on which to compute the euler characteristic. + +# Returns +# ------- +# The list of euler characteristic values +# """ +# # TODO : muticritical, compute it from a coordinate simplextree, tbb. +# # if len(points) == 0: +# # return cnp.empty() +# # if type(points[0]) is float: +# # points = cnp.array([points]) +# # if type(points) is cnp.ndarray: +# # assert len(points.shape) in [1,2] +# # if len(points.shape) == 1: +# # points = [points] +# c_points = np.asarray(points) +# assert c_points.shape[1] == self.num_parameters +# return np.asarray(self.get_ptr().euler_char(c_points)) cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil: return (new Simplex_tree_multi_interface(dereference(stree.get_ptr()))) +def _todo_regular_closest(cnp.ndarray[some_float,ndim=1] f, int r, bool unique): + f_regular = np.linspace(np.min(f),np.max(f),num=r) + f_regular_closest = np.asarray([f[np.argmin(np.abs(f-x))] for x in f_regular]) + if unique: f_regular_closest = np.unique(f_regular_closest) + return f_regular_closest + +def _todo_partition(cnp.ndarray[some_float,ndim=1] data,int resolution, bool unique): + if data.shape[0] < resolution: resolution=data.shape[0] + k = data.shape[0] // resolution + partitions = np.partition(data, k) + f = partitions[[i*k for i in range(resolution)]] + if unique: f= np.unique(f) + return f + -def _simplextree_multify(simplextree:SimplexTree, num_parameters:int=2)->SimplexTreeMulti: +def _simplextree_multify(simplextree:SimplexTree, num_parameters:int=2, default_values=[])->SimplexTreeMulti: """Converts a gudhi simplextree to a multi simplextree. Parameters ---------- @@ -1087,6 +1231,7 @@ def _simplextree_multify(simplextree:SimplexTree, num_parameters:int=2)->Simplex cdef int c_num_parameters = num_parameters cdef intptr_t old_ptr = simplextree.thisptr cdef intptr_t new_ptr = st.thisptr + cdef vector[value_type] c_default_values=default_values with nogil: - multify(old_ptr, new_ptr, c_num_parameters) - return st + multify_from_ptr(old_ptr, new_ptr, c_num_parameters, c_default_values) + return \ No newline at end of file diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index ac4354fe91..500d5f55c6 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -9,14 +9,13 @@ * - YYYY/MM Author: Description of the modification */ -#ifndef INCLUDE_SIMPLEX_TREE_INTERFACE_H_ -#define INCLUDE_SIMPLEX_TREE_INTERFACE_H_ +#pragma once //#include //#include //#include //#include -#include +#include "Simplex_tree_interface.h" #include "Simplex_tree_multi.h" // #include #include "multi_filtrations/finitely_critical_filtrations.h" @@ -26,11 +25,12 @@ #include // std::pair #include #include // for std::distance +#include namespace Gudhi { -template -class Simplex_tree_interface : public Simplex_tree { +template +class Simplex_tree_interface_multi : public Simplex_tree_interface { public: using Python_filtration_type = std::vector; // TODO : std::conditional using Base = Simplex_tree; @@ -39,7 +39,7 @@ class Simplex_tree_interface : public Simplex_tree { using Simplex_handle = typename Base::Simplex_handle; using Insertion_result = typename std::pair; using Simplex = std::vector; - using Simplex_and_filtration = std::pair; + using Simplex_and_filtration = std::pair; using Filtered_simplices = std::vector; using Skeleton_simplex_iterator = typename Base::Skeleton_simplex_iterator; using Complex_simplex_iterator = typename Base::Complex_simplex_iterator; @@ -52,16 +52,21 @@ class Simplex_tree_interface : public Simplex_tree { Extended_filtration_data efd; - bool find_simplex(const Simplex& simplex) { - return (Base::find(simplex) != Base::null_simplex()); - } +// bool find_simplex(const Simplex& simplex) { +// return (Base::find(simplex) != Base::null_simplex()); +// } - void assign_simplex_filtration(const Simplex& simplex, Filtration_value filtration) { + void assign_simplex_filtration(const Simplex& simplex, const Filtration_value& filtration) { Base::assign_filtration(Base::find(simplex), filtration); Base::clear_filtration(); } +// void assign_simplex_filtration(const Simplex& simplex, const Python_filtration_type& filtration) { +// Filtration_value& filtration_ = *(Filtration_value*)(&filtration); // Jardinage for no copy. +// Base::assign_filtration(Base::find(simplex), filtration_); +// Base::clear_filtration(); +// } - bool insert(const Simplex& simplex, Filtration_value filtration = 0) { + bool insert(const Simplex& simplex, const Filtration_value& filtration ) { Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); if (result.first != Base::null_simplex()) Base::clear_filtration(); @@ -69,44 +74,53 @@ class Simplex_tree_interface : public Simplex_tree { } // Do not interface this function, only used in alpha complex interface for complex creation - bool insert_simplex(const Simplex& simplex, Filtration_value filtration = 0) { + bool insert_simplex(const Simplex& simplex, const Filtration_value& filtration ) { Insertion_result result = Base::insert_simplex(simplex, filtration); return (result.second); } +// bool insert_simplex(const Simplex& simplex, const Python_filtration_type& filtration ) { +// Filtration_value& filtration_ = *(Filtration_value*)(&filtration); // Jardinage for no copy. +// Insertion_result result = Base::insert_simplex(simplex, filtration); +// return (result.second); +// } // Do not interface this function, only used in interface for complex creation - bool insert_simplex_and_subfaces(const Simplex& simplex, Filtration_value filtration = 0) { + bool insert_simplex_and_subfaces(const Simplex& simplex, const Filtration_value& filtration ) { Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); return (result.second); } +// bool insert_simplex_and_subfaces(const Simplex& simplex, const Python_filtration_type& filtration ) { +// Filtration_value& filtration_ = *(Filtration_value*)(&filtration); // Jardinage for no copy. +// Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); +// return (result.second); +// } // Do not interface this function, only used in strong witness interface for complex creation - bool insert_simplex(const std::vector& simplex, Filtration_value filtration = 0) { + bool insert_simplex(const std::vector& simplex, const Filtration_value& filtration ) { Insertion_result result = Base::insert_simplex(simplex, filtration); return (result.second); } // Do not interface this function, only used in strong witness interface for complex creation - bool insert_simplex_and_subfaces(const std::vector& simplex, Filtration_value filtration = 0) { + bool insert_simplex_and_subfaces(const std::vector& simplex, const Filtration_value& filtration) { Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); return (result.second); } - - Python_filtration_type simplex_filtration(const Simplex& simplex) { - return Base::filtration(Base::find(simplex)); + typename SimplexTreeOptions::value_type* simplex_filtration(const Simplex& simplex) { + auto& filtration = Base::filtration(Base::find(simplex)); + return &filtration[0]; } - void remove_maximal_simplex(const Simplex& simplex) { - Base::remove_maximal_simplex(Base::find(simplex)); - Base::clear_filtration(); - } Simplex_and_filtration get_simplex_and_filtration(Simplex_handle f_simplex) { - Simplex simplex; - for (auto vertex : Base::simplex_vertex_range(f_simplex)) { - simplex.insert(simplex.begin(), vertex); - } - return std::make_pair(std::move(simplex), Base::filtration(f_simplex)); + // Simplex simplex; + // for (auto vertex : Base::simplex_vertex_range(f_simplex)) { + // // simplex.insert(simplex.begin(), vertex); // why not push back ? + // } + auto it = Base::simplex_vertex_range(f_simplex); + Simplex simplex(it.begin(), it.end()); + std::reverse(simplex.begin(), simplex.end()); + return std::make_pair(std::move(simplex), &Base::filtration(f_simplex)[0]); } Filtered_simplices get_star(const Simplex& simplex) { @@ -116,7 +130,7 @@ class Simplex_tree_interface : public Simplex_tree { for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex_star.insert(simplex_star.begin(), vertex); } - star.push_back(std::make_pair(simplex_star, Base::filtration(f_simplex))); + star.push_back(std::make_pair(simplex_star, &Base::filtration(f_simplex)[0])); } return star; } @@ -128,90 +142,17 @@ class Simplex_tree_interface : public Simplex_tree { for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex_coface.insert(simplex_coface.begin(), vertex); } - cofaces.push_back(std::make_pair(simplex_coface, Base::filtration(f_simplex))); + cofaces.push_back(std::make_pair(simplex_coface, &Base::filtration(f_simplex)[0])); } return cofaces; } void compute_extended_filtration() { - this->efd = this->extend_filtration(); - return; - } - -/* Simplex_tree_interface* collapse_edges(int nb_collapse_iteration) {*/ -/* using Filtered_edge = std::tuple;*/ -/* std::vector edges;*/ -/* for (Simplex_handle sh : Base::skeleton_simplex_range(1)) {*/ -/* if (Base::dimension(sh) == 1) {*/ -/* typename Base::Simplex_vertex_range rg = Base::simplex_vertex_range(sh);*/ -/* auto vit = rg.begin();*/ -/* Vertex_handle v = *vit;*/ -/* Vertex_handle w = *++vit;*/ -/* edges.emplace_back(v, w, Base::filtration(sh));*/ -/* }*/ -/* }*/ - -/* for (int iteration = 0; iteration < nb_collapse_iteration; iteration++) {*/ -/* edges = Gudhi::collapse::flag_complex_collapse_edges(std::move(edges));*/ -/* }*/ -/* Simplex_tree_interface* collapsed_stree_ptr = new Simplex_tree_interface();*/ -/* // Copy the original 0-skeleton*/ -/* for (Simplex_handle sh : Base::skeleton_simplex_range(0)) {*/ -/* collapsed_stree_ptr->insert({*(Base::simplex_vertex_range(sh).begin())}, Base::filtration(sh));*/ -/* }*/ -/* // Insert remaining edges*/ -/* for (auto remaining_edge : edges) {*/ -/* collapsed_stree_ptr->insert({std::get<0>(remaining_edge), std::get<1>(remaining_edge)}, std::get<2>(remaining_edge));*/ -/* }*/ -/* return collapsed_stree_ptr;*/ -/* }*/ - - void expansion_with_blockers_callback(int dimension, blocker_func_t user_func, void *user_data) { - Base::expansion_with_blockers(dimension, [&](Simplex_handle sh){ - Simplex simplex(Base::simplex_vertex_range(sh).begin(), Base::simplex_vertex_range(sh).end()); - return user_func(simplex, user_data); - }); - } - - // Iterator over the simplex tree - Complex_simplex_iterator get_simplices_iterator_begin() { - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - return Base::complex_simplex_range().begin(); - } - - Complex_simplex_iterator get_simplices_iterator_end() { - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - return Base::complex_simplex_range().end(); - } - - typename std::vector::const_iterator get_filtration_iterator_begin() { - // Base::initialize_filtration(); already performed in filtration_simplex_range - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - return Base::filtration_simplex_range().begin(); - } - - typename std::vector::const_iterator get_filtration_iterator_end() { - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - return Base::filtration_simplex_range().end(); - } - - Skeleton_simplex_iterator get_skeleton_iterator_begin(int dimension) { - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - return Base::skeleton_simplex_range(dimension).begin(); - } - - Skeleton_simplex_iterator get_skeleton_iterator_end(int dimension) { - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - return Base::skeleton_simplex_range(dimension).end(); + throw std::logic_error("Incompatible with multipers"); } - std::pair get_boundary_iterators(const Simplex& simplex) { - auto bd_sh = Base::find(simplex); - if (bd_sh == Base::null_simplex()) - throw std::runtime_error("simplex not found - cannot find boundaries"); - // this specific case works because the range is just a pair of iterators - won't work if range was a vector - auto boundary_srange = Base::boundary_simplex_range(bd_sh); - return std::make_pair(boundary_srange.begin(), boundary_srange.end()); + Simplex_tree_interface_multi* collapse_edges(int nb_collapse_iteration) { + throw std::logic_error("Incompatible with multipers"); } @@ -227,11 +168,11 @@ class Simplex_tree_interface : public Simplex_tree { return Base::key(Base::find(simplex)); } - void set_key(Simplex& simplex, int key){ + void set_key(const Simplex& simplex, int key){ Base::assign_key(Base::find(simplex), key); return; } - void fill_lowerstar(std::vector filtration, int axis){ + void fill_lowerstar(const std::vector& filtration, int axis){ using value_type=options_multi::value_type; for (auto &SimplexHandle : Base::complex_simplex_range()){ std::vector current_birth = Base::filtration(SimplexHandle); @@ -247,7 +188,7 @@ class Simplex_tree_interface : public Simplex_tree { simplices_list get_simplices_of_dimension(int dimension){ simplices_list simplex_list; simplex_list.reserve(Base::num_simplices()); - for (auto &simplexhandle : Base::skeleton_simplex_range(dimension)){ + for (auto simplexhandle : Base::skeleton_simplex_range(dimension)){ if (Base::dimension(simplexhandle) == dimension){ std::vector simplex; simplex.reserve(dimension+1); @@ -276,39 +217,39 @@ class Simplex_tree_interface : public Simplex_tree { return simplex_list; } // DEPRECATED, USE COORDINATE SIMPLEX TREE - euler_chars_type euler_char(std::vector> &point_list){ // TODO multi-critical - const int npts = point_list.size(); - if (npts == 0){ - return {}; - } - using Gudhi::multi_filtrations::Finitely_critical_multi_filtration; +// euler_chars_type euler_char(const std::vector> &point_list){ // TODO multi-critical +// const int npts = point_list.size(); +// if (npts == 0){ +// return {}; +// } +// using Gudhi::multi_filtrations::Finitely_critical_multi_filtration; - euler_chars_type out(point_list.size(), 0.); - - // auto is_greater = [nparameters](const point_type &a, const point_type &b){ //french greater - // for (int i = 0; i< nparameters; i++) - // if( a[i] < b[i]) - // return false; - // return true; - // }; -// #pragma omp parallel for - for (int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel - auto &euler_char_at_point = out[i]; -// #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR - for(const auto &SimplexHandle : Base::complex_simplex_range()){ - // const Finitely_critical_multi_filtration &pt = *(Finitely_critical_multi_filtration*)(&point_list[i]); - options_multi::Filtration_value filtration = Base::filtration(SimplexHandle); - // const Finitely_critical_multi_filtration &filtration = *(Finitely_critical_multi_filtration*)(&filtration_); - Finitely_critical_multi_filtration pt(point_list[i]); - // if (is_greater(pt, filtration)){ - if (filtration <= pt){ - int sign = Base::dimension(SimplexHandle) %2 ? -1 : 1; - euler_char_at_point += sign; - } - } - } - return out; - } +// euler_chars_type out(point_list.size(), 0.); + +// // auto is_greater = [nparameters](const point_type &a, const point_type &b){ //french greater +// // for (int i = 0; i< nparameters; i++) +// // if( a[i] < b[i]) +// // return false; +// // return true; +// // }; +// // #pragma omp parallel for +// for (int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel +// auto &euler_char_at_point = out[i]; +// // #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR +// for(const auto &SimplexHandle : Base::complex_simplex_range()){ +// // const Finitely_critical_multi_filtration &pt = *(Finitely_critical_multi_filtration*)(&point_list[i]); +// options_multi::Filtration_value filtration = Base::filtration(SimplexHandle); +// // const Finitely_critical_multi_filtration &filtration = *(Finitely_critical_multi_filtration*)(&filtration_); +// Finitely_critical_multi_filtration pt(point_list[i]); +// // if (is_greater(pt, filtration)){ +// if (filtration <= pt){ +// int sign = Base::dimension(SimplexHandle) %2 ? -1 : 1; +// euler_char_at_point += sign; +// } +// } +// } +// return out; +// } void resize_all_filtrations(int num){ //TODO : that is for 1 critical filtrations if (num < 0) return; for(const auto &SimplexHandle : Base::complex_simplex_range()){ @@ -317,10 +258,37 @@ class Simplex_tree_interface : public Simplex_tree { Base::assign_filtration(SimplexHandle, new_filtration_value); } } + }; +using interface_std = Simplex_tree_interface; +using interface_multi = Simplex_tree_interface_multi; + + +void flatten_diag_from_ptr(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ // for python + auto &st = get_simplextree_from_pointer(newsplxptr); + auto &st_multi = get_simplextree_from_pointer(splxptr); + flatten_diag(st,st_multi,basepoint, dimension); +} +void multify_from_ptr(uintptr_t splxptr,uintptr_t newsplxptr, const int dimension, const multi_filtration_type& default_values){ //for python + auto &st = get_simplextree_from_pointer(splxptr); + auto &st_multi = get_simplextree_from_pointer(newsplxptr); + multify(st, st_multi, dimension, default_values); +} +void flatten_from_ptr(uintptr_t splxptr, uintptr_t newsplxptr, const int dimension = 0){ // for python + auto &st = get_simplextree_from_pointer(newsplxptr); + auto &st_multi = get_simplextree_from_pointer(splxptr); + flatten(st, st_multi, dimension); +} +template +void linear_projection_from_ptr(const uintptr_t ptr, const uintptr_t ptr_multi, Args...args){ + auto &st = get_simplextree_from_pointer(ptr); + auto &st_multi = get_simplextree_from_pointer(ptr_multi); + linear_projection(st, st_multi, args...); +} + + } // namespace Gudhi -#endif // INCLUDE_SIMPLEX_TREE_INTERFACE_H_ diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index 2a6c5b7a71..5a9adb261b 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -38,6 +38,8 @@ struct Simplex_tree_options_multidimensional_filtration { static const bool store_key = true; static const bool store_filtration = true; static const bool contiguous_vertices = false; + static const bool link_nodes_by_label = true; + static const bool stable_simplex_handles = false; static const bool is_multi_parameter = true; }; @@ -45,86 +47,120 @@ struct Simplex_tree_options_multidimensional_filtration { using options_multi = Simplex_tree_options_multidimensional_filtration; using options_std = Simplex_tree_options_full_featured; +using simplextree_std = Simplex_tree; +using simplextree_multi = Simplex_tree; + using multi_filtration_type = std::vector; using multi_filtration_grid = std::vector; -template -Simplex_tree& get_simplextree_from_pointer(const uintptr_t splxptr){ //DANGER - Simplex_tree &st = *(Gudhi::Simplex_tree*)(splxptr); +template +simplextreeinterface& get_simplextree_from_pointer(const uintptr_t splxptr){ //DANGER + simplextreeinterface &st = *(simplextreeinterface*)(splxptr); return st; } -template -void multify(Simplex_tree<_options_std> &st, Simplex_tree<_options_multi> &st_multi, const int dimension){ - if (dimension <= 0) +template +void multify(simplextree_std &st, simplextree_multi &st_multi, const int num_parameters, const typename simplextree_multi::Options::Filtration_value& default_values={}){ + if (num_parameters <= 0) {std::cerr << "Empty filtration\n"; throw ;} - typename _options_multi::Filtration_value f(dimension); + // if (default_values.size() -1 > num_parameters) + // {std::cerr << "default values too large !\n"; throw ;} + typename simplextree_multi::Options::Filtration_value f(num_parameters); + for (auto i = 0u; i(default_values.size()), static_cast(num_parameters-1));i++) + f[i+1] = default_values[i]; + std::vector simplex; + simplex.reserve(st.dimension()+1); for (auto &simplex_handle : st.complex_simplex_range()){ - std::vector simplex; + simplex.clear(); for (auto vertex : st.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); f[0] = st.filtration(simplex_handle); + // std::cout << "Inserting " << f << "\n"; st_multi.insert_simplex(simplex,f); + } } -void multify(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension){ //for python - auto &st = get_simplextree_from_pointer(splxptr); - auto &st_multi = get_simplextree_from_pointer(newsplxptr); - multify(st, st_multi, dimension); -} -template -void flatten(Simplex_tree<_options_std> &st, Simplex_tree<_options_multi> &st_multi, const int dimension = 0){ + + +template +void flatten(simplextree_std &st, simplextree_multi &st_multi, const int dimension = 0){ for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - typename _options_multi::value_type f = dimension >= 0 ? st_multi.filtration(simplex_handle)[dimension] : 0; + typename simplextree_multi::Options::value_type f = dimension >= 0 ? st_multi.filtration(simplex_handle)[dimension] : 0; st.insert_simplex(simplex,f); } } -void flatten(const uintptr_t splxptr, const uintptr_t newsplxptr, const int dimension = 0){ // for python - auto &st = get_simplextree_from_pointer(newsplxptr); - auto &st_multi = get_simplextree_from_pointer(splxptr); - flatten(st, st_multi, dimension); + +template +void linear_projection(simplextree_std &st, simplextree_multi &st_multi, const std::vector& linear_form){ + auto sh = st.complex_simplex_range().begin(); + auto sh_multi = st_multi.complex_simplex_range().begin(); + auto end = st.complex_simplex_range().end(); + typename simplextree_multi::Options::Filtration_value multi_filtration; + for (; sh != end; ++sh, ++sh_multi){ + multi_filtration = st_multi.filtration(*sh_multi); + auto projected_filtration = multi_filtration.linear_projection(linear_form); + st.assign_filtration(*sh, projected_filtration); + } } +// For python interface. Do not use. + + -template -void flatten_diag(Simplex_tree<_options_std> &st, Simplex_tree<_options_multi> &st_multi, const std::vector basepoint, int dimension){ - Gudhi::multi_filtrations::Line l(basepoint); +template +void flatten_diag(simplextree_std &st, simplextree_multi &st_multi, const std::vector basepoint, int dimension){ + Gudhi::multi_filtrations::Line l(basepoint); for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - std::vector f = st_multi.filtration(simplex_handle); + std::vector f = st_multi.filtration(simplex_handle); if (dimension <0) dimension = 0; - typename _options_multi::value_type new_filtration = l.push_forward(f)[dimension]; + typename simplextree_multi::Options::value_type new_filtration = l.push_forward(f)[dimension]; st.insert_simplex(simplex,new_filtration); } } -void flatten_diag(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ // for python - auto &st = get_simplextree_from_pointer(newsplxptr); - auto &st_multi = get_simplextree_from_pointer(splxptr); - flatten_diag(st,st_multi,basepoint, dimension); -} + +/// @brief projects the point x on the grid +/// @tparam out_type +/// @param x +/// @param grid +/// @return template std::vector find_coordinates(const std::vector &x, const multi_filtration_grid &grid){ // TODO: optimize with, e.g., dichotomy std::vector coordinates(grid.size()); - for (int parameter = 0; parameter< (int)grid.size(); parameter++){ - const auto& filtration = grid[parameter]; - const auto& to_project = x[parameter]; - std::vector distance_vector(filtration.size()); - for (int i = 0; i < (int)filtration.size(); i++){ - distance_vector[i] = std::abs(to_project - filtration[i]); + for (int parameter = 0; parameter < (int)grid.size(); parameter++){ + const auto& filtration = grid[parameter]; // assumes its sorted + const auto to_project = x[parameter]; + unsigned int i = 0; + // std::cout << to_project<< " " < filtration[i] && i distance_vector(filtration.size()); + // for (int i = 0; i < (int)filtration.size(); i++){ + // distance_vector[i] = std::abs(to_project - filtration[i]); + // } + // coordinates[parameter] = std::distance(distance_vector.begin(), std::min_element(distance_vector.begin(), distance_vector.end())); } return coordinates; } diff --git a/src/python/include/multi_filtrations/box.h b/src/python/include/multi_filtrations/box.h index 0ee7113ad2..06a61053b0 100644 --- a/src/python/include/multi_filtrations/box.h +++ b/src/python/include/multi_filtrations/box.h @@ -52,6 +52,12 @@ class Box bool contains(const point_type& point) const; void infer_from_filters(const std::vector &Filters_list); bool is_trivial() const ; + std::pair get_pair() const{ + return {bottomCorner_,upperCorner_}; + } + std::pair get_pair(){ + return {bottomCorner_,upperCorner_}; + } private: point_type bottomCorner_; diff --git a/src/python/include/multi_filtrations/finitely_critical_filtrations.h b/src/python/include/multi_filtrations/finitely_critical_filtrations.h index 297fca1383..a2a6037b30 100644 --- a/src/python/include/multi_filtrations/finitely_critical_filtrations.h +++ b/src/python/include/multi_filtrations/finitely_critical_filtrations.h @@ -7,18 +7,21 @@ namespace Gudhi::multi_filtrations{ -template +template class Finitely_critical_multi_filtration : public std::vector { // Class to prevent doing illegal stuff with the standard library, e.g., compare two vectors public: // explicit Finitely_critical_multi_filtration(std::vector& v) : ptr_(&v) { // }; // Conversion + // using std::vector::vector; Finitely_critical_multi_filtration() : std::vector() {}; Finitely_critical_multi_filtration(int n) : std::vector(n, -std::numeric_limits::infinity()) {}; // minus infinity by default Finitely_critical_multi_filtration(int n, T value) : std::vector(n,value) {}; Finitely_critical_multi_filtration(std::initializer_list init) : std::vector(init) {}; Finitely_critical_multi_filtration(const std::vector& v) : std::vector(v) {}; - + Finitely_critical_multi_filtration(typename std::vector::iterator it_begin,typename std::vector::iterator it_end) : std::vector(it_begin, it_end) {}; + Finitely_critical_multi_filtration(typename std::vector::const_iterator it_begin,typename std::vector::const_iterator it_end) : std::vector(it_begin, it_end) {}; + operator std::vector&() const { return *this; @@ -27,6 +30,8 @@ class Finitely_critical_multi_filtration : public std::vector { return static_cast>(*this); } + + //TODO : multicritical -> iterator over filtrations // LESS THAN OPERATORS @@ -95,6 +100,7 @@ class Finitely_critical_multi_filtration : public std::vector { + friend Finitely_critical_multi_filtration& operator-=(Finitely_critical_multi_filtration &result, const Finitely_critical_multi_filtration &to_substract){ std::transform(result.begin(), result.end(), to_substract.begin(),result.begin(), std::minus()); return result; @@ -119,9 +125,9 @@ class Finitely_critical_multi_filtration : public std::vector { } static std::vector> to_python(const std::vector>& to_convert){ - return std::vector>(to_convert.begin(), to_convert.end()); } + static std::vector> from_python(const std::vector>& to_convert){ return std::vector>(to_convert.begin(), to_convert.end());; @@ -139,6 +145,28 @@ class Finitely_critical_multi_filtration : public std::vector { ); } + T linear_projection(const std::vector& x){ + T projection=0; + unsigned int size = std::min(x.size(), this->size()); + for (auto i =0u; iat(i); + return projection; + } + + // easy debug + friend std::ostream& operator<<(std::ostream& stream, const Finitely_critical_multi_filtration& truc){ + stream << "["; + for(unsigned int i = 0; i < truc.size()-1; i++){ + stream << truc[i] << ", "; + } + if(!truc.empty()) stream << truc.back(); + stream << "]"; + return stream; + } + + + + }; From a67628c50b8a31f77bab787c909ea3d4423f1792 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Wed, 6 Sep 2023 11:57:44 +0200 Subject: [PATCH 10/18] simplextree signatures fix --- src/python/gudhi/simplex_tree_multi.pyi | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/python/gudhi/simplex_tree_multi.pyi b/src/python/gudhi/simplex_tree_multi.pyi index 0c56229016..3ef31ff830 100644 --- a/src/python/gudhi/simplex_tree_multi.pyi +++ b/src/python/gudhi/simplex_tree_multi.pyi @@ -1,12 +1,11 @@ import numpy as np from gudhi.simplex_tree import SimplexTree ## Small hack for typing -from multipers.multiparameter_module_approximation import PyModule from typing import Iterable from tqdm import tqdm # SimplexTree python interface -def class SimplexTreeMulti: +class SimplexTreeMulti: """The simplex tree is an efficient and flexible data structure for representing general (filtered) simplicial complexes. The data structure is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex @@ -713,4 +712,4 @@ def class SimplexTreeMulti: :rtype: bool """ ... - \ No newline at end of file + From 44abf0d1c96b2f09605c513dd4d3550199f1b775 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Mon, 11 Sep 2023 19:51:26 +0200 Subject: [PATCH 11/18] namespace multiparameter, added options to tests --- .../simplex_tree_cofaces_benchmark.cpp | 1 + src/Simplex_tree/concept/SimplexTreeOptions.h | 2 + .../simplex_tree_ctor_and_move_unit_test.cpp | 1 + ...simplex_tree_graph_expansion_unit_test.cpp | 1 + ...ke_filtration_non_decreasing_unit_test.cpp | 1 + .../test/simplex_tree_unit_test.cpp | 1 + ...pse.py => multiparameter_edge_collapse.py} | 2 +- src/python/gudhi/simplex_tree_multi.pxd | 16 ++++---- src/python/gudhi/simplex_tree_multi.pyi | 2 +- src/python/gudhi/simplex_tree_multi.pyx | 39 ++++--------------- .../include/Simplex_tree_interface_multi.h | 8 +--- src/python/include/Simplex_tree_multi.h | 16 ++++---- src/python/include/multi_filtrations/box.h | 2 +- .../finitely_critical_filtrations.h | 2 +- src/python/include/multi_filtrations/line.h | 2 +- 15 files changed, 37 insertions(+), 59 deletions(-) rename src/python/gudhi/{edge_collapse.py => multiparameter_edge_collapse.py} (89%) diff --git a/src/Simplex_tree/benchmark/simplex_tree_cofaces_benchmark.cpp b/src/Simplex_tree/benchmark/simplex_tree_cofaces_benchmark.cpp index f6e001b631..e4353977b6 100644 --- a/src/Simplex_tree/benchmark/simplex_tree_cofaces_benchmark.cpp +++ b/src/Simplex_tree/benchmark/simplex_tree_cofaces_benchmark.cpp @@ -91,6 +91,7 @@ struct Simplex_tree_options_stable_simplex_handles { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = true; static const bool stable_simplex_handles = true; + static const bool is_multi_parameter = false; }; int main(int argc, char *argv[]) { diff --git a/src/Simplex_tree/concept/SimplexTreeOptions.h b/src/Simplex_tree/concept/SimplexTreeOptions.h index 0c95d3011c..1f216338fe 100644 --- a/src/Simplex_tree/concept/SimplexTreeOptions.h +++ b/src/Simplex_tree/concept/SimplexTreeOptions.h @@ -31,5 +31,7 @@ struct SimplexTreeOptions { static const bool link_nodes_by_label; /// If true, Simplex_handle will not be invalidated after insertions or removals. static const bool stable_simplex_handles; + + static const bool is_multi_parameter; }; diff --git a/src/Simplex_tree/test/simplex_tree_ctor_and_move_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_ctor_and_move_unit_test.cpp index 9f2f572922..2f74c6d534 100644 --- a/src/Simplex_tree/test/simplex_tree_ctor_and_move_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_ctor_and_move_unit_test.cpp @@ -35,6 +35,7 @@ struct Simplex_tree_options_stable_simplex_handles { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = true; static const bool stable_simplex_handles = true; + static const bool is_multi_parameter = false; }; typedef boost::mpl::list, diff --git a/src/Simplex_tree/test/simplex_tree_graph_expansion_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_graph_expansion_unit_test.cpp index ed0f38d632..3a0eea485f 100644 --- a/src/Simplex_tree/test/simplex_tree_graph_expansion_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_graph_expansion_unit_test.cpp @@ -31,6 +31,7 @@ struct Simplex_tree_options_stable_simplex_handles { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = true; + static const bool is_multi_parameter = false; }; typedef boost::mpl::list, diff --git a/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp index 05b7052612..3ac3b97401 100644 --- a/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp @@ -33,6 +33,7 @@ struct Simplex_tree_options_stable_simplex_handles { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = true; + static const bool is_multi_parameter = false; }; typedef boost::mpl::list, diff --git a/src/Simplex_tree/test/simplex_tree_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_unit_test.cpp index 0e61b9e66b..3942ab9ca4 100644 --- a/src/Simplex_tree/test/simplex_tree_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_unit_test.cpp @@ -41,6 +41,7 @@ struct Simplex_tree_options_stable_simplex_handles { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = true; static const bool stable_simplex_handles = true; + static const bool is_multi_parameter = false; }; typedef boost::mpl::list, diff --git a/src/python/gudhi/edge_collapse.py b/src/python/gudhi/multiparameter_edge_collapse.py similarity index 89% rename from src/python/gudhi/edge_collapse.py rename to src/python/gudhi/multiparameter_edge_collapse.py index f4c9edd65d..9337ce7321 100644 --- a/src/python/gudhi/edge_collapse.py +++ b/src/python/gudhi/multiparameter_edge_collapse.py @@ -1,10 +1,10 @@ from tqdm import tqdm +from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated def _collapse_edge_list(edges, num:int=0, full:bool=False, strong:bool=False, progress:bool=False): """ Given an edge list defining a 1 critical 2 parameter 1 dimensional simplicial complex, simplificates this filtered simplicial complex, using filtration-domination's edge collapser. """ - from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated n = len(edges) if full: num = 100 diff --git a/src/python/gudhi/simplex_tree_multi.pxd b/src/python/gudhi/simplex_tree_multi.pxd index 2c11eaad5d..a40bf7f399 100644 --- a/src/python/gudhi/simplex_tree_multi.pxd +++ b/src/python/gudhi/simplex_tree_multi.pxd @@ -31,8 +31,8 @@ ctypedef pair[simplex_type, value_type*] simplex_filtration_type -cdef extern from "multi_filtrations/finitely_critical_filtrations.h" namespace "Gudhi::multi_filtrations": - cdef cppclass Finitely_critical_multi_filtration "Gudhi::multi_filtrations::Finitely_critical_multi_filtration": +cdef extern from "multi_filtrations/finitely_critical_filtrations.h" namespace "Gudhi::multiparameter::multi_filtrations": + cdef cppclass Finitely_critical_multi_filtration "Gudhi::multiparameter::multi_filtrations::Finitely_critical_multi_filtration": Finitely_critical_multi_filtration() except + nogil Finitely_critical_multi_filtration(vector[value_type]) except + Finitely_critical_multi_filtration& operator=(const Finitely_critical_multi_filtration&) @@ -44,33 +44,33 @@ cdef extern from "multi_filtrations/finitely_critical_filtrations.h" namespace " -cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi": +cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi::multiparameter": cdef cppclass Simplex_tree_options_multidimensional_filtration: pass - cdef cppclass Simplex_tree_multi_simplex_handle "Gudhi::Simplex_tree_interface_multi::Simplex_handle": + cdef cppclass Simplex_tree_multi_simplex_handle "Gudhi::multiparameter::Simplex_tree_interface_multi::Simplex_handle": pass - cdef cppclass Simplex_tree_multi_simplices_iterator "Gudhi::Simplex_tree_interface_multi::Complex_simplex_iterator": + cdef cppclass Simplex_tree_multi_simplices_iterator "Gudhi::multiparameter::Simplex_tree_interface_multi::Complex_simplex_iterator": Simplex_tree_multi_simplices_iterator() nogil Simplex_tree_multi_simplex_handle& operator*() nogil Simplex_tree_multi_simplices_iterator operator++() nogil bint operator!=(Simplex_tree_multi_simplices_iterator) nogil - cdef cppclass Simplex_tree_multi_skeleton_iterator "Gudhi::Simplex_tree_interface_multi::Skeleton_simplex_iterator": + cdef cppclass Simplex_tree_multi_skeleton_iterator "Gudhi::multiparameter::Simplex_tree_interface_multi::Skeleton_simplex_iterator": Simplex_tree_multi_skeleton_iterator() nogil Simplex_tree_multi_simplex_handle& operator*() nogil Simplex_tree_multi_skeleton_iterator operator++() nogil bint operator!=(Simplex_tree_multi_skeleton_iterator) nogil - cdef cppclass Simplex_tree_multi_boundary_iterator "Gudhi::Simplex_tree_interface_multi::Boundary_simplex_iterator": + cdef cppclass Simplex_tree_multi_boundary_iterator "Gudhi::multiparameter::Simplex_tree_interface_multi::Boundary_simplex_iterator": Simplex_tree_multi_boundary_iterator() nogil Simplex_tree_multi_simplex_handle& operator*() nogil Simplex_tree_multi_boundary_iterator operator++() nogil bint operator!=(Simplex_tree_multi_boundary_iterator) nogil - cdef cppclass Simplex_tree_multi_interface "Gudhi::Simplex_tree_interface_multi": + cdef cppclass Simplex_tree_multi_interface "Gudhi::multiparameter::Simplex_tree_interface_multi": Simplex_tree_multi_interface() nogil Simplex_tree_multi_interface(Simplex_tree_multi_interface&) nogil value_type* simplex_filtration(const vector[int]& simplex) nogil diff --git a/src/python/gudhi/simplex_tree_multi.pyi b/src/python/gudhi/simplex_tree_multi.pyi index 3ef31ff830..87b02aabd6 100644 --- a/src/python/gudhi/simplex_tree_multi.pyi +++ b/src/python/gudhi/simplex_tree_multi.pyi @@ -712,4 +712,4 @@ class SimplexTreeMulti: :rtype: bool """ ... - + \ No newline at end of file diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index 8cb8b61a10..d2c65adab4 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -50,7 +50,7 @@ from tqdm import tqdm from warnings import warn -cdef extern from "Simplex_tree_multi.h" namespace "Gudhi": +cdef extern from "Simplex_tree_multi.h" namespace "Gudhi::multiparameter": void multify_from_ptr(const uintptr_t, const uintptr_t, const unsigned int, const vector[value_type]&) except + nogil void flatten_from_ptr(const uintptr_t, const uintptr_t, const unsigned int) nogil void linear_projection_from_ptr(const uintptr_t, const uintptr_t, const vector[value_type]&) nogil @@ -691,7 +691,7 @@ cdef class SimplexTreeMulti: # """ # self.compute_persistence(homology_coeff_field, min_persistence, persistence_dim_max) # return self.pcohptr.get_persistence() - + ## This function is only meant for the edge collapse interface. def get_edge_list(self): @@ -737,7 +737,7 @@ cdef class SimplexTreeMulti: # Retrieves the edge list, and send it to filration_domination edges = self.get_edge_list() - from multipers.edge_collapse import _collapse_edge_list + from gudhi.multiparameter_edge_collapse import _collapse_edge_list edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress) # Retrieves the collapsed simplicial complex self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension) @@ -1023,7 +1023,7 @@ cdef class SimplexTreeMulti: - def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, coordinate_values:bool=True, force=False, **filtration_grid_kwargs)->SimplexTreeMulti: + def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, **filtration_grid_kwargs)->SimplexTreeMulti: """ Fit the filtration of the simplextree to a grid. @@ -1038,10 +1038,10 @@ cdef class SimplexTreeMulti: if filtration_grid is None: filtration_grid = self.get_filtration_grid(**filtration_grid_kwargs) cdef vector[vector[value_type]] c_filtration_grid = filtration_grid cdef intptr_t ptr = self.thisptr - cdef bool c_coordinate_values = coordinate_values - self.filtration_grid = c_filtration_grid + if coordinate_values: + self.filtration_grid = c_filtration_grid with nogil: - squeeze_filtration(ptr, c_filtration_grid, c_coordinate_values) + squeeze_filtration(ptr, c_filtration_grid, coordinate_values) return self @property @@ -1170,31 +1170,6 @@ cdef class SimplexTreeMulti: """ return dereference(self.get_ptr()) == dereference(other.get_ptr()) -# def euler_char(self, points:np.ndarray|list) -> np.ndarray: -# """ Computes the Euler Characteristic of the filtered complex at given (multiparameter) time - -# Parameters -# ---------- -# points: 2-dimensional array. -# List of filtration values on which to compute the euler characteristic. - -# Returns -# ------- -# The list of euler characteristic values -# """ -# # TODO : muticritical, compute it from a coordinate simplextree, tbb. -# # if len(points) == 0: -# # return cnp.empty() -# # if type(points[0]) is float: -# # points = cnp.array([points]) -# # if type(points) is cnp.ndarray: -# # assert len(points.shape) in [1,2] -# # if len(points.shape) == 1: -# # points = [points] -# c_points = np.asarray(points) -# assert c_points.shape[1] == self.num_parameters -# return np.asarray(self.get_ptr().euler_char(c_points)) - cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil: return (new Simplex_tree_multi_interface(dereference(stree.get_ptr()))) diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index 500d5f55c6..4464b180ba 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -11,13 +11,9 @@ #pragma once -//#include -//#include -//#include -//#include + #include "Simplex_tree_interface.h" #include "Simplex_tree_multi.h" -// #include #include "multi_filtrations/finitely_critical_filtrations.h" #include @@ -27,7 +23,7 @@ #include // for std::distance #include -namespace Gudhi { +namespace Gudhi::multiparameter { template class Simplex_tree_interface_multi : public Simplex_tree_interface { diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index 5a9adb261b..941298ee53 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -21,7 +21,7 @@ -namespace Gudhi { +namespace Gudhi::multiparameter { /** Model of SimplexTreeOptions. * * Maximum number of simplices to compute persistence is std::numeric_limits::max() @@ -33,7 +33,7 @@ struct Simplex_tree_options_multidimensional_filtration { typedef linear_indexing_tag Indexing_tag; typedef int Vertex_handle; typedef float value_type; - using Filtration_value = Gudhi::multi_filtrations::Finitely_critical_multi_filtration; + using Filtration_value = multi_filtrations::Finitely_critical_multi_filtration; typedef std::uint32_t Simplex_key; static const bool store_key = true; static const bool store_filtration = true; @@ -113,7 +113,7 @@ void linear_projection(simplextree_std &st, simplextree_multi &st_multi, const s template void flatten_diag(simplextree_std &st, simplextree_multi &st_multi, const std::vector basepoint, int dimension){ - Gudhi::multi_filtrations::Line l(basepoint); + multi_filtrations::Line l(basepoint); for (const auto &simplex_handle : st_multi.complex_simplex_range()){ std::vector simplex; for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) @@ -217,16 +217,16 @@ std::vector get_filtration_values(const uintptr_t splxptr namespace std { template<> -class numeric_limits +class numeric_limits { public: - static Gudhi::multi_filtration_type infinity() throw(){ - return Gudhi::multi_filtration_type(1, std::numeric_limits::infinity()); + static Gudhi::multiparameter::multi_filtration_type infinity() throw(){ + return Gudhi::multiparameter::multi_filtration_type(1, std::numeric_limits::infinity()); }; - static Gudhi::multi_filtration_type quiet_NaN() throw(){ - return Gudhi::multi_filtration_type(1, numeric_limits::quiet_NaN()); + static Gudhi::multiparameter::multi_filtration_type quiet_NaN() throw(){ + return Gudhi::multiparameter::multi_filtration_type(1, numeric_limits::quiet_NaN()); }; }; diff --git a/src/python/include/multi_filtrations/box.h b/src/python/include/multi_filtrations/box.h index 06a61053b0..ea16686ac3 100644 --- a/src/python/include/multi_filtrations/box.h +++ b/src/python/include/multi_filtrations/box.h @@ -31,7 +31,7 @@ * @brief Holds the square box on which to compute. */ -namespace Gudhi::multi_filtrations{ +namespace Gudhi::multiparameter::multi_filtrations{ template class Box diff --git a/src/python/include/multi_filtrations/finitely_critical_filtrations.h b/src/python/include/multi_filtrations/finitely_critical_filtrations.h index a2a6037b30..0832f7b676 100644 --- a/src/python/include/multi_filtrations/finitely_critical_filtrations.h +++ b/src/python/include/multi_filtrations/finitely_critical_filtrations.h @@ -5,7 +5,7 @@ #include #include -namespace Gudhi::multi_filtrations{ +namespace Gudhi::multiparameter::multi_filtrations{ template class Finitely_critical_multi_filtration : public std::vector { diff --git a/src/python/include/multi_filtrations/line.h b/src/python/include/multi_filtrations/line.h index e4a939036c..0fa82cf715 100644 --- a/src/python/include/multi_filtrations/line.h +++ b/src/python/include/multi_filtrations/line.h @@ -19,7 +19,7 @@ #include "box.h" #include "finitely_critical_filtrations.h" -namespace Gudhi::multi_filtrations{ +namespace Gudhi::multiparameter::multi_filtrations{ template From 79a42b4f414fedc8793ccebc0de7fbdcd82c0473 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Mon, 11 Sep 2023 21:02:27 +0200 Subject: [PATCH 12/18] add multipers option to tests of pcoh --- src/Persistent_cohomology/test/betti_numbers_unit_test.cpp | 2 ++ .../test/persistent_cohomology_unit_test.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Persistent_cohomology/test/betti_numbers_unit_test.cpp b/src/Persistent_cohomology/test/betti_numbers_unit_test.cpp index eccd17b21f..96b880cc94 100644 --- a/src/Persistent_cohomology/test/betti_numbers_unit_test.cpp +++ b/src/Persistent_cohomology/test/betti_numbers_unit_test.cpp @@ -19,6 +19,8 @@ struct MiniSTOptions : Gudhi::Simplex_tree_options_full_featured { static const bool store_key = true; // I have few vertices typedef short Vertex_handle; + + static const bool is_multi_parameter = false; }; using Mini_simplex_tree = Gudhi::Simplex_tree; diff --git a/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp b/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp index 5a68ffb600..71c6717087 100644 --- a/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp +++ b/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp @@ -177,6 +177,7 @@ struct MiniSTOptions { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = false; + static const bool is_multi_parameter = false; }; using Mini_simplex_tree = Gudhi::Simplex_tree; From 79c52cdfa615a5642cf2a7da31a41fcf036195ab Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Wed, 13 Sep 2023 10:56:38 +0200 Subject: [PATCH 13/18] set filtration to const by default and add a mutable filtration method --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 12 +++++++++-- .../include/Simplex_tree_interface_multi.h | 21 ++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 66b9c10f7c..e0a33fda15 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -153,6 +153,7 @@ class Simplex_tree { Filtration_simplex_base_real() : filt_{} {} void assign_filtration(const Filtration_value& f) { filt_ = f; } // Filtration_value filtration() const { return filt_; } + const Filtration_value& filtration() const { return filt_; } Filtration_value& filtration() { return filt_; } private: Filtration_value filt_; @@ -160,7 +161,7 @@ class Simplex_tree { struct Filtration_simplex_base_dummy { Filtration_simplex_base_dummy() {} void assign_filtration(Filtration_value GUDHI_CHECK_code(f)) { GUDHI_CHECK(f == 0, "filtration value specified for a complex that does not store them"); } - Filtration_value& filtration() { return inf_; } + const Filtration_value& filtration() const { return {}; } }; typedef typename std::conditional::type Filtration_simplex_base; @@ -602,7 +603,14 @@ class Simplex_tree { * Called on the null_simplex, it returns infinity. * If SimplexTreeOptions::store_filtration is false, returns 0. */ - static Filtration_value& filtration(Simplex_handle sh){ + static const Filtration_value& filtration(Simplex_handle sh){ + if (sh != null_simplex()) { + return sh->second.filtration(); + } else { + return inf_; + } + } + static Filtration_value& filtration_mutable(Simplex_handle sh){ if (sh != null_simplex()) { return sh->second.filtration(); } else { diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index 4464b180ba..db493f9302 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -103,7 +103,7 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface& filtration, int axis){ using value_type=options_multi::value_type; for (auto &SimplexHandle : Base::complex_simplex_range()){ - std::vector current_birth = Base::filtration(SimplexHandle); + std::vector& current_birth = Base::filtration_mutable(SimplexHandle); value_type to_assign = -1*std::numeric_limits::infinity(); for (auto vertex : Base::simplex_vertex_range(SimplexHandle)){ to_assign = std::max(filtration[vertex], to_assign); } current_birth[axis] = to_assign; - Base::assign_filtration(SimplexHandle, current_birth); + // Base::assign_filtration(SimplexHandle, current_birth); } } using simplices_list = std::vector>; @@ -205,7 +205,7 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface simplex; auto it = Base::simplex_vertex_range(simplexHandle).begin(); simplex = {*it, *(++it)}; - auto f = Base::filtration(simplexHandle); + const auto& f = Base::filtration(simplexHandle); simplex_list.push_back({simplex, {f[0], f[1]}}); } } @@ -249,9 +249,10 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface new_filtration_value = Base::filtration(SimplexHandle); - new_filtration_value.resize(num); - Base::assign_filtration(SimplexHandle, new_filtration_value); + // std::vector new_filtration_value = Base::filtration(SimplexHandle); + // new_filtration_value.resize(num); + // Base::assign_filtration(SimplexHandle, new_filtration_value); + Base::filtration_mutable(SimplexHandle).resize(num);; } } From 962f6e3836cdab6795632e654d192e6809416225 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Wed, 13 Sep 2023 11:34:26 +0200 Subject: [PATCH 14/18] typo --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index e0a33fda15..5a556c4a09 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -973,7 +973,7 @@ class Simplex_tree { // assign_filtration(simplex_one, new_filtration); } } - else{ + else{ // non-multiparameter assign_filtration(simplex_one, filt); } @@ -1586,7 +1586,7 @@ class Simplex_tree { for (auto& simplex : boost::adaptors::reverse(sib->members())) { // Find the maximum filtration value in the border Boundary_simplex_range boundary = boundary_simplex_range(&simplex); - typename SimplexTreeOptions::Filtration_value max_filt_border_value {}; + typename SimplexTreeOptions::Filtration_value max_filt_border_value; if constexpr (SimplexTreeOptions::is_multi_parameter){ // in that case, we assume that Filtration_value has a `push_to` member to handle this. max_filt_border_value = typename SimplexTreeOptions::Filtration_value(this->number_of_parameters_); From b73577f9c380e814995eb203413f9e840fdae123 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Wed, 13 Sep 2023 15:42:32 +0200 Subject: [PATCH 15/18] fixed dummy filtration with ref --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 5a556c4a09..97284aac26 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -161,7 +161,8 @@ class Simplex_tree { struct Filtration_simplex_base_dummy { Filtration_simplex_base_dummy() {} void assign_filtration(Filtration_value GUDHI_CHECK_code(f)) { GUDHI_CHECK(f == 0, "filtration value specified for a complex that does not store them"); } - const Filtration_value& filtration() const { return {}; } + const Filtration_value& filtration() const { return null_value; } + static constexpr Filtration_value null_value={}; }; typedef typename std::conditional::type Filtration_simplex_base; From 6267d419eacb2b3ea0bc780bc969b67545e48399 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Thu, 21 Sep 2023 18:57:26 +0200 Subject: [PATCH 16/18] add prune wrt dim --- src/python/gudhi/simplex_tree_multi.pxd | 1 + src/python/gudhi/simplex_tree_multi.pyx | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/python/gudhi/simplex_tree_multi.pxd b/src/python/gudhi/simplex_tree_multi.pxd index a40bf7f399..187633b1c9 100644 --- a/src/python/gudhi/simplex_tree_multi.pxd +++ b/src/python/gudhi/simplex_tree_multi.pxd @@ -88,6 +88,7 @@ cdef extern from "Simplex_tree_interface_multi.h" namespace "Gudhi::multiparamet void expansion(int max_dim) except + nogil void remove_maximal_simplex(simplex_type simplex) nogil # bool prune_above_filtration(filtration_type filtration) nogil + bool prune_above_dimension(int dimension) nogil bool make_filtration_non_decreasing() except + nogil # void compute_extended_filtration() nogil # Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) except + nogil diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index d2c65adab4..c3f625033a 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -540,7 +540,15 @@ cdef class SimplexTreeMulti: # method to recompute the exact dimension. # """ # return self.get_ptr().prune_above_filtration(filtration) + def prune_above_dimension(self, int dimension): + """Remove all simplices of dimension greater than a given value. + :param dimension: Maximum dimension value. + :type dimension: int + :returns: The modification information. + :rtype: bool + """ + return self.get_ptr().prune_above_dimension(dimension) def expansion(self, int max_dim)->SimplexTreeMulti: """Expands the simplex tree containing only its one skeleton until dimension max_dim. From 36de6423be68cc113853eaf0388624d4b9666f7d Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Mon, 25 Sep 2023 15:28:42 +0200 Subject: [PATCH 17/18] safe+slow simplextree conversion and optimizations --- src/python/gudhi/simplex_tree_multi.pyx | 30 ++++++++-- .../include/Simplex_tree_interface_multi.h | 36 +----------- src/python/include/Simplex_tree_multi.h | 58 +++++++++++-------- 3 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/python/gudhi/simplex_tree_multi.pyx b/src/python/gudhi/simplex_tree_multi.pyx index c3f625033a..d2beda7069 100644 --- a/src/python/gudhi/simplex_tree_multi.pyx +++ b/src/python/gudhi/simplex_tree_multi.pyx @@ -59,8 +59,8 @@ cdef extern from "Simplex_tree_multi.h" namespace "Gudhi::multiparameter": vector[vector[vector[value_type]]] get_filtration_values(uintptr_t, const vector[int]&) except + nogil -cdef bool callback(vector[int] simplex, void *blocker_func): - return (blocker_func)(simplex) +# cdef bool callback(vector[int] simplex, void *blocker_func): + # return (blocker_func)(simplex) # SimplexTree python interface cdef class SimplexTreeMulti: @@ -84,7 +84,7 @@ cdef class SimplexTreeMulti: # cdef Simplex_tree_persistence_interface * pcohptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, other = None, num_parameters:int=2,default_values=[]): + def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False): """SimplexTreeMulti constructor. :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created. @@ -102,6 +102,7 @@ cdef class SimplexTreeMulti: # The real cython constructor def __cinit__(self, other = None, int num_parameters=2, default_values=[-np.inf], # I'm not sure why `[]` does not work. Cython bug ? +bool safe_conversion=False, ): #TODO doc cdef vector[value_type] c_default_values=default_values if other is not None: @@ -111,7 +112,11 @@ cdef class SimplexTreeMulti: self.filtration_grid = other.filtration_grid elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree self.thisptr = (new Simplex_tree_multi_interface()) - multify_from_ptr(other.thisptr, self.thisptr, num_parameters, c_default_values) + if safe_conversion: + new_st_multi = _safe_simplextree_multify(other, num_parameters = num_parameters) + self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr + else: + multify_from_ptr(other.thisptr, self.thisptr, num_parameters, c_default_values) else: raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.") else: @@ -1217,4 +1222,19 @@ def _simplextree_multify(simplextree:SimplexTree, num_parameters:int=2, default_ cdef vector[value_type] c_default_values=default_values with nogil: multify_from_ptr(old_ptr, new_ptr, c_num_parameters, c_default_values) - return \ No newline at end of file + return st + +def _safe_simplextree_multify(simplextree:SimplexTree,int num_parameters=2)->SimplexTreeMulti: + if isinstance(simplextree, SimplexTreeMulti): + return simplextree + simplices = [[] for _ in range(simplextree.dimension()+1)] + filtration_values = [[] for _ in range(simplextree.dimension()+1)] + for s,f in simplextree.get_simplices(): + filtration_values[len(s)-1].append(f) + simplices[len(s)-1].append(s) + st_multi = SimplexTreeMulti(num_parameters=1) + for batch_simplices, batch_filtrations in zip(simplices,filtration_values): + st_multi.insert_batch(np.asarray(batch_simplices).T, np.asarray(batch_filtrations)[:,None]) + if num_parameters > 1: + st_multi.set_num_parameter(num_parameters) + return st_multi \ No newline at end of file diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index db493f9302..139def9ee2 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -212,40 +212,6 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface> &point_list){ // TODO multi-critical -// const int npts = point_list.size(); -// if (npts == 0){ -// return {}; -// } -// using Gudhi::multi_filtrations::Finitely_critical_multi_filtration; - -// euler_chars_type out(point_list.size(), 0.); - -// // auto is_greater = [nparameters](const point_type &a, const point_type &b){ //french greater -// // for (int i = 0; i< nparameters; i++) -// // if( a[i] < b[i]) -// // return false; -// // return true; -// // }; -// // #pragma omp parallel for -// for (int i = 0; i< npts; i++){ // Maybe add a pragma here for parallel -// auto &euler_char_at_point = out[i]; -// // #pragma omp parallel for reduction(+:euler_char_at_point) // GUDHI : not possible, need a RANDOM ACCESS ITERATOR -// for(const auto &SimplexHandle : Base::complex_simplex_range()){ -// // const Finitely_critical_multi_filtration &pt = *(Finitely_critical_multi_filtration*)(&point_list[i]); -// options_multi::Filtration_value filtration = Base::filtration(SimplexHandle); -// // const Finitely_critical_multi_filtration &filtration = *(Finitely_critical_multi_filtration*)(&filtration_); -// Finitely_critical_multi_filtration pt(point_list[i]); -// // if (is_greater(pt, filtration)){ -// if (filtration <= pt){ -// int sign = Base::dimension(SimplexHandle) %2 ? -1 : 1; -// euler_char_at_point += sign; -// } -// } -// } -// return out; -// } void resize_all_filtrations(int num){ //TODO : that is for 1 critical filtrations if (num < 0) return; for(const auto &SimplexHandle : Base::complex_simplex_range()){ @@ -260,7 +226,7 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface; +using interface_std = Simplex_tree; // Interface not necessary (smaller so should do less segfaults) using interface_multi = Simplex_tree_interface_multi; diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index 941298ee53..2fcdf35821 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -61,9 +61,7 @@ simplextreeinterface& get_simplextree_from_pointer(const uintptr_t splxptr){ //D } template void multify(simplextree_std &st, simplextree_multi &st_multi, const int num_parameters, const typename simplextree_multi::Options::Filtration_value& default_values={}){ - if (num_parameters <= 0) - {std::cerr << "Empty filtration\n"; throw ;} - // if (default_values.size() -1 > num_parameters) + // if (default_values.size() -1 > num_parameters) // {std::cerr << "default values too large !\n"; throw ;} typename simplextree_multi::Options::Filtration_value f(num_parameters); for (auto i = 0u; i(default_values.size()), static_cast(num_parameters-1));i++) @@ -74,9 +72,12 @@ void multify(simplextree_std &st, simplextree_multi &st_multi, const int num_par simplex.clear(); for (auto vertex : st.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); + + if (num_parameters > 0) f[0] = st.filtration(simplex_handle); // std::cout << "Inserting " << f << "\n"; - st_multi.insert_simplex(simplex,f); + auto filtration_copy = f; + st_multi.insert_simplex(simplex,filtration_copy); } } @@ -130,18 +131,28 @@ void flatten_diag(simplextree_std &st, simplextree_multi &st_multi, const std::v -/// @brief projects the point x on the grid +/// @brief turns filtration value x into coordinates in the grid /// @tparam out_type /// @param x /// @param grid /// @return -template -std::vector find_coordinates(const std::vector &x, const multi_filtration_grid &grid){ +template +inline void find_coordinates(vector_like& x, const multi_filtration_grid &grid){ // TODO: optimize with, e.g., dichotomy - std::vector coordinates(grid.size()); - for (int parameter = 0; parameter < (int)grid.size(); parameter++){ + + for (auto parameter = 0u; parameter < grid.size(); parameter++){ const auto& filtration = grid[parameter]; // assumes its sorted const auto to_project = x[parameter]; + if constexpr (std::numeric_limits::has_infinity) + if (to_project == std::numeric_limits::infinity()){ + x[parameter] = std::numeric_limits::infinity(); +continue; + } + + if (to_project >= filtration.back()){ + x[parameter] = filtration.size()-1; + continue; + } // deals with infinite value at the end of the grid, TODO DEAL unsigned int i = 0; // std::cout << to_project<< " " < filtration[i] && i find_coordinates(const std::vector distance_vector(filtration.size()); // for (int i = 0; i < (int)filtration.size(); i++){ @@ -162,29 +173,26 @@ std::vector find_coordinates(const std::vector &st_multi = *(Gudhi::Simplex_tree*)(splxptr); - int num_parameters = st_multi.get_number_of_parameters(); - if ((int)grid.size() != num_parameters){ + auto num_parameters = static_cast(st_multi.get_number_of_parameters()); + if (grid.size() != num_parameters){ std::cerr << "Bad grid !" << std::endl; throw; } for (const auto &simplex_handle : st_multi.complex_simplex_range()){ - std::vector simplex_filtration = st_multi.filtration(simplex_handle); - if (coordinate_values) - st_multi.assign_filtration(simplex_handle, find_coordinates(simplex_filtration, grid)); - else{ - auto coordinates = find_coordinates(simplex_filtration, grid); - std::vector squeezed_filtration(num_parameters); - for (int parameter = 0; parameter < num_parameters; parameter++) - squeezed_filtration[parameter] = grid[parameter][coordinates[parameter]]; - st_multi.assign_filtration(simplex_handle, squeezed_filtration); + auto& simplex_filtration = st_multi.filtration_mutable(simplex_handle); + find_coordinates(simplex_filtration, grid); // turns the simplexfiltration into coords in the grid + if (!coordinate_values){ + for (auto parameter = 0u; parameter < num_parameters; parameter++) + simplex_filtration[parameter] = grid[parameter][simplex_filtration[parameter]]; } } return; @@ -214,6 +222,8 @@ std::vector get_filtration_values(const uintptr_t splxptr } // namespace Gudhi + + namespace std { template<> From 82ce7d0fb937366a8dd9a72af60a96de2636d669 Mon Sep 17 00:00:00 2001 From: David Loiseaux Date: Tue, 26 Sep 2023 16:55:19 +0200 Subject: [PATCH 18/18] cleaning --- src/Simplex_tree/concept/SimplexTreeOptions.h | 2 +- src/Simplex_tree/include/gudhi/Simplex_tree.h | 23 ++++------- .../include/Simplex_tree_interface_multi.h | 23 +++++------ src/python/include/Simplex_tree_multi.h | 29 +++++--------- src/python/include/multi_filtrations/box.h | 14 +------ .../finitely_critical_filtrations.h | 40 ++++--------------- 6 files changed, 37 insertions(+), 94 deletions(-) diff --git a/src/Simplex_tree/concept/SimplexTreeOptions.h b/src/Simplex_tree/concept/SimplexTreeOptions.h index 1f216338fe..45e5f03bca 100644 --- a/src/Simplex_tree/concept/SimplexTreeOptions.h +++ b/src/Simplex_tree/concept/SimplexTreeOptions.h @@ -31,7 +31,7 @@ struct SimplexTreeOptions { static const bool link_nodes_by_label; /// If true, Simplex_handle will not be invalidated after insertions or removals. static const bool stable_simplex_handles; - + /// If true, assumes that Filtration_value is vector-like instead of float-like. This also assumes that Filtration_values is a class, which has a push_to method to push a filtration value $x$ onto $this>=0$. static const bool is_multi_parameter; }; diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 2c9efbca70..5b92c90804 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -153,7 +153,6 @@ class Simplex_tree { struct Filtration_simplex_base_real { Filtration_simplex_base_real() : filt_{} {} void assign_filtration(const Filtration_value& f) { filt_ = f; } - // Filtration_value filtration() const { return filt_; } const Filtration_value& filtration() const { return filt_; } Filtration_value& filtration() { return filt_; } private: @@ -980,22 +979,14 @@ class Simplex_tree { Simplex_handle simplex_one = insertion_result.first; bool one_is_new = insertion_result.second; if (!one_is_new) { - if (!(filtration(simplex_one) <= filt)) { // TODO : For multipersistence, it's not clear what should be the default, especially for multicritical filtrations + if (!(filtration(simplex_one) <= filt)) { if constexpr (SimplexTreeOptions::is_multi_parameter){ + // By default, does nothing and assumes that the user is smart. if (filt < filtration(simplex_one)){ - // assign_filtration(simplex_one, filt); - // I don't really like this behavior. - // It prevents inserting simplices by default at the smallest possible position after its childrens. - // e.g., if (python) we type : st.insert([0], [1,0]), st.insert([1], [0,1]), st.insert([0,1]) - // we may want st.filtration([0,1]) to be [1,1]. (maybe after a make_filtration_decreasing from user) - // Furthermore, this may more sense as default value -> 0 can erase filtration values of childrens... + // placeholder for comparable filtrations } - else{ // As multicritical filtrations are not well supported yet, its not safe to concatenate yet. - // two incomparables filtrations values -> we concatenate - // std::cout << "incomparable -> concatenate" << std::endl; - // Filtration_value new_filtration = filtration(simplex_one); - // new_filtration.insert_new(filt); // we assume then that Filtration_value has a insert_new method. - // assign_filtration(simplex_one, new_filtration); + else{ + // placeholder for incomparable filtrations } } else{ // non-multiparameter @@ -1633,10 +1624,10 @@ class Simplex_tree { if (dim == 0) return; // Find the maximum filtration value in the border Boundary_simplex_range&& boundary = boundary_simplex_range(sh); - typename SimplexTreeOptions::Filtration_value max_filt_border_value; + Filtration_value max_filt_border_value; if constexpr (SimplexTreeOptions::is_multi_parameter){ // in that case, we assume that Filtration_value has a `push_to` member to handle this. - max_filt_border_value = typename SimplexTreeOptions::Filtration_value(this->number_of_parameters_); + max_filt_border_value = Filtration_value(this->number_of_parameters_); for (auto &face_sh : boundary){ max_filt_border_value.push_to(filtration(face_sh)); // pushes the value of max_filt_border_value to reach simplex' filtration } diff --git a/src/python/include/Simplex_tree_interface_multi.h b/src/python/include/Simplex_tree_interface_multi.h index 139def9ee2..9c0e14bcf0 100644 --- a/src/python/include/Simplex_tree_interface_multi.h +++ b/src/python/include/Simplex_tree_interface_multi.h @@ -48,19 +48,15 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface& filtration, int axis){ using value_type=options_multi::value_type; for (auto &SimplexHandle : Base::complex_simplex_range()){ @@ -180,6 +178,8 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface>; simplices_list get_simplices_of_dimension(int dimension){ simplices_list simplex_list; @@ -215,9 +215,6 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface new_filtration_value = Base::filtration(SimplexHandle); - // new_filtration_value.resize(num); - // Base::assign_filtration(SimplexHandle, new_filtration_value); Base::filtration_mutable(SimplexHandle).resize(num);; } } @@ -229,7 +226,7 @@ class Simplex_tree_interface_multi : public Simplex_tree_interface; // Interface not necessary (smaller so should do less segfaults) using interface_multi = Simplex_tree_interface_multi; - +// Wrappers of the functions in Simplex_tree_multi.h, to deal with the "pointer only" python interface void flatten_diag_from_ptr(const uintptr_t splxptr, const uintptr_t newsplxptr, const std::vector basepoint, int dimension){ // for python auto &st = get_simplextree_from_pointer(newsplxptr); auto &st_multi = get_simplextree_from_pointer(splxptr); diff --git a/src/python/include/Simplex_tree_multi.h b/src/python/include/Simplex_tree_multi.h index 2fcdf35821..2725346e5e 100644 --- a/src/python/include/Simplex_tree_multi.h +++ b/src/python/include/Simplex_tree_multi.h @@ -53,16 +53,16 @@ using simplextree_multi = Simplex_tree; using multi_filtration_type = std::vector; using multi_filtration_grid = std::vector; - +// Retrieves a simplextree from a pointer. As the python simplex_tree and simplex_tree_multi don't know each other we have to exchange them using pointers. template simplextreeinterface& get_simplextree_from_pointer(const uintptr_t splxptr){ //DANGER simplextreeinterface &st = *(simplextreeinterface*)(splxptr); return st; } + +// Turns a 1-parameter simplextree into a multiparameter simplextree, and keeps the 1-filtration in the 1st axis template void multify(simplextree_std &st, simplextree_multi &st_multi, const int num_parameters, const typename simplextree_multi::Options::Filtration_value& default_values={}){ - // if (default_values.size() -1 > num_parameters) - // {std::cerr << "default values too large !\n"; throw ;} typename simplextree_multi::Options::Filtration_value f(num_parameters); for (auto i = 0u; i(default_values.size()), static_cast(num_parameters-1));i++) f[i+1] = default_values[i]; @@ -75,7 +75,6 @@ void multify(simplextree_std &st, simplextree_multi &st_multi, const int num_par if (num_parameters > 0) f[0] = st.filtration(simplex_handle); - // std::cout << "Inserting " << f << "\n"; auto filtration_copy = f; st_multi.insert_simplex(simplex,filtration_copy); @@ -84,7 +83,7 @@ void multify(simplextree_std &st, simplextree_multi &st_multi, const int num_par - +// Turns a multi-parameter simplextree into a 1-parameter simplextree template void flatten(simplextree_std &st, simplextree_multi &st_multi, const int dimension = 0){ for (const auto &simplex_handle : st_multi.complex_simplex_range()){ @@ -96,6 +95,7 @@ void flatten(simplextree_std &st, simplextree_multi &st_multi, const int dimensi } } +// Applies a linear form (i.e. scalar product with Riesz rpz) to the filtration to flatten a simplextree multi template void linear_projection(simplextree_std &st, simplextree_multi &st_multi, const std::vector& linear_form){ auto sh = st.complex_simplex_range().begin(); @@ -108,9 +108,6 @@ void linear_projection(simplextree_std &st, simplextree_multi &st_multi, const s st.assign_filtration(*sh, projected_filtration); } } -// For python interface. Do not use. - - template void flatten_diag(simplextree_std &st, simplextree_multi &st_multi, const std::vector basepoint, int dimension){ @@ -146,18 +143,16 @@ inline void find_coordinates(vector_like& x, const multi_filtration_grid &grid){ if constexpr (std::numeric_limits::has_infinity) if (to_project == std::numeric_limits::infinity()){ x[parameter] = std::numeric_limits::infinity(); -continue; + continue; } - if (to_project >= filtration.back()){ x[parameter] = filtration.size()-1; continue; - } // deals with infinite value at the end of the grid, TODO DEAL + } // deals with infinite value at the end of the grid + unsigned int i = 0; - // std::cout << to_project<< " " < filtration[i] && i distance_vector(filtration.size()); - // for (int i = 0; i < (int)filtration.size(); i++){ - // distance_vector[i] = std::abs(to_project - filtration[i]); - // } - // coordinates[parameter] = std::distance(distance_vector.begin(), std::min_element(distance_vector.begin(), distance_vector.end())); } - // return x; } @@ -197,6 +186,8 @@ void squeeze_filtration(uintptr_t splxptr, const multi_filtration_grid &grid, bo } return; } + +// retrieves the filtration values of a simplextree. Useful to generate a grid. std::vector get_filtration_values(const uintptr_t splxptr, const std::vector °rees){ Simplex_tree &st_multi = *(Gudhi::Simplex_tree*)(splxptr); int num_parameters = st_multi.get_number_of_parameters(); diff --git a/src/python/include/multi_filtrations/box.h b/src/python/include/multi_filtrations/box.h index ea16686ac3..a4c28fdf1f 100644 --- a/src/python/include/multi_filtrations/box.h +++ b/src/python/include/multi_filtrations/box.h @@ -28,7 +28,7 @@ /** - * @brief Holds the square box on which to compute. + * @brief Simple box in $\mathbb R^n$ . */ namespace Gudhi::multiparameter::multi_filtrations{ @@ -74,7 +74,6 @@ inline Box::Box(const point_type &bottomCorner, const point_type &upperCorner upperCorner_(upperCorner) { assert(bottomCorner.size() == upperCorner.size() - // && is_smaller(bottomCorner, upperCorner) && bottomCorner <= upperCorner && "This box is trivial !"); } @@ -89,11 +88,6 @@ inline Box::Box(const std::pair &box) template inline void Box::inflate(T delta) { -// #pragma omp simd - // for (int i = 0; i < bottomCorner_.size(); i++){ - // bottomCorner_[i] -= delta; - // upperCorner_[i] += delta; - // } bottomCorner_ -= delta; upperCorner_ += delta; } @@ -151,12 +145,6 @@ inline bool Box::contains(const point_type &point) const { if (point.size() != bottomCorner_.size()) return false; - // for (int i = 0; i < (int)point.size(); i++){ - // if (point[i] < bottomCorner_[i]) return false; - // if (point[i] > upperCorner_[i]) return false; - // } - - // return true; return bottomCorner_ <= point && point <= upperCorner_; } diff --git a/src/python/include/multi_filtrations/finitely_critical_filtrations.h b/src/python/include/multi_filtrations/finitely_critical_filtrations.h index 0832f7b676..a75c45c641 100644 --- a/src/python/include/multi_filtrations/finitely_critical_filtrations.h +++ b/src/python/include/multi_filtrations/finitely_critical_filtrations.h @@ -11,9 +11,6 @@ template class Finitely_critical_multi_filtration : public std::vector { // Class to prevent doing illegal stuff with the standard library, e.g., compare two vectors public: - // explicit Finitely_critical_multi_filtration(std::vector& v) : ptr_(&v) { - // }; // Conversion - // using std::vector::vector; Finitely_critical_multi_filtration() : std::vector() {}; Finitely_critical_multi_filtration(int n) : std::vector(n, -std::numeric_limits::infinity()) {}; // minus infinity by default Finitely_critical_multi_filtration(int n, T value) : std::vector(n,value) {}; @@ -32,24 +29,9 @@ class Finitely_critical_multi_filtration : public std::vector { - //TODO : multicritical -> iterator over filtrations - - // LESS THAN OPERATORS friend bool operator<(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) { bool isSame = true; - // if (a.size() != b.size()){ - - - // if (a.size()>1 && b.size() >1){ - // std::cerr << "Filtrations are not of the same size ! (" << a.size() << " " << b.size() << ")."; - // throw; - // } - // // {inf, inf, ...} can be stored as {inf} - // if (b[0] == std::numeric_limits::infinity()) //TODO FIXME, we have to check every coord of b instead of 1 - // return a[0] != std::numeric_limits::infinity(); - // return false; - // } int n = std::min(a.size(), b.size()); for (int i = 0; i < n; ++i){ if (a[i] > b[i]) return false; @@ -60,16 +42,6 @@ class Finitely_critical_multi_filtration : public std::vector { } friend bool operator<=(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) { - // if (a.size() != b.size()){ - // if (a.size()>1 && b.size() >1){ - // std::cerr << "Filtrations are not of the same size ! (" << a.size() << " " << b.size() << ")."; - // throw; - // } - // // {inf, inf, ...} can be stored as {inf} - // if (b[0] == std::numeric_limits::infinity()) //TODO FIXME, we have to check every coord of b instead of 1 - // return a[0] != std::numeric_limits::infinity(); - // return false; - // } int n = std::min(a.size(), b.size()); for (int i = 0; i < n; ++i){ if (a[i] > b[i]) return false; @@ -82,11 +54,11 @@ class Finitely_critical_multi_filtration : public std::vector { //GREATER THAN OPERATORS friend bool operator>(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) { - return b=(const Finitely_critical_multi_filtration& a, const Finitely_critical_multi_filtration& b) { - return b<=a; // C'est honteux. + return b<=a; } Finitely_critical_multi_filtration& operator=(const Finitely_critical_multi_filtration& a){ @@ -133,8 +105,10 @@ class Finitely_critical_multi_filtration : public std::vector { return std::vector>(to_convert.begin(), to_convert.end());; } void push_to(const Finitely_critical_multi_filtration& x){ - if (this->size() != x.size()) - {std::cerr << "Does only work with 1-critical filtrations ! Sizes " << this->size() << " and " << x.size() << "are different !" << std::endl; return;} + if (this->size() != x.size()){ + std::cerr << "Does only work with 1-critical filtrations ! Sizes " << this->size() << " and " << x.size() << "are different !" << std::endl; + throw std::logic_error("Bad sizes"); + } for (unsigned int i = 0; i < x.size(); i++) this->at(i) = this->at(i) > x[i] ? this->at(i) : x[i]; } @@ -145,6 +119,8 @@ class Finitely_critical_multi_filtration : public std::vector { ); } + + // scalar product of a filtration value with x. T linear_projection(const std::vector& x){ T projection=0; unsigned int size = std::min(x.size(), this->size());