From cea821f9ca34c270a5ccc047342c2c21ae79a6c0 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Fri, 28 Aug 2020 17:42:12 +0200 Subject: A prototype to fix #364 --- src/python/gudhi/simplex_tree.pxd | 3 ++ src/python/gudhi/simplex_tree.pyx | 21 ++++++++++++++ src/python/include/Simplex_tree_interface.h | 12 ++++++++ src/python/test/test_simplex_tree.py | 44 +++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 75e94e0b..44533d7f 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -66,6 +66,9 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_end() nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil + # + ctypedef bool (*blocker_func)(vector[int], void *user_data) + void expansion_with_blockers_callback(int dimension, blocker_func user_func, void *user_data) cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_persistence_interface "Gudhi::Persistent_cohomology_interface>": diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index dfb1d985..e297cfbd 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -17,6 +17,9 @@ __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" __license__ = "MIT" +cdef bool callback(vector[int] simplex, void *blocker_func): + return (blocker_func)(simplex) + # SimplexTree python interface cdef class SimplexTree: """The simplex tree is an efficient and flexible data structure for @@ -409,6 +412,24 @@ cdef class SimplexTree: persistence_result = self.pcohptr.get_persistence() return self.get_ptr().compute_extended_persistence_subdiagrams(persistence_result, min_persistence) + def expansion_with_blocker(self, max_dim, blocker_func): + """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 + """ + 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. diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index e288a8cf..9f92b349 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -39,6 +39,7 @@ class Simplex_tree_interface : public Simplex_tree { 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; + typedef bool (*blocker_func)(Simplex simplex, void *user_data); public: @@ -188,6 +189,17 @@ class Simplex_tree_interface : public Simplex_tree { return collapsed_stree_ptr; } + void expansion_with_blockers_callback(int dimension, blocker_func user_func, void *user_data) { + Base::expansion_with_blockers(dimension, [&](Simplex_handle sh){ + Simplex simplex; + for (auto vertex : Base::simplex_vertex_range(sh)) { + simplex.insert(simplex.begin(), vertex); + } + return user_func(simplex, user_data); + }); + Base::clear_filtration(); + } + // 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 diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 83be0602..7aad8259 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -358,3 +358,47 @@ def test_collapse_edges(): assert st.find([1, 3]) == False for simplex in st.get_skeleton(0): assert simplex[1] == 1. + +def blocker(simplex): + try: + # Block all simplices that countains vertex 6 + simplex.index(6) + print(simplex, ' is blocked') + return True + except ValueError: + print(simplex, ' is accepted') + return False + +def test_expansion_with_blocker(): + st=SimplexTree() + st.insert([0,1],0) + st.insert([0,2],1) + st.insert([0,3],2) + st.insert([1,2],3) + st.insert([1,3],4) + st.insert([2,3],5) + st.insert([2,4],6) + st.insert([3,6],7) + st.insert([4,5],8) + st.insert([4,6],9) + st.insert([5,6],10) + st.insert([6],10) + + st.expansion_with_blocker(2, blocker) + assert st.num_simplices() == 22 + assert st.dimension() == 2 + assert st.find([4,5,6]) == False + assert st.filtration([0,1,2]) == 3. + assert st.filtration([0,1,3]) == 4. + assert st.filtration([0,2,3]) == 5. + assert st.filtration([1,2,3]) == 5. + + st.expansion_with_blocker(3, blocker) + assert st.num_simplices() == 23 + assert st.dimension() == 3 + assert st.find([4,5,6]) == False + assert st.filtration([0,1,2]) == 3. + assert st.filtration([0,1,3]) == 4. + assert st.filtration([0,2,3]) == 5. + assert st.filtration([1,2,3]) == 5. + assert st.filtration([0,1,2,3]) == 5. -- cgit v1.2.3 From c2eb0484191f89fcbe40bc4ab04943eb808f12a9 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 1 Sep 2020 14:51:25 +0200 Subject: expansion with blocker and how to modify filtration value --- src/python/gudhi/simplex_tree.pxd | 2 +- src/python/gudhi/simplex_tree.pyx | 23 +++++++++--------- src/python/test/test_simplex_tree.py | 46 +++++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 31 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 44533d7f..80c6ffca 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -66,7 +66,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_end() nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil - # + # Expansion with blockers ctypedef bool (*blocker_func)(vector[int], void *user_data) void expansion_with_blockers_callback(int dimension, blocker_func user_func, void *user_data) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index e297cfbd..debe92c0 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -413,21 +413,22 @@ cdef class SimplexTree: return self.get_ptr().compute_extended_persistence_subdiagrams(persistence_result, min_persistence) def expansion_with_blocker(self, max_dim, blocker_func): - """Expands the Simplex_tree containing only its one skeleton - until dimension max_dim. + """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 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 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. - The Simplex_tree must contain no simplex of dimension bigger than - 1 when calling the method. + Note that you cannot update the filtration value of the simplex during the evaluation of `blocker_func`, as it + would segfault. - :param max_dim: The maximal dimension. + :param max_dim: Expansion maximal dimension value. :type max_dim: int + :param blocker_func: Blocker oracle. + :type blocker_func: Its concept is `Boolean blocker_func(list of int)` """ self.get_ptr().expansion_with_blockers_callback(max_dim, callback, blocker_func) diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 7aad8259..33b0ac99 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -359,16 +359,6 @@ def test_collapse_edges(): for simplex in st.get_skeleton(0): assert simplex[1] == 1. -def blocker(simplex): - try: - # Block all simplices that countains vertex 6 - simplex.index(6) - print(simplex, ' is blocked') - return True - except ValueError: - print(simplex, ' is accepted') - return False - def test_expansion_with_blocker(): st=SimplexTree() st.insert([0,1],0) @@ -384,21 +374,39 @@ def test_expansion_with_blocker(): st.insert([5,6],10) st.insert([6],10) + # One cannot modify filtration inside blocker - segfault - Let's record accepted simplices + accepted_simp = [] + def blocker(simplex): + try: + # Block all simplices that countains vertex 6 + simplex.index(6) + print(simplex, ' is blocked') + return True + except ValueError: + print(simplex, ' is accepted') + accepted_simp.append(simplex) + return False + st.expansion_with_blocker(2, blocker) + for simplex in accepted_simp: + st.assign_filtration(simplex, st.filtration(simplex) + 1.) assert st.num_simplices() == 22 assert st.dimension() == 2 assert st.find([4,5,6]) == False - assert st.filtration([0,1,2]) == 3. - assert st.filtration([0,1,3]) == 4. - assert st.filtration([0,2,3]) == 5. - assert st.filtration([1,2,3]) == 5. + assert st.filtration([0,1,2]) == 4. + assert st.filtration([0,1,3]) == 5. + assert st.filtration([0,2,3]) == 6. + assert st.filtration([1,2,3]) == 6. + accepted_simp = [] st.expansion_with_blocker(3, blocker) + for simplex in accepted_simp: + st.assign_filtration(simplex, st.filtration(simplex) + 1.) assert st.num_simplices() == 23 assert st.dimension() == 3 assert st.find([4,5,6]) == False - assert st.filtration([0,1,2]) == 3. - assert st.filtration([0,1,3]) == 4. - assert st.filtration([0,2,3]) == 5. - assert st.filtration([1,2,3]) == 5. - assert st.filtration([0,1,2,3]) == 5. + assert st.filtration([0,1,2]) == 4. + assert st.filtration([0,1,3]) == 5. + assert st.filtration([0,2,3]) == 6. + assert st.filtration([1,2,3]) == 6. + assert st.filtration([0,1,2,3]) == 6. -- cgit v1.2.3 From 953a7da68657b9ab493cfb35b41fc46e26a73876 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 25 Mar 2021 08:52:50 +0100 Subject: Add equality operator for python Simplex tree --- src/python/gudhi/simplex_tree.pxd | 1 + src/python/gudhi/simplex_tree.pyx | 7 +++++++ src/python/test/test_simplex_tree.py | 12 ++++++++++++ 3 files changed, 20 insertions(+) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 000323af..3df614dd 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -65,6 +65,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": vector[vector[pair[int, pair[double, double]]]] compute_extended_persistence_subdiagrams(vector[pair[int, pair[double, double]]] dgm, double min_persistence) nogil Simplex_tree_interface_full_featured* collapse_edges(int nb_collapse_iteration) nogil except + void reset_filtration(double filtration, int dimension) nogil + bint operator==(Simplex_tree_interface_full_featured) nogil # Iterators over Simplex tree pair[vector[int], double] get_simplex_and_filtration(Simplex_tree_simplex_handle f_simplex) nogil Simplex_tree_simplices_iterator get_simplices_iterator_begin() nogil diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index d7991417..0bfaa19f 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -639,3 +639,10 @@ cdef class SimplexTree: self.thisptr = (ptr.collapse_edges(nb_iter)) # Delete old pointer del ptr + + def __eq__(self, other:SimplexTree): + """Simplex tree equality operator using C++ depth first search operator== + :returns: True if the 2 simplex trees are equal, False otherwise. + :rtype: bool + """ + return dereference(self.get_ptr()) == dereference(other.get_ptr()) \ No newline at end of file diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index a3eacaa9..92b909ca 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -404,3 +404,15 @@ def test_boundaries_iterator(): with pytest.raises(RuntimeError): list(st.get_boundaries([6])) # (6) does not exist + +def test_equality_operator(): + st1 = SimplexTree() + st2 = SimplexTree() + + assert st1 == st2 + + st1.insert([1,2,3], 4.) + assert st1 != st2 + + st2.insert([1,2,3], 4.) + assert st1 == st2 \ No newline at end of file -- cgit v1.2.3 From a6e7f96f7d2c391e4548309174cc05f5ae05d871 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Thu, 15 Apr 2021 22:48:10 +0200 Subject: Update src/python/gudhi/simplex_tree.pyx Co-authored-by: Marc Glisse --- src/python/gudhi/simplex_tree.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 0bfaa19f..67428401 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -641,8 +641,8 @@ cdef class SimplexTree: del ptr def __eq__(self, other:SimplexTree): - """Simplex tree equality operator using C++ depth first search operator== + """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()) \ No newline at end of file + return dereference(self.get_ptr()) == dereference(other.get_ptr()) -- cgit v1.2.3 From f4f5992e9025d055e35c86589923f1b0299f552e Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 22 Apr 2021 08:39:03 +0200 Subject: code review: rename block_func when it is a type. Type is a 'callable' --- src/python/gudhi/simplex_tree.pxd | 4 ++-- src/python/gudhi/simplex_tree.pyx | 2 +- src/python/include/Simplex_tree_interface.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 80c6ffca..ff750af9 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -67,8 +67,8 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil # Expansion with blockers - ctypedef bool (*blocker_func)(vector[int], void *user_data) - void expansion_with_blockers_callback(int dimension, blocker_func user_func, void *user_data) + 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) cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_persistence_interface "Gudhi::Persistent_cohomology_interface>": diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index debe92c0..383d1949 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -428,7 +428,7 @@ cdef class SimplexTree: :param max_dim: Expansion maximal dimension value. :type max_dim: int :param blocker_func: Blocker oracle. - :type blocker_func: Its concept is `Boolean blocker_func(list of int)` + :type blocker_func: Callable[[List[int]], bool] """ self.get_ptr().expansion_with_blockers_callback(max_dim, callback, blocker_func) diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 9f92b349..a859bec0 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -39,7 +39,7 @@ class Simplex_tree_interface : public Simplex_tree { 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; - typedef bool (*blocker_func)(Simplex simplex, void *user_data); + typedef bool (*blocker_func_t)(Simplex simplex, void *user_data); public: @@ -189,7 +189,7 @@ class Simplex_tree_interface : public Simplex_tree { return collapsed_stree_ptr; } - void expansion_with_blockers_callback(int dimension, blocker_func user_func, void *user_data) { + 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; for (auto vertex : Base::simplex_vertex_range(sh)) { -- cgit v1.2.3 From 69168e8ed24165ab89ea1c57bc21dd994c93dd8e Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 27 Apr 2021 09:03:47 +0200 Subject: vector initialization improvement --- src/python/include/Simplex_tree_interface.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src/python') diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index a859bec0..dd8d715c 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -191,13 +191,9 @@ class Simplex_tree_interface : public Simplex_tree { 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; - for (auto vertex : Base::simplex_vertex_range(sh)) { - simplex.insert(simplex.begin(), vertex); - } + Simplex simplex(Base::simplex_vertex_range(sh).begin(), Base::simplex_vertex_range(sh).end()); return user_func(simplex, user_data); }); - Base::clear_filtration(); } // Iterator over the simplex tree -- cgit v1.2.3 From 17da2842a3af3db3abec7c2ce03f77dad47ef5dc Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 15 Jun 2021 18:01:20 +0200 Subject: Remove deprecated max_barcodes in plot_persistence_barcode and max_plots in plot_persistence_diagram. Fix #453 and factorize code --- src/python/gudhi/persistence_graphical_tools.py | 63 ++++++++++++------------- 1 file changed, 29 insertions(+), 34 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 848dc03e..460e2558 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -12,6 +12,7 @@ from os import path from math import isfinite import numpy as np from functools import lru_cache +import warnings from gudhi.reader_utils import read_persistence_intervals_in_dimension from gudhi.reader_utils import read_persistence_intervals_grouped_by_dimension @@ -58,6 +59,26 @@ def _array_handler(a): else: return a +def _limit_to_max_intervals(persistence, max_intervals, key): + '''This function returns truncated persistence if length is bigger than max_intervals. + :param persistence: Persistence intervals values list. Can be grouped by dimension or not. + :type persistence: an array of (dimension, array of (birth, death)) or an array of (birth, death). + :param max_intervals: maximal number of intervals to display. + Selected intervals are those with the longest life time. Set it + to 0 to see all. Default value is 1000. + :type max_intervals: int. + :param key: key function for sort algorithm. + :type key: function or lambda. + ''' + if max_intervals > 0 and max_intervals < len(persistence): + warnings.warn('There are %s intervals given as input, whereas max_intervals is set to %s.' % + (len(persistence), max_intervals) + ) + # Sort by life time, then takes only the max_intervals elements + return sorted(persistence, key = key, reverse = True)[:max_intervals] + else: + return persistence + @lru_cache(maxsize=1) def _matplotlib_can_use_tex(): """This function returns True if matplotlib can deal with LaTeX, False otherwise. @@ -75,7 +96,6 @@ def plot_persistence_barcode( persistence_file="", alpha=0.6, max_intervals=1000, - max_barcodes=1000, inf_delta=0.1, legend=False, colormap=None, @@ -142,18 +162,9 @@ def plot_persistence_barcode( persistence = _array_handler(persistence) - if max_barcodes != 1000: - print("Deprecated parameter. It has been replaced by max_intervals") - max_intervals = max_barcodes - - if max_intervals > 0 and max_intervals < len(persistence): - # Sort by life time, then takes only the max_intervals elements - persistence = sorted( - persistence, - key=lambda life_time: life_time[1][1] - life_time[1][0], - reverse=True, - )[:max_intervals] - + persistence = _limit_to_max_intervals(persistence, max_intervals, + key = lambda life_time: life_time[1][1] - life_time[1][0]) + if colormap == None: colormap = plt.cm.Set1.colors if axes == None: @@ -220,7 +231,6 @@ def plot_persistence_diagram( alpha=0.6, band=0.0, max_intervals=1000, - max_plots=1000, inf_delta=0.1, legend=False, colormap=None, @@ -291,17 +301,8 @@ def plot_persistence_diagram( persistence = _array_handler(persistence) - if max_plots != 1000: - print("Deprecated parameter. It has been replaced by max_intervals") - max_intervals = max_plots - - if max_intervals > 0 and max_intervals < len(persistence): - # Sort by life time, then takes only the max_intervals elements - persistence = sorted( - persistence, - key=lambda life_time: life_time[1][1] - life_time[1][0], - reverse=True, - )[:max_intervals] + persistence = _limit_to_max_intervals(persistence, max_intervals, + key = lambda life_time: life_time[1][1] - life_time[1][0]) if colormap == None: colormap = plt.cm.Set1.colors @@ -474,15 +475,9 @@ def plot_persistence_density( ) persistence_dim = persistence_dim[np.isfinite(persistence_dim[:, 1])] - if max_intervals > 0 and max_intervals < len(persistence_dim): - # Sort by life time, then takes only the max_intervals elements - persistence_dim = np.array( - sorted( - persistence_dim, - key=lambda life_time: life_time[1] - life_time[0], - reverse=True, - )[:max_intervals] - ) + + persistence_dim = np.array(_limit_to_max_intervals(persistence_dim, max_intervals, + key = lambda life_time: life_time[1] - life_time[0])) # Set as numpy array birth and death (remove undefined values - inf and NaN) birth = persistence_dim[:, 0] -- cgit v1.2.3 From 8587b70b268aaf7be2ab5b0e163d43f5587b0aac Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 15 Jun 2021 18:03:45 +0200 Subject: Fix MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C as it is deprecated since 3.3 --- src/python/gudhi/persistence_graphical_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 460e2558..460029fb 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -502,7 +502,7 @@ def plot_persistence_density( zi = k(np.vstack([xi.flatten(), yi.flatten()])) # Make the plot - img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap) + img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading='auto') if greyblock: axes.add_patch(mpatches.Polygon([[birth.min(), birth.min()], [death.max(), birth.min()], [death.max(), death.max()]], fill=True, color='lightgrey')) -- cgit v1.2.3 From 2eb2f96248f09e5c793760cfb44ce7b1a40c1af4 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 15 Jun 2021 18:32:33 +0200 Subject: Ignore figure as not used on default subplot --- src/python/gudhi/persistence_graphical_tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 460029fb..b129b375 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -168,7 +168,7 @@ def plot_persistence_barcode( if colormap == None: colormap = plt.cm.Set1.colors if axes == None: - fig, axes = plt.subplots(1, 1) + _, axes = plt.subplots(1, 1) persistence = sorted(persistence, key=lambda birth: birth[1][0]) @@ -307,7 +307,7 @@ def plot_persistence_diagram( if colormap == None: colormap = plt.cm.Set1.colors if axes == None: - fig, axes = plt.subplots(1, 1) + _, axes = plt.subplots(1, 1) (min_birth, max_death) = __min_birth_max_death(persistence, band) delta = (max_death - min_birth) * inf_delta @@ -487,7 +487,7 @@ def plot_persistence_density( if cmap is None: cmap = plt.cm.hot_r if axes == None: - fig, axes = plt.subplots(1, 1) + _, axes = plt.subplots(1, 1) # line display of equation : birth = death x = np.linspace(death.min(), birth.max(), 1000) -- cgit v1.2.3 From 486b281c726cbb6110cfe3c63b3f225690bcd348 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 17 Jun 2021 11:01:18 +0200 Subject: Use float as it can be ambiguous. Do not work if we remove np.inf value --- src/python/doc/persistence_graphical_tools_user.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/doc/persistence_graphical_tools_user.rst b/src/python/doc/persistence_graphical_tools_user.rst index d95b9d2b..e1d28c71 100644 --- a/src/python/doc/persistence_graphical_tools_user.rst +++ b/src/python/doc/persistence_graphical_tools_user.rst @@ -60,7 +60,7 @@ of shape (N x 2) encoding a persistence diagram (in a given dimension). import matplotlib.pyplot as plt import gudhi import numpy as np - d = np.array([[0, 1], [1, 2], [1, np.inf]]) + d = np.array([[0., 1.], [1., 2.], [1., np.inf]]) gudhi.plot_persistence_diagram(d) plt.show() -- cgit v1.2.3 From cb108929433617f553e0a0c7185b3073cce35696 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 17 Jun 2021 15:43:30 +0200 Subject: Fix #461 and review all error cases (no more prints, warnings and exceptions instead) --- src/python/CMakeLists.txt | 4 + src/python/gudhi/persistence_graphical_tools.py | 245 +++++++++++++----------- 2 files changed, 141 insertions(+), 108 deletions(-) (limited to 'src/python') diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 98f2b85f..1f0d74d4 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -542,6 +542,10 @@ if(PYTHONINTERP_FOUND) add_gudhi_py_test(test_dtm_rips_complex) endif() + # persistence graphical tools + if(MATPLOTLIB_FOUND) + add_gudhi_py_test(test_persistence_graphical_tools) + endif() # Set missing or not modules set(GUDHI_MODULES ${GUDHI_MODULES} "python" CACHE INTERNAL "GUDHI_MODULES") diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index b129b375..9bfc19e0 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -13,6 +13,8 @@ from math import isfinite import numpy as np from functools import lru_cache import warnings +import errno +import os from gudhi.reader_utils import read_persistence_intervals_in_dimension from gudhi.reader_utils import read_persistence_intervals_grouped_by_dimension @@ -45,6 +47,9 @@ def __min_birth_max_death(persistence, band=0.0): min_birth = float(interval[1][0]) if band > 0.0: max_death += band + # can happen if only points at inf death + if min_birth == max_death: + max_death = max_death + 1. return (min_birth, max_death) @@ -54,7 +59,7 @@ def _array_handler(a): persistence-compatible list (padding with 0), so that the plot can be performed seamlessly. ''' - if isinstance(a[0][1], np.float64) or isinstance(a[0][1], float): + if isinstance(a[0][1], np.floating) or isinstance(a[0][1], float): return [[0, x] for x in a] else: return a @@ -88,7 +93,7 @@ def _matplotlib_can_use_tex(): from matplotlib import checkdep_usetex return checkdep_usetex(True) except ImportError: - print("This function is not available, you may be missing matplotlib.") + warnings.warn("This function is not available, you may be missing matplotlib.") def plot_persistence_barcode( @@ -157,53 +162,58 @@ def plot_persistence_barcode( for persistence_interval in diag[key]: persistence.append((key, persistence_interval)) else: - print("file " + persistence_file + " not found.") - return None + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), persistence_file) - persistence = _array_handler(persistence) - - persistence = _limit_to_max_intervals(persistence, max_intervals, - key = lambda life_time: life_time[1][1] - life_time[1][0]) - - if colormap == None: - colormap = plt.cm.Set1.colors - if axes == None: - _, axes = plt.subplots(1, 1) - - persistence = sorted(persistence, key=lambda birth: birth[1][0]) + try: + persistence = _array_handler(persistence) + persistence = _limit_to_max_intervals(persistence, max_intervals, + key = lambda life_time: life_time[1][1] - life_time[1][0]) + (min_birth, max_death) = __min_birth_max_death(persistence) + persistence = sorted(persistence, key=lambda birth: birth[1][0]) + except IndexError: + min_birth, max_death = 0., 1. + pass - (min_birth, max_death) = __min_birth_max_death(persistence) - ind = 0 delta = (max_death - min_birth) * inf_delta # Replace infinity values with max_death + delta for bar code to be more # readable infinity = max_death + delta axis_start = min_birth - delta + + if axes == None: + _, axes = plt.subplots(1, 1) + if colormap == None: + colormap = plt.cm.Set1.colors + ind = 0 + # Draw horizontal bars in loop for interval in reversed(persistence): - if float(interval[1][1]) != float("inf"): - # Finite death case - axes.barh( - ind, - (interval[1][1] - interval[1][0]), - height=0.8, - left=interval[1][0], - alpha=alpha, - color=colormap[interval[0]], - linewidth=0, - ) - else: - # Infinite death case for diagram to be nicer - axes.barh( - ind, - (infinity - interval[1][0]), - height=0.8, - left=interval[1][0], - alpha=alpha, - color=colormap[interval[0]], - linewidth=0, - ) - ind = ind + 1 + try: + if float(interval[1][1]) != float("inf"): + # Finite death case + axes.barh( + ind, + (interval[1][1] - interval[1][0]), + height=0.8, + left=interval[1][0], + alpha=alpha, + color=colormap[interval[0]], + linewidth=0, + ) + else: + # Infinite death case for diagram to be nicer + axes.barh( + ind, + (infinity - interval[1][0]), + height=0.8, + left=interval[1][0], + alpha=alpha, + color=colormap[interval[0]], + linewidth=0, + ) + ind = ind + 1 + except IndexError: + pass if legend: dimensions = list(set(item[0] for item in persistence)) @@ -218,11 +228,12 @@ def plot_persistence_barcode( axes.set_title("Persistence barcode", fontsize=fontsize) # Ends plot on infinity value and starts a little bit before min_birth - axes.axis([axis_start, infinity, 0, ind]) + if ind != 0: + axes.axis([axis_start, infinity, 0, ind]) return axes except ImportError: - print("This function is not available, you may be missing matplotlib.") + warnings.warn("This function is not available, you may be missing matplotlib.") def plot_persistence_diagram( @@ -296,20 +307,17 @@ def plot_persistence_diagram( for persistence_interval in diag[key]: persistence.append((key, persistence_interval)) else: - print("file " + persistence_file + " not found.") - return None - - persistence = _array_handler(persistence) - - persistence = _limit_to_max_intervals(persistence, max_intervals, - key = lambda life_time: life_time[1][1] - life_time[1][0]) + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), persistence_file) - if colormap == None: - colormap = plt.cm.Set1.colors - if axes == None: - _, axes = plt.subplots(1, 1) + try: + persistence = _array_handler(persistence) + persistence = _limit_to_max_intervals(persistence, max_intervals, + key = lambda life_time: life_time[1][1] - life_time[1][0]) + min_birth, max_death = __min_birth_max_death(persistence, band) + except IndexError: + min_birth, max_death = 0., 1. + pass - (min_birth, max_death) = __min_birth_max_death(persistence, band) delta = (max_death - min_birth) * inf_delta # Replace infinity values with max_death + delta for diagram to be more # readable @@ -317,33 +325,43 @@ def plot_persistence_diagram( axis_end = max_death + delta / 2 axis_start = min_birth - delta + if axes == None: + _, axes = plt.subplots(1, 1) + if colormap == None: + colormap = plt.cm.Set1.colors # bootstrap band if band > 0.0: x = np.linspace(axis_start, infinity, 1000) axes.fill_between(x, x, x + band, alpha=alpha, facecolor="red") # lower diag patch if greyblock: - axes.add_patch(mpatches.Polygon([[axis_start, axis_start], [axis_end, axis_start], [axis_end, axis_end]], fill=True, color='lightgrey')) + axes.add_patch(mpatches.Polygon([[axis_start, axis_start], [axis_end, axis_start], [axis_end, axis_end]], + fill=True, color='lightgrey')) + # line display of equation : birth = death + axes.plot([axis_start, axis_end], [axis_start, axis_end], linewidth=1.0, color="k") + # Draw points in loop pts_at_infty = False # Records presence of pts at infty for interval in reversed(persistence): - if float(interval[1][1]) != float("inf"): - # Finite death case - axes.scatter( - interval[1][0], - interval[1][1], - alpha=alpha, - color=colormap[interval[0]], - ) - else: - pts_at_infty = True - # Infinite death case for diagram to be nicer - axes.scatter( - interval[1][0], infinity, alpha=alpha, color=colormap[interval[0]] - ) + try: + if float(interval[1][1]) != float("inf"): + # Finite death case + axes.scatter( + interval[1][0], + interval[1][1], + alpha=alpha, + color=colormap[interval[0]], + ) + else: + pts_at_infty = True + # Infinite death case for diagram to be nicer + axes.scatter( + interval[1][0], infinity, alpha=alpha, color=colormap[interval[0]] + ) + except IndexError: + pass if pts_at_infty: # infinity line and text - axes.plot([axis_start, axis_end], [axis_start, axis_end], linewidth=1.0, color="k") axes.plot([axis_start, axis_end], [infinity, infinity], linewidth=1.0, color="k", alpha=alpha) # Infinity label yt = axes.get_yticks() @@ -371,7 +389,7 @@ def plot_persistence_diagram( return axes except ImportError: - print("This function is not available, you may be missing matplotlib.") + warnings.warn("This function is not available, you may be missing matplotlib.") def plot_persistence_density( @@ -461,27 +479,7 @@ def plot_persistence_density( persistence_file=persistence_file, only_this_dim=dimension ) else: - print("file " + persistence_file + " not found.") - return None - - if len(persistence) > 0: - persistence = _array_handler(persistence) - persistence_dim = np.array( - [ - (dim_interval[1][0], dim_interval[1][1]) - for dim_interval in persistence - if (dim_interval[0] == dimension) or (dimension is None) - ] - ) - - persistence_dim = persistence_dim[np.isfinite(persistence_dim[:, 1])] - - persistence_dim = np.array(_limit_to_max_intervals(persistence_dim, max_intervals, - key = lambda life_time: life_time[1] - life_time[0])) - - # Set as numpy array birth and death (remove undefined values - inf and NaN) - birth = persistence_dim[:, 0] - death = persistence_dim[:, 1] + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), persistence_file) # default cmap value cannot be done at argument definition level as matplotlib is not yet defined. if cmap is None: @@ -489,23 +487,56 @@ def plot_persistence_density( if axes == None: _, axes = plt.subplots(1, 1) + try: + if len(persistence) > 0: + # if not read from file but given by an argument + persistence = _array_handler(persistence) + persistence_dim = np.array( + [ + (dim_interval[1][0], dim_interval[1][1]) + for dim_interval in persistence + if (dim_interval[0] == dimension) or (dimension is None) + ] + ) + persistence_dim = persistence_dim[np.isfinite(persistence_dim[:, 1])] + persistence_dim = np.array(_limit_to_max_intervals(persistence_dim, max_intervals, + key = lambda life_time: life_time[1] - life_time[0])) + + # Set as numpy array birth and death (remove undefined values - inf and NaN) + birth = persistence_dim[:, 0] + death = persistence_dim[:, 1] + birth_min = birth.min() + birth_max = birth.max() + death_min = death.min() + death_max = death.max() + + # Evaluate a gaussian kde on a regular grid of nbins x nbins over data extents + k = kde.gaussian_kde([birth, death], bw_method=bw_method) + xi, yi = np.mgrid[ + birth_min : birth_max : nbins * 1j, + death_min : death_max : nbins * 1j, + ] + zi = k(np.vstack([xi.flatten(), yi.flatten()])) + # Make the plot + img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading='auto') + + # IndexError on empty diagrams, ValueError on only inf death values + except (IndexError, ValueError): + birth_min = 0. + birth_max = 1. + death_min = 0. + death_max = 1. + pass + # line display of equation : birth = death - x = np.linspace(death.min(), birth.max(), 1000) + x = np.linspace(death_min, birth_max, 1000) axes.plot(x, x, color="k", linewidth=1.0) - # Evaluate a gaussian kde on a regular grid of nbins x nbins over data extents - k = kde.gaussian_kde([birth, death], bw_method=bw_method) - xi, yi = np.mgrid[ - birth.min() : birth.max() : nbins * 1j, - death.min() : death.max() : nbins * 1j, - ] - zi = k(np.vstack([xi.flatten(), yi.flatten()])) - - # Make the plot - img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading='auto') - if greyblock: - axes.add_patch(mpatches.Polygon([[birth.min(), birth.min()], [death.max(), birth.min()], [death.max(), death.max()]], fill=True, color='lightgrey')) + axes.add_patch(mpatches.Polygon([[birth_min, birth_min], + [death_max, birth_min], + [death_max, death_max]], + fill=True, color='lightgrey')) if legend: plt.colorbar(img, ax=axes) @@ -517,6 +548,4 @@ def plot_persistence_density( return axes except ImportError: - print( - "This function is not available, you may be missing matplotlib and/or scipy." - ) + warnings.warn("This function is not available, you may be missing matplotlib and/or scipy.") -- cgit v1.2.3 From 72f72770ccf0d1ddb78a7f23103b2777f407e72c Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 17 Jun 2021 16:29:18 +0200 Subject: Forgot to add test file for graphical --- .../test/test_persistence_graphical_tools.py | 105 +++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/python/test/test_persistence_graphical_tools.py (limited to 'src/python') diff --git a/src/python/test/test_persistence_graphical_tools.py b/src/python/test/test_persistence_graphical_tools.py new file mode 100644 index 00000000..1ad1ae23 --- /dev/null +++ b/src/python/test/test_persistence_graphical_tools.py @@ -0,0 +1,105 @@ +""" This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + Author(s): Vincent Rouvreau + + Copyright (C) 2021 Inria + + Modification(s): + - YYYY/MM Author: Description of the modification +""" + +import gudhi as gd +import numpy as np +import matplotlib as plt +import pytest + +def test_array_handler(): + diags = np.array([[1, 2], [3, 4], [5, 6]], np.float) + arr_diags = gd.persistence_graphical_tools._array_handler(diags) + for idx in range(len(diags)): + assert arr_diags[idx][0] == 0 + np.testing.assert_array_equal(arr_diags[idx][1], diags[idx]) + + diags = [(1., 2.), (3., 4.), (5., 6.)] + arr_diags = gd.persistence_graphical_tools._array_handler(diags) + for idx in range(len(diags)): + assert arr_diags[idx][0] == 0 + assert arr_diags[idx][1] == diags[idx] + + diags = [(0, (1., 2.)), (0, (3., 4.)), (0, (5., 6.))] + assert gd.persistence_graphical_tools._array_handler(diags) == diags + +def test_min_birth_max_death(): + diags = [ + (0, (0., float("inf"))), + (0, (0.0983494, float("inf"))), + (0, (0., 0.122545)), + (0, (0., 0.12047)), + (0, (0., 0.118398)), + (0, (0.118398, 1.)), + (0, (0., 0.117908)), + (0, (0., 0.112307)), + (0, (0., 0.107535)), + (0, (0., 0.106382)), + ] + assert gd.persistence_graphical_tools.__min_birth_max_death(diags) == (0., 1.) + assert gd.persistence_graphical_tools.__min_birth_max_death(diags, band=4.) == (0., 5.) + +def test_limit_min_birth_max_death(): + diags = [ + (0, (2., float("inf"))), + (0, (2., float("inf"))), + ] + assert gd.persistence_graphical_tools.__min_birth_max_death(diags) == (2., 3.) + assert gd.persistence_graphical_tools.__min_birth_max_death(diags, band = 4.) == (2., 6.) + +def test_limit_to_max_intervals(): + diags = [ + (0, (0., float("inf"))), + (0, (0.0983494, float("inf"))), + (0, (0., 0.122545)), + (0, (0., 0.12047)), + (0, (0., 0.118398)), + (0, (0.118398, 1.)), + (0, (0., 0.117908)), + (0, (0., 0.112307)), + (0, (0., 0.107535)), + (0, (0., 0.106382)), + ] + # check no warnings if max_intervals equals to the diagrams number + with pytest.warns(None) as record: + truncated_diags = gd.persistence_graphical_tools._limit_to_max_intervals(diags, 10, + key = lambda life_time: life_time[1][1] - life_time[1][0]) + # check diagrams are not sorted + assert truncated_diags == diags + assert len(record) == 0 + + # check warning if max_intervals lower than the diagrams number + with pytest.warns(UserWarning) as record: + truncated_diags = gd.persistence_graphical_tools._limit_to_max_intervals(diags, 5, + key = lambda life_time: life_time[1][1] - life_time[1][0]) + # check diagrams are truncated and sorted by life time + assert truncated_diags == [(0, (0., float("inf"))), + (0, (0.0983494, float("inf"))), + (0, (0.118398, 1.0)), + (0, (0., 0.122545)), + (0, (0., 0.12047))] + assert len(record) == 1 + +def _limit_plot_persistence(function): + pplot = function(persistence=[()]) + assert issubclass(type(pplot), plt.axes.SubplotBase) + pplot = function(persistence=[(0, float("inf"))]) + assert issubclass(type(pplot), plt.axes.SubplotBase) + +def test_limit_plot_persistence(): + for function in [gd.plot_persistence_barcode, gd.plot_persistence_diagram, gd.plot_persistence_density]: + _limit_plot_persistence(function) + +def _non_existing_persistence_file(function): + with pytest.raises(FileNotFoundError): + function(persistence_file="pouetpouettralala.toubiloubabdou") + +def test_non_existing_persistence_file(): + for function in [gd.plot_persistence_barcode, gd.plot_persistence_diagram, gd.plot_persistence_density]: + _non_existing_persistence_file(function) -- cgit v1.2.3 From f9b1e50a6adaadf88c4940cb4214f9ecef542144 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 22 Jun 2021 18:47:46 +0200 Subject: black -l 120 modified files --- src/python/gudhi/persistence_graphical_tools.py | 144 +++++++++++---------- .../test/test_persistence_graphical_tools.py | 82 +++++++----- 2 files changed, 120 insertions(+), 106 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 9bfc19e0..837218ca 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -25,6 +25,7 @@ __license__ = "MIT" _gudhi_matplotlib_use_tex = True + def __min_birth_max_death(persistence, band=0.0): """This function returns (min_birth, max_death) from the persistence. @@ -49,23 +50,24 @@ def __min_birth_max_death(persistence, band=0.0): max_death += band # can happen if only points at inf death if min_birth == max_death: - max_death = max_death + 1. + max_death = max_death + 1.0 return (min_birth, max_death) def _array_handler(a): - ''' + """ :param a: if array, assumes it is a (n x 2) np.array and return a persistence-compatible list (padding with 0), so that the plot can be performed seamlessly. - ''' + """ if isinstance(a[0][1], np.floating) or isinstance(a[0][1], float): return [[0, x] for x in a] else: return a + def _limit_to_max_intervals(persistence, max_intervals, key): - '''This function returns truncated persistence if length is bigger than max_intervals. + """This function returns truncated persistence if length is bigger than max_intervals. :param persistence: Persistence intervals values list. Can be grouped by dimension or not. :type persistence: an array of (dimension, array of (birth, death)) or an array of (birth, death). :param max_intervals: maximal number of intervals to display. @@ -74,16 +76,18 @@ def _limit_to_max_intervals(persistence, max_intervals, key): :type max_intervals: int. :param key: key function for sort algorithm. :type key: function or lambda. - ''' + """ if max_intervals > 0 and max_intervals < len(persistence): - warnings.warn('There are %s intervals given as input, whereas max_intervals is set to %s.' % - (len(persistence), max_intervals) - ) + warnings.warn( + "There are %s intervals given as input, whereas max_intervals is set to %s." + % (len(persistence), max_intervals) + ) # Sort by life time, then takes only the max_intervals elements - return sorted(persistence, key = key, reverse = True)[:max_intervals] + return sorted(persistence, key=key, reverse=True)[:max_intervals] else: return persistence + @lru_cache(maxsize=1) def _matplotlib_can_use_tex(): """This function returns True if matplotlib can deal with LaTeX, False otherwise. @@ -91,6 +95,7 @@ def _matplotlib_can_use_tex(): """ try: from matplotlib import checkdep_usetex + return checkdep_usetex(True) except ImportError: warnings.warn("This function is not available, you may be missing matplotlib.") @@ -144,20 +149,19 @@ def plot_persistence_barcode( import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib import rc + if _gudhi_matplotlib_use_tex and _matplotlib_can_use_tex(): - plt.rc('text', usetex=True) - plt.rc('font', family='serif') + plt.rc("text", usetex=True) + plt.rc("font", family="serif") else: - plt.rc('text', usetex=False) - plt.rc('font', family='DejaVu Sans') + plt.rc("text", usetex=False) + plt.rc("font", family="DejaVu Sans") if persistence_file != "": if path.isfile(persistence_file): # Reset persistence persistence = [] - diag = read_persistence_intervals_grouped_by_dimension( - persistence_file=persistence_file - ) + diag = read_persistence_intervals_grouped_by_dimension(persistence_file=persistence_file) for key in diag.keys(): for persistence_interval in diag[key]: persistence.append((key, persistence_interval)) @@ -166,12 +170,13 @@ def plot_persistence_barcode( try: persistence = _array_handler(persistence) - persistence = _limit_to_max_intervals(persistence, max_intervals, - key = lambda life_time: life_time[1][1] - life_time[1][0]) + persistence = _limit_to_max_intervals( + persistence, max_intervals, key=lambda life_time: life_time[1][1] - life_time[1][0] + ) (min_birth, max_death) = __min_birth_max_death(persistence) persistence = sorted(persistence, key=lambda birth: birth[1][0]) except IndexError: - min_birth, max_death = 0., 1. + min_birth, max_death = 0.0, 1.0 pass delta = (max_death - min_birth) * inf_delta @@ -218,11 +223,7 @@ def plot_persistence_barcode( if legend: dimensions = list(set(item[0] for item in persistence)) axes.legend( - handles=[ - mpatches.Patch(color=colormap[dim], label=str(dim)) - for dim in dimensions - ], - loc="lower right", + handles=[mpatches.Patch(color=colormap[dim], label=str(dim)) for dim in dimensions], loc="lower right", ) axes.set_title("Persistence barcode", fontsize=fontsize) @@ -247,7 +248,7 @@ def plot_persistence_diagram( colormap=None, axes=None, fontsize=16, - greyblock=True + greyblock=True, ): """This function plots the persistence diagram from persistence values list, a np.array of shape (N x 2) representing a diagram in a single @@ -289,20 +290,19 @@ def plot_persistence_diagram( import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib import rc + if _gudhi_matplotlib_use_tex and _matplotlib_can_use_tex(): - plt.rc('text', usetex=True) - plt.rc('font', family='serif') + plt.rc("text", usetex=True) + plt.rc("font", family="serif") else: - plt.rc('text', usetex=False) - plt.rc('font', family='DejaVu Sans') + plt.rc("text", usetex=False) + plt.rc("font", family="DejaVu Sans") if persistence_file != "": if path.isfile(persistence_file): # Reset persistence persistence = [] - diag = read_persistence_intervals_grouped_by_dimension( - persistence_file=persistence_file - ) + diag = read_persistence_intervals_grouped_by_dimension(persistence_file=persistence_file) for key in diag.keys(): for persistence_interval in diag[key]: persistence.append((key, persistence_interval)) @@ -311,11 +311,12 @@ def plot_persistence_diagram( try: persistence = _array_handler(persistence) - persistence = _limit_to_max_intervals(persistence, max_intervals, - key = lambda life_time: life_time[1][1] - life_time[1][0]) + persistence = _limit_to_max_intervals( + persistence, max_intervals, key=lambda life_time: life_time[1][1] - life_time[1][0] + ) min_birth, max_death = __min_birth_max_death(persistence, band) except IndexError: - min_birth, max_death = 0., 1. + min_birth, max_death = 0.0, 1.0 pass delta = (max_death - min_birth) * inf_delta @@ -335,8 +336,13 @@ def plot_persistence_diagram( axes.fill_between(x, x, x + band, alpha=alpha, facecolor="red") # lower diag patch if greyblock: - axes.add_patch(mpatches.Polygon([[axis_start, axis_start], [axis_end, axis_start], [axis_end, axis_end]], - fill=True, color='lightgrey')) + axes.add_patch( + mpatches.Polygon( + [[axis_start, axis_start], [axis_end, axis_start], [axis_end, axis_end]], + fill=True, + color="lightgrey", + ) + ) # line display of equation : birth = death axes.plot([axis_start, axis_end], [axis_start, axis_end], linewidth=1.0, color="k") @@ -347,17 +353,12 @@ def plot_persistence_diagram( if float(interval[1][1]) != float("inf"): # Finite death case axes.scatter( - interval[1][0], - interval[1][1], - alpha=alpha, - color=colormap[interval[0]], + interval[1][0], interval[1][1], alpha=alpha, color=colormap[interval[0]], ) else: pts_at_infty = True # Infinite death case for diagram to be nicer - axes.scatter( - interval[1][0], infinity, alpha=alpha, color=colormap[interval[0]] - ) + axes.scatter(interval[1][0], infinity, alpha=alpha, color=colormap[interval[0]]) except IndexError: pass if pts_at_infty: @@ -365,27 +366,22 @@ def plot_persistence_diagram( axes.plot([axis_start, axis_end], [infinity, infinity], linewidth=1.0, color="k", alpha=alpha) # Infinity label yt = axes.get_yticks() - yt = yt[np.where(yt < axis_end)] # to avoid ploting ticklabel higher than infinity + yt = yt[np.where(yt < axis_end)] # to avoid ploting ticklabel higher than infinity yt = np.append(yt, infinity) ytl = ["%.3f" % e for e in yt] # to avoid float precision error - ytl[-1] = r'$+\infty$' + ytl[-1] = r"$+\infty$" axes.set_yticks(yt) axes.set_yticklabels(ytl) if legend: dimensions = list(set(item[0] for item in persistence)) - axes.legend( - handles=[ - mpatches.Patch(color=colormap[dim], label=str(dim)) - for dim in dimensions - ] - ) + axes.legend(handles=[mpatches.Patch(color=colormap[dim], label=str(dim)) for dim in dimensions]) axes.set_xlabel("Birth", fontsize=fontsize) axes.set_ylabel("Death", fontsize=fontsize) axes.set_title("Persistence diagram", fontsize=fontsize) # Ends plot on infinity value and starts a little bit before min_birth - axes.axis([axis_start, axis_end, axis_start, infinity + delta/2]) + axes.axis([axis_start, axis_end, axis_start, infinity + delta / 2]) return axes except ImportError: @@ -403,7 +399,7 @@ def plot_persistence_density( legend=False, axes=None, fontsize=16, - greyblock=False + greyblock=False, ): """This function plots the persistence density from persistence values list, np.array of shape (N x 2) representing a diagram @@ -463,12 +459,13 @@ def plot_persistence_density( import matplotlib.patches as mpatches from scipy.stats import kde from matplotlib import rc + if _gudhi_matplotlib_use_tex and _matplotlib_can_use_tex(): - plt.rc('text', usetex=True) - plt.rc('font', family='serif') + plt.rc("text", usetex=True) + plt.rc("font", family="serif") else: - plt.rc('text', usetex=False) - plt.rc('font', family='DejaVu Sans') + plt.rc("text", usetex=False) + plt.rc("font", family="DejaVu Sans") if persistence_file != "": if dimension is None: @@ -499,8 +496,11 @@ def plot_persistence_density( ] ) persistence_dim = persistence_dim[np.isfinite(persistence_dim[:, 1])] - persistence_dim = np.array(_limit_to_max_intervals(persistence_dim, max_intervals, - key = lambda life_time: life_time[1] - life_time[0])) + persistence_dim = np.array( + _limit_to_max_intervals( + persistence_dim, max_intervals, key=lambda life_time: life_time[1] - life_time[0] + ) + ) # Set as numpy array birth and death (remove undefined values - inf and NaN) birth = persistence_dim[:, 0] @@ -513,19 +513,18 @@ def plot_persistence_density( # Evaluate a gaussian kde on a regular grid of nbins x nbins over data extents k = kde.gaussian_kde([birth, death], bw_method=bw_method) xi, yi = np.mgrid[ - birth_min : birth_max : nbins * 1j, - death_min : death_max : nbins * 1j, + birth_min : birth_max : nbins * 1j, death_min : death_max : nbins * 1j, ] zi = k(np.vstack([xi.flatten(), yi.flatten()])) # Make the plot - img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading='auto') + img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading="auto") # IndexError on empty diagrams, ValueError on only inf death values except (IndexError, ValueError): - birth_min = 0. - birth_max = 1. - death_min = 0. - death_max = 1. + birth_min = 0.0 + birth_max = 1.0 + death_min = 0.0 + death_max = 1.0 pass # line display of equation : birth = death @@ -533,10 +532,13 @@ def plot_persistence_density( axes.plot(x, x, color="k", linewidth=1.0) if greyblock: - axes.add_patch(mpatches.Polygon([[birth_min, birth_min], - [death_max, birth_min], - [death_max, death_max]], - fill=True, color='lightgrey')) + axes.add_patch( + mpatches.Polygon( + [[birth_min, birth_min], [death_max, birth_min], [death_max, death_max]], + fill=True, + color="lightgrey", + ) + ) if legend: plt.colorbar(img, ax=axes) diff --git a/src/python/test/test_persistence_graphical_tools.py b/src/python/test/test_persistence_graphical_tools.py index 1ad1ae23..7d9bae90 100644 --- a/src/python/test/test_persistence_graphical_tools.py +++ b/src/python/test/test_persistence_graphical_tools.py @@ -13,6 +13,7 @@ import numpy as np import matplotlib as plt import pytest + def test_array_handler(): diags = np.array([[1, 2], [3, 4], [5, 6]], np.float) arr_diags = gd.persistence_graphical_tools._array_handler(diags) @@ -20,86 +21,97 @@ def test_array_handler(): assert arr_diags[idx][0] == 0 np.testing.assert_array_equal(arr_diags[idx][1], diags[idx]) - diags = [(1., 2.), (3., 4.), (5., 6.)] + diags = [(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)] arr_diags = gd.persistence_graphical_tools._array_handler(diags) for idx in range(len(diags)): assert arr_diags[idx][0] == 0 assert arr_diags[idx][1] == diags[idx] - diags = [(0, (1., 2.)), (0, (3., 4.)), (0, (5., 6.))] + diags = [(0, (1.0, 2.0)), (0, (3.0, 4.0)), (0, (5.0, 6.0))] assert gd.persistence_graphical_tools._array_handler(diags) == diags + def test_min_birth_max_death(): diags = [ - (0, (0., float("inf"))), + (0, (0.0, float("inf"))), (0, (0.0983494, float("inf"))), - (0, (0., 0.122545)), - (0, (0., 0.12047)), - (0, (0., 0.118398)), - (0, (0.118398, 1.)), - (0, (0., 0.117908)), - (0, (0., 0.112307)), - (0, (0., 0.107535)), - (0, (0., 0.106382)), + (0, (0.0, 0.122545)), + (0, (0.0, 0.12047)), + (0, (0.0, 0.118398)), + (0, (0.118398, 1.0)), + (0, (0.0, 0.117908)), + (0, (0.0, 0.112307)), + (0, (0.0, 0.107535)), + (0, (0.0, 0.106382)), ] - assert gd.persistence_graphical_tools.__min_birth_max_death(diags) == (0., 1.) - assert gd.persistence_graphical_tools.__min_birth_max_death(diags, band=4.) == (0., 5.) + assert gd.persistence_graphical_tools.__min_birth_max_death(diags) == (0.0, 1.0) + assert gd.persistence_graphical_tools.__min_birth_max_death(diags, band=4.0) == (0.0, 5.0) + def test_limit_min_birth_max_death(): diags = [ - (0, (2., float("inf"))), - (0, (2., float("inf"))), + (0, (2.0, float("inf"))), + (0, (2.0, float("inf"))), ] - assert gd.persistence_graphical_tools.__min_birth_max_death(diags) == (2., 3.) - assert gd.persistence_graphical_tools.__min_birth_max_death(diags, band = 4.) == (2., 6.) + assert gd.persistence_graphical_tools.__min_birth_max_death(diags) == (2.0, 3.0) + assert gd.persistence_graphical_tools.__min_birth_max_death(diags, band=4.0) == (2.0, 6.0) + def test_limit_to_max_intervals(): diags = [ - (0, (0., float("inf"))), + (0, (0.0, float("inf"))), (0, (0.0983494, float("inf"))), - (0, (0., 0.122545)), - (0, (0., 0.12047)), - (0, (0., 0.118398)), - (0, (0.118398, 1.)), - (0, (0., 0.117908)), - (0, (0., 0.112307)), - (0, (0., 0.107535)), - (0, (0., 0.106382)), + (0, (0.0, 0.122545)), + (0, (0.0, 0.12047)), + (0, (0.0, 0.118398)), + (0, (0.118398, 1.0)), + (0, (0.0, 0.117908)), + (0, (0.0, 0.112307)), + (0, (0.0, 0.107535)), + (0, (0.0, 0.106382)), ] # check no warnings if max_intervals equals to the diagrams number with pytest.warns(None) as record: - truncated_diags = gd.persistence_graphical_tools._limit_to_max_intervals(diags, 10, - key = lambda life_time: life_time[1][1] - life_time[1][0]) + truncated_diags = gd.persistence_graphical_tools._limit_to_max_intervals( + diags, 10, key=lambda life_time: life_time[1][1] - life_time[1][0] + ) # check diagrams are not sorted assert truncated_diags == diags assert len(record) == 0 # check warning if max_intervals lower than the diagrams number with pytest.warns(UserWarning) as record: - truncated_diags = gd.persistence_graphical_tools._limit_to_max_intervals(diags, 5, - key = lambda life_time: life_time[1][1] - life_time[1][0]) + truncated_diags = gd.persistence_graphical_tools._limit_to_max_intervals( + diags, 5, key=lambda life_time: life_time[1][1] - life_time[1][0] + ) # check diagrams are truncated and sorted by life time - assert truncated_diags == [(0, (0., float("inf"))), - (0, (0.0983494, float("inf"))), - (0, (0.118398, 1.0)), - (0, (0., 0.122545)), - (0, (0., 0.12047))] + assert truncated_diags == [ + (0, (0.0, float("inf"))), + (0, (0.0983494, float("inf"))), + (0, (0.118398, 1.0)), + (0, (0.0, 0.122545)), + (0, (0.0, 0.12047)), + ] assert len(record) == 1 + def _limit_plot_persistence(function): pplot = function(persistence=[()]) assert issubclass(type(pplot), plt.axes.SubplotBase) pplot = function(persistence=[(0, float("inf"))]) assert issubclass(type(pplot), plt.axes.SubplotBase) + def test_limit_plot_persistence(): for function in [gd.plot_persistence_barcode, gd.plot_persistence_diagram, gd.plot_persistence_density]: _limit_plot_persistence(function) + def _non_existing_persistence_file(function): with pytest.raises(FileNotFoundError): function(persistence_file="pouetpouettralala.toubiloubabdou") + def test_non_existing_persistence_file(): for function in [gd.plot_persistence_barcode, gd.plot_persistence_diagram, gd.plot_persistence_density]: _non_existing_persistence_file(function) -- cgit v1.2.3 From 87da488d24c70cbd470ad1c2dae762af68cd227e Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 24 Aug 2021 14:50:12 +0200 Subject: code review: isinstance can take several instances types as a list --- src/python/gudhi/persistence_graphical_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 837218ca..70d550ab 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -60,7 +60,7 @@ def _array_handler(a): persistence-compatible list (padding with 0), so that the plot can be performed seamlessly. """ - if isinstance(a[0][1], np.floating) or isinstance(a[0][1], float): + if isinstance(a[0][1], (np.floating, float)): return [[0, x] for x in a] else: return a -- cgit v1.2.3 From f339fb0ef1dbe74cdf4912dc256ec3648308ec2b Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 6 Dec 2021 15:29:37 +0100 Subject: Modify minimal requirements for python typing. Fix no cgal compilation --- .github/next_release.md | 3 + .../modules/GUDHI_third_party_libraries.cmake | 7 +- src/python/CMakeLists.txt | 1 + src/python/doc/installation.rst | 93 +++++++++++----------- src/python/pyproject.toml | 2 +- src/python/setup.py.in | 4 +- 6 files changed, 60 insertions(+), 50 deletions(-) (limited to 'src/python') diff --git a/.github/next_release.md b/.github/next_release.md index 170c7494..0d4673bd 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -9,6 +9,9 @@ Below is a list of changes made since GUDHI 3.4.0: - [Coxeter triangulation](https://gudhi.inria.fr/doc/latest/group__coxeter__triangulation.html) - constructs a piecewise-linear approximation of an m-dimensional smooth manifold embedded in R^d using an ambient triangulation. +- [Python installation](link) + - Python >= 3.5 and cython >= 0.27 are now required for Python typing purpose. + - [Module](link) - ... diff --git a/src/cmake/modules/GUDHI_third_party_libraries.cmake b/src/cmake/modules/GUDHI_third_party_libraries.cmake index 023061f1..deaea588 100644 --- a/src/cmake/modules/GUDHI_third_party_libraries.cmake +++ b/src/cmake/modules/GUDHI_third_party_libraries.cmake @@ -6,6 +6,10 @@ find_package(Boost 1.56.0 QUIET OPTIONAL_COMPONENTS filesystem unit_test_framewo if(NOT Boost_VERSION) message(FATAL_ERROR "NOTICE: This program requires Boost and will not be compiled.") endif(NOT Boost_VERSION) +INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) +message(STATUS "boost include dirs:" ${Boost_INCLUDE_DIRS}) +message(STATUS "boost library dirs:" ${Boost_LIBRARY_DIRS}) + find_package(GMP) if(GMP_FOUND) @@ -89,9 +93,6 @@ add_definitions( -DBOOST_ALL_DYN_LINK ) # problem on Mac with boost_system and boost_thread add_definitions( -DBOOST_SYSTEM_NO_DEPRECATED ) -message(STATUS "boost include dirs:" ${Boost_INCLUDE_DIRS}) -message(STATUS "boost library dirs:" ${Boost_LIBRARY_DIRS}) - # Find the correct Python interpreter. # Can be set with -DPYTHON_EXECUTABLE=/usr/bin/python3 or -DPython_ADDITIONAL_VERSIONS=3 for instance. find_package( PythonInterp ) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 4a017251..12534b86 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -175,6 +175,7 @@ if(PYTHONINTERP_FOUND) set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'euclidean_strong_witness_complex', ") endif () + add_gudhi_debug_info("Boost version ${Boost_VERSION}") if(CGAL_FOUND) # Add CGAL compilation args if(CGAL_HEADER_ONLY) diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index 35c344e3..6ed53960 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -33,25 +33,19 @@ Compiling These instructions are for people who want to compile gudhi from source, they are unnecessary if you installed a binary package of Gudhi as above. They assume that you have downloaded a `release `_, -with a name like `gudhi.3.2.0.tar.gz`, then run `tar xf gudhi.3.2.0.tar.gz`, which -created a directory `gudhi.3.2.0`, hereinafter referred to as `/path-to-gudhi/`. +with a name like `gudhi.3.X.X.tar.gz`, then run `tar xf gudhi.3.X.X.tar.gz`, which +created a directory `gudhi.3.X.X`, hereinafter referred to as `/path-to-gudhi/`. If you are instead using a git checkout, beware that the paths are a bit different, and in particular the `python/` subdirectory is actually `src/python/` there. The library uses c++14 and requires `Boost `_ :math:`\geq` 1.56.0, `CMake `_ :math:`\geq` 3.5 to generate makefiles, -`NumPy `_ :math:`\geq` 1.15.0, `Cython `_ and -`pybind11 `_ to compile -the GUDHI Python module. -It is a multi-platform library and compiles on Linux, Mac OSX and Visual -Studio 2017 or later. +Python :math:`\geq` 3.5, `NumPy `_ :math:`\geq` 1.15.0, `Cython `_ +:math:`\geq` 0.27 and `pybind11 `_ to compile the GUDHI Python module. +It is a multi-platform library and compiles on Linux, Mac OSX and Visual Studio 2017 or later. -On `Windows `_ , only Python -:math:`\geq` 3.5 are available because of the required Visual Studio version. - -On other systems, if you have several Python/python installed, the version 2.X -will be used by default, but you can force it by adding +If you have several Python/python installed, the version 2.X may be used by default, but you can force it by adding :code:`-DPython_ADDITIONAL_VERSIONS=3` to the cmake command. GUDHI Python module compilation @@ -143,53 +137,62 @@ If :code:`import gudhi` succeeds, please have a look to debug information: .. code-block:: python import gudhi - print(gudhi.__debug_info__) +print(gudhi.__debug_info__) +print("+ Installed modules are: " + gudhi.__available_modules) +print("+ Missing modules are: " + gudhi.__missing_modules) You shall have something like: .. code-block:: none - Python version 2.7.15 - Cython version 0.26.1 - Numpy version 1.14.1 - Eigen3 version 3.1.1 - Installed modules are: off_reader;simplex_tree;rips_complex; - cubical_complex;periodic_cubical_complex;reader_utils;witness_complex; - strong_witness_complex;alpha_complex; - Missing modules are: bottleneck_distance;nerve_gic;subsampling; - tangential_complex;persistence_graphical_tools; - euclidean_witness_complex;euclidean_strong_witness_complex; - CGAL version 4.7.1000 - GMP_LIBRARIES = /usr/lib/x86_64-linux-gnu/libgmp.so - GMPXX_LIBRARIES = /usr/lib/x86_64-linux-gnu/libgmpxx.so - TBB version 9107 found and used + Pybind11 version 2.8.1 + Python version 3.7.12 + Cython version 0.29.25 + Numpy version 1.21.4 + Boost version 1.77.0 + print("+ Installed modules are: " + gudhi.__available_modules) + + Installed modules are: off_reader;simplex_tree;rips_complex;cubical_complex;periodic_cubical_complex; + persistence_graphical_tools;reader_utils;witness_complex;strong_witness_complex; + + Missing modules are: bottleneck;nerve_gic;subsampling;tangential_complex;alpha_complex;euclidean_witness_complex; + euclidean_strong_witness_complex; -Here, you can see that bottleneck_distance, nerve_gic, subsampling and -tangential_complex are missing because of the CGAL version. -persistence_graphical_tools is not available as matplotlib is not -available. +Here, you can see that are requiring CGAL are missing because CGAL is not installed. +:code:`persistence_graphical_tools` is installed, but +`its functions `_ will produce an error as +matplotlib is not available. Unitary tests cannot be run as pytest is missing. A complete configuration would be : .. code-block:: none - Python version 3.6.5 - Cython version 0.28.2 - Pytest version 3.3.2 - Matplotlib version 2.2.2 - Numpy version 1.14.5 - Eigen3 version 3.3.4 - Installed modules are: off_reader;simplex_tree;rips_complex; - cubical_complex;periodic_cubical_complex;persistence_graphical_tools; - reader_utils;witness_complex;strong_witness_complex; - persistence_graphical_tools;bottleneck_distance;nerve_gic;subsampling; - tangential_complex;alpha_complex;euclidean_witness_complex; - euclidean_strong_witness_complex; - CGAL header only version 4.11.0 + Pybind11 version 2.8.1 + Python version 3.9.7 + Cython version 0.29.24 + Pytest version 6.2.5 + Matplotlib version 3.5.0 + Numpy version 1.21.4 + Scipy version 1.7.3 + Scikit-learn version 1.0.1 + POT version 0.8.0 + HNSWlib found + PyKeOps version [pyKeOps]: 1.5 + EagerPy version 0.30.0 + TensorFlow version 2.7.0 + Sphinx version 4.3.0 + Sphinx-paramlinks version 0.5.2 + python_docs_theme found + Eigen3 version 3.4.0 + Boost version 1.74.0 + CGAL version 5.3 GMP_LIBRARIES = /usr/lib/x86_64-linux-gnu/libgmp.so GMPXX_LIBRARIES = /usr/lib/x86_64-linux-gnu/libgmpxx.so - TBB version 9107 found and used + MPFR_LIBRARIES = /usr/lib/x86_64-linux-gnu/libmpfr.so + + Installed modules are: bottleneck;off_reader;simplex_tree;rips_complex;cubical_complex;periodic_cubical_complex; + persistence_graphical_tools;reader_utils;witness_complex;strong_witness_complex;nerve_gic;subsampling; + tangential_complex;alpha_complex;euclidean_witness_complex;euclidean_strong_witness_complex; + + Missing modules are: + Documentation ============= diff --git a/src/python/pyproject.toml b/src/python/pyproject.toml index a9fb4985..55b64466 100644 --- a/src/python/pyproject.toml +++ b/src/python/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools", "wheel", "numpy>=1.15.0", "cython", "pybind11"] +requires = ["setuptools>=24.2.0", "wheel", "numpy>=1.15.0", "cython>=0.27", "pybind11"] build-backend = "setuptools.build_meta" diff --git a/src/python/setup.py.in b/src/python/setup.py.in index 23746998..2c67c2c5 100644 --- a/src/python/setup.py.in +++ b/src/python/setup.py.in @@ -5,6 +5,7 @@ Copyright (C) 2019 Inria Modification(s): + - 2021/12 Vincent Rouvreau: Python 3.5 as minimal version - YYYY/MM Author: Description of the modification """ @@ -43,7 +44,7 @@ for module in cython_modules: include_dirs=include_dirs, runtime_library_dirs=runtime_library_dirs,)) -ext_modules = cythonize(ext_modules, compiler_directives={'language_level': str(sys.version_info[0])}) +ext_modules = cythonize(ext_modules, compiler_directives={'language_level': '3'}) for module in pybind11_modules: my_include_dirs = include_dirs + [pybind11.get_include(False), pybind11.get_include(True)] @@ -86,6 +87,7 @@ setup( long_description_content_type='text/x-rst', long_description=long_description, ext_modules = ext_modules, + python_requires='>=3.5.0', install_requires = ['numpy >= 1.15.0',], package_data={"": ["*.dll"], }, ) -- cgit v1.2.3 From 54f6f178d30f59a35758421e285cfac946927064 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 6 Dec 2021 17:36:03 +0100 Subject: Doc review: rephrase --- .github/next_release.md | 2 +- src/python/doc/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/python') diff --git a/.github/next_release.md b/.github/next_release.md index 0d4673bd..5a1215df 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -10,7 +10,7 @@ Below is a list of changes made since GUDHI 3.4.0: - constructs a piecewise-linear approximation of an m-dimensional smooth manifold embedded in R^d using an ambient triangulation. - [Python installation](link) - - Python >= 3.5 and cython >= 0.27 are now required for Python typing purpose. + - Python >= 3.5 and cython >= 0.27 are now required. - [Module](link) - ... diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index 6ed53960..e8321fed 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -156,7 +156,7 @@ You shall have something like: + Missing modules are: bottleneck;nerve_gic;subsampling;tangential_complex;alpha_complex;euclidean_witness_complex; euclidean_strong_witness_complex; -Here, you can see that are requiring CGAL are missing because CGAL is not installed. +Here, you can see that the modules that need CGAL are missing, because CGAL is not installed. :code:`persistence_graphical_tools` is installed, but `its functions `_ will produce an error as matplotlib is not available. -- cgit v1.2.3 From f221db3627c3f71a76f57c3f7b5308b00f1d4248 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Fri, 21 Jan 2022 11:26:46 +0100 Subject: [skip ci] Code review: restore TBB version for the complete configuration example --- src/python/doc/installation.rst | 1 + 1 file changed, 1 insertion(+) (limited to 'src/python') diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index e8321fed..8fca94f5 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -188,6 +188,7 @@ A complete configuration would be : GMP_LIBRARIES = /usr/lib/x86_64-linux-gnu/libgmp.so GMPXX_LIBRARIES = /usr/lib/x86_64-linux-gnu/libgmpxx.so MPFR_LIBRARIES = /usr/lib/x86_64-linux-gnu/libmpfr.so + TBB version 9107 found and used + Installed modules are: bottleneck;off_reader;simplex_tree;rips_complex;cubical_complex;periodic_cubical_complex; persistence_graphical_tools;reader_utils;witness_complex;strong_witness_complex;nerve_gic;subsampling; tangential_complex;alpha_complex;euclidean_witness_complex;euclidean_strong_witness_complex; -- cgit v1.2.3 From 6b6a2ca097e38ca8d1601e764c8f3a6eecfd19e7 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 24 Jan 2022 10:46:27 +0100 Subject: doc review: remove unwanted line. Use 'import gudhi as gd' --- src/python/doc/installation.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src/python') diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index 8fca94f5..873f7870 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -33,8 +33,8 @@ Compiling These instructions are for people who want to compile gudhi from source, they are unnecessary if you installed a binary package of Gudhi as above. They assume that you have downloaded a `release `_, -with a name like `gudhi.3.X.X.tar.gz`, then run `tar xf gudhi.3.X.X.tar.gz`, which -created a directory `gudhi.3.X.X`, hereinafter referred to as `/path-to-gudhi/`. +with a name like `gudhi.3.X.Y.tar.gz`, then run `tar xf gudhi.3.X.Y.tar.gz`, which +created a directory `gudhi.3.X.Y`, hereinafter referred to as `/path-to-gudhi/`. If you are instead using a git checkout, beware that the paths are a bit different, and in particular the `python/` subdirectory is actually `src/python/` there. @@ -136,10 +136,10 @@ If :code:`import gudhi` succeeds, please have a look to debug information: .. code-block:: python - import gudhi -print(gudhi.__debug_info__) -print("+ Installed modules are: " + gudhi.__available_modules) -print("+ Missing modules are: " + gudhi.__missing_modules) + import gudhi as gd + print(gd.__debug_info__) + print("+ Installed modules are: " + gd.__available_modules) + print("+ Missing modules are: " + gd.__missing_modules) You shall have something like: @@ -150,7 +150,6 @@ You shall have something like: Cython version 0.29.25 Numpy version 1.21.4 Boost version 1.77.0 - print("+ Installed modules are: " + gudhi.__available_modules) + Installed modules are: off_reader;simplex_tree;rips_complex;cubical_complex;periodic_cubical_complex; persistence_graphical_tools;reader_utils;witness_complex;strong_witness_complex; + Missing modules are: bottleneck;nerve_gic;subsampling;tangential_complex;alpha_complex;euclidean_witness_complex; @@ -349,8 +348,8 @@ You can still deactivate LaTeX rendering by saying: .. code-block:: python - import gudhi - gudhi.persistence_graphical_tools._gudhi_matplotlib_use_tex=False + import gudhi as gd + gd.persistence_graphical_tools._gudhi_matplotlib_use_tex=False PyKeOps ------- -- cgit v1.2.3 From 4fc331267735a3368160ea7cd001b7fa45d720f7 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Thu, 10 Feb 2022 10:32:18 +0100 Subject: Boost 1.66.0 is now the minimal version as required from cgal 5.1 - Also remove useless include boost/version.h --- .github/next_release.md | 3 +++ src/Alpha_complex/include/gudhi/Alpha_complex_3d.h | 1 - src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_iterators.h | 1 - src/Subsampling/include/gudhi/sparsify_point_set.h | 5 ----- src/cmake/modules/GUDHI_third_party_libraries.cmake | 2 +- src/common/doc/installation.h | 2 +- src/common/include/gudhi/reader_utils.h | 4 ---- src/python/doc/installation.rst | 2 +- 8 files changed, 6 insertions(+), 14 deletions(-) (limited to 'src/python') diff --git a/.github/next_release.md b/.github/next_release.md index 65a92816..e21b25c7 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -13,6 +13,9 @@ Below is a list of changes made since GUDHI 3.5.0: - [Representations](https://gudhi.inria.fr/python/latest/representations.html#gudhi.representations.vector_methods.BettiCurve) - A more flexible Betti curve class capable of computing exact curves +- Installation + - Boost ≥ 1.66.0 is now required (was ≥ 1.56.0). + - [Module](link) - ... diff --git a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h index ccc3d852..df5c630e 100644 --- a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h +++ b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h @@ -12,7 +12,6 @@ #ifndef ALPHA_COMPLEX_3D_H_ #define ALPHA_COMPLEX_3D_H_ -#include #include #include #include diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_iterators.h b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_iterators.h index ee64a277..e5522cc7 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_iterators.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree/Simplex_tree_iterators.h @@ -14,7 +14,6 @@ #include #include -#include #include #include diff --git a/src/Subsampling/include/gudhi/sparsify_point_set.h b/src/Subsampling/include/gudhi/sparsify_point_set.h index 4571b8f3..b325fe3c 100644 --- a/src/Subsampling/include/gudhi/sparsify_point_set.h +++ b/src/Subsampling/include/gudhi/sparsify_point_set.h @@ -11,12 +11,7 @@ #ifndef SPARSIFY_POINT_SET_H_ #define SPARSIFY_POINT_SET_H_ -#include -#if BOOST_VERSION < 106600 -# include -#else # include -#endif #include #ifdef GUDHI_SUBSAMPLING_PROFILING diff --git a/src/cmake/modules/GUDHI_third_party_libraries.cmake b/src/cmake/modules/GUDHI_third_party_libraries.cmake index b316740d..68ffaee1 100644 --- a/src/cmake/modules/GUDHI_third_party_libraries.cmake +++ b/src/cmake/modules/GUDHI_third_party_libraries.cmake @@ -1,6 +1,6 @@ # This files manage third party libraries required by GUDHI -find_package(Boost 1.56.0 QUIET OPTIONAL_COMPONENTS filesystem unit_test_framework program_options) +find_package(Boost 1.66.0 QUIET OPTIONAL_COMPONENTS filesystem unit_test_framework program_options) # Boost_FOUND is not reliable if(NOT Boost_VERSION) diff --git a/src/common/doc/installation.h b/src/common/doc/installation.h index ef668dfb..67d026bd 100644 --- a/src/common/doc/installation.h +++ b/src/common/doc/installation.h @@ -5,7 +5,7 @@ * Examples of GUDHI headers inclusion can be found in \ref utilities. * * \section compiling Compiling - * The library uses c++14 and requires Boost ≥ 1.56.0 + * The library uses c++14 and requires Boost ≥ 1.66.0 * and CMake ≥ 3.5. * It is a multi-platform library and compiles on Linux, Mac OSX and Visual Studio 2015. * diff --git a/src/common/include/gudhi/reader_utils.h b/src/common/include/gudhi/reader_utils.h index a1b104e2..29d5423d 100644 --- a/src/common/include/gudhi/reader_utils.h +++ b/src/common/include/gudhi/reader_utils.h @@ -14,11 +14,7 @@ #include #include -#if BOOST_VERSION < 106600 -# include -#else # include -#endif #include #include diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index 35c344e3..c2c3db34 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -39,7 +39,7 @@ If you are instead using a git checkout, beware that the paths are a bit different, and in particular the `python/` subdirectory is actually `src/python/` there. -The library uses c++14 and requires `Boost `_ :math:`\geq` 1.56.0, +The library uses c++14 and requires `Boost `_ :math:`\geq` 1.66.0, `CMake `_ :math:`\geq` 3.5 to generate makefiles, `NumPy `_ :math:`\geq` 1.15.0, `Cython `_ and `pybind11 `_ to compile -- cgit v1.2.3 From 37a141533397568e7070c734e21ef9c4dc85d132 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Thu, 10 Feb 2022 16:16:14 +0100 Subject: Add SimplexTree copy method and its test --- src/python/gudhi/simplex_tree.pxd | 1 + src/python/gudhi/simplex_tree.pyx | 14 ++++++++++++++ src/python/test/test_simplex_tree.py | 19 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 006a24ed..92139db4 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -45,6 +45,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_interface_full_featured "Gudhi::Simplex_tree_interface": Simplex_tree_interface_full_featured() nogil + Simplex_tree_interface_full_featured(Simplex_tree_interface_full_featured&) nogil double simplex_filtration(vector[int] simplex) nogil void assign_simplex_filtration(vector[int] simplex, double filtration) nogil void initialize_filtration() nogil diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index c3720936..6b3116a4 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -63,6 +63,20 @@ cdef class SimplexTree: """ return self.pcohptr != NULL + def copy(self): + """ + :returns: A simplex tree that is a deep copy itself. + :rtype: SimplexTree + """ + stree = SimplexTree() + cdef Simplex_tree_interface_full_featured* stree_ptr + cdef Simplex_tree_interface_full_featured* self_ptr=self.get_ptr() + with nogil: + stree_ptr = new Simplex_tree_interface_full_featured(dereference(self_ptr)) + + stree.thisptr = (stree_ptr) + return stree + def filtration(self, simplex): """This function returns the filtration value for a given N-simplex in this simplicial complex, or +infinity if it is not in the complex. diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 31c46213..dac45288 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -447,4 +447,21 @@ def test_persistence_intervals_in_dimension(): assert np.array_equal(H2, np.array([[ 0., float("inf")]])) # Test empty case assert st.persistence_intervals_in_dimension(3).shape == (0, 2) - \ No newline at end of file + +def test_simplex_tree_copy(): + st = SimplexTree() + st .insert([1,2,3], 0.) + a = st.copy() + # TODO(VR): when #463 is merged, replace with + # assert a == st + assert a.num_vertices() == st.num_vertices() + assert a.num_simplices() == st.num_simplices() + st_filt_list = list(st.get_filtration()) + assert list(a.get_filtration()) == st_filt_list + + a.remove_maximal_simplex([1, 2, 3]) + a_filt_list = list(a.get_filtration()) + assert len(a_filt_list) < len(st_filt_list) + + for a_splx in a_filt_list: + assert a_splx in st_filt_list -- cgit v1.2.3 From 1eae93c4f6c5fa401cf03dce0bcbc70e72d4f46f Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Fri, 11 Feb 2022 14:22:55 +0100 Subject: Debug traces for python module --- src/python/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/python') diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 8eb7478e..777fae72 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -264,6 +264,10 @@ if(PYTHONINTERP_FOUND) set(GUDHI_PYTHON_INCLUDE_DIRS "${GUDHI_PYTHON_INCLUDE_DIRS}'${TBB_INCLUDE_DIRS}', ") endif() + if(DEBUG_TRACES) + set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DDEBUG_TRACES', ") + endif(DEBUG_TRACES) + if(UNIX AND WITH_GUDHI_PYTHON_RUNTIME_LIBRARY_DIRS) set( GUDHI_PYTHON_RUNTIME_LIBRARY_DIRS "${GUDHI_PYTHON_LIBRARY_DIRS}") endif(UNIX AND WITH_GUDHI_PYTHON_RUNTIME_LIBRARY_DIRS) -- cgit v1.2.3 From fb8ce008feadcaf6a936740a3ed54d50970c731c Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Fri, 11 Feb 2022 23:11:26 +0100 Subject: __copy__, __deepcopy__, copy, and copy ctors. Still pb with the doc --- .github/next_release.md | 3 ++ src/python/gudhi/simplex_tree.pyx | 54 +++++++++++++++---- src/python/test/test_simplex_tree.py | 102 +++++++++++++++++++++++++++++++---- 3 files changed, 140 insertions(+), 19 deletions(-) (limited to 'src/python') diff --git a/.github/next_release.md b/.github/next_release.md index e21b25c7..3946404b 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -13,6 +13,9 @@ Below is a list of changes made since GUDHI 3.5.0: - [Representations](https://gudhi.inria.fr/python/latest/representations.html#gudhi.representations.vector_methods.BettiCurve) - A more flexible Betti curve class capable of computing exact curves +- [Simplex tree](https://gudhi.inria.fr/python/latest/simplex_tree_ref.html) + - `__copy__`, `__deepcopy__`, `copy` and copy constructors + - Installation - Boost ≥ 1.66.0 is now required (was ≥ 1.56.0). diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 6b3116a4..ed7c3b92 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -30,6 +30,7 @@ cdef class SimplexTree: # unfortunately 'cdef public Simplex_tree_interface_full_featured* thisptr' is not possible # Use intptr_t instead to cast the pointer cdef public intptr_t thisptr + cdef bool __thisptr_to_be_deleted # Get the pointer casted as it should be cdef Simplex_tree_interface_full_featured* get_ptr(self) nogil: @@ -38,17 +39,36 @@ cdef class SimplexTree: cdef Simplex_tree_persistence_interface * pcohptr # Fake constructor that does nothing but documenting the constructor - def __init__(self): + def __init__(self, other = None, copy = True): """SimplexTree constructor. + :param other: If `other` is a SimplexTree (default = None), the SimplexTree is constructed from a deep/shallow copy of `other`. + :type other: SimplexTree + :param copy: If `True`, the copy will be deep and if `False, the copy will be shallow. Default is `True`. + :type copy: bool + :returns: A simplex tree that is a (deep or shallow) copy of itself. + :rtype: SimplexTree + :note: copy constructor requires :func:`compute_persistence` to be launched again as the result is not copied. """ # The real cython constructor - def __cinit__(self): - self.thisptr = (new Simplex_tree_interface_full_featured()) + def __cinit__(self, other = None, copy = True): + cdef SimplexTree ostr + if other and type(other) is SimplexTree: + ostr = other + if copy: + self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) + else: + self.thisptr = ostr.thisptr + # Avoid double free - The original is in charge of deletion + self.__thisptr_to_be_deleted = False + else: + self.__thisptr_to_be_deleted = True + self.thisptr = (new Simplex_tree_interface_full_featured()) def __dealloc__(self): cdef Simplex_tree_interface_full_featured* ptr = self.get_ptr() - if ptr != NULL: + # Avoid double free - The original is in charge of deletion + if ptr != NULL and self.__thisptr_to_be_deleted: del ptr if self.pcohptr != NULL: del self.pcohptr @@ -63,20 +83,34 @@ cdef class SimplexTree: """ return self.pcohptr != NULL - def copy(self): + def copy(self, deep=True): """ - :returns: A simplex tree that is a deep copy itself. + :param deep: If `True`, the copy will be deep and if `False`, the copy will be shallow. Default is `True`. + :type deep: bool + :returns: A simplex tree that is a (deep or shallow) copy of itself. :rtype: SimplexTree + :note: copy requires :func:`compute_persistence` to be launched again as the result is not copied. """ stree = SimplexTree() cdef Simplex_tree_interface_full_featured* stree_ptr cdef Simplex_tree_interface_full_featured* self_ptr=self.get_ptr() - with nogil: - stree_ptr = new Simplex_tree_interface_full_featured(dereference(self_ptr)) - - stree.thisptr = (stree_ptr) + if deep: + with nogil: + stree_ptr = new Simplex_tree_interface_full_featured(dereference(self_ptr)) + + stree.thisptr = (stree_ptr) + else: + stree.thisptr = self.thisptr + # Avoid double free - The original is in charge of deletion + stree.__thisptr_to_be_deleted = False return stree + def __copy__(self): + return self.copy(deep=False) + + def __deepcopy__(self): + return self.copy(deep=True) + def filtration(self, simplex): """This function returns the filtration value for a given N-simplex in this simplicial complex, or +infinity if it is not in the complex. diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index dac45288..6db6d8fb 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -448,20 +448,104 @@ def test_persistence_intervals_in_dimension(): # Test empty case assert st.persistence_intervals_in_dimension(3).shape == (0, 2) -def test_simplex_tree_copy(): +def test_simplex_tree_deep_copy(): st = SimplexTree() - st .insert([1,2,3], 0.) - a = st.copy() + st.insert([1, 2, 3], 0.) + # persistence is not copied + st.compute_persistence() + + st_copy = st.copy(deep=True) # TODO(VR): when #463 is merged, replace with - # assert a == st - assert a.num_vertices() == st.num_vertices() - assert a.num_simplices() == st.num_simplices() + # assert st_copy == st + assert st_copy.num_vertices() == st.num_vertices() + assert st_copy.num_simplices() == st.num_simplices() st_filt_list = list(st.get_filtration()) - assert list(a.get_filtration()) == st_filt_list + assert list(st_copy.get_filtration()) == st_filt_list + + assert st.__is_persistence_defined() == True + assert st_copy.__is_persistence_defined() == False - a.remove_maximal_simplex([1, 2, 3]) - a_filt_list = list(a.get_filtration()) + st_copy.remove_maximal_simplex([1, 2, 3]) + a_filt_list = list(st_copy.get_filtration()) assert len(a_filt_list) < len(st_filt_list) for a_splx in a_filt_list: assert a_splx in st_filt_list + + # test double free + del st + del st_copy + +def test_simplex_tree_shallow_copy(): + st = SimplexTree() + st.insert([1, 2, 3], 0.) + # persistence is not copied + st.compute_persistence() + + st_copy = st.copy(deep=False) + # TODO(VR): when #463 is merged, replace with + # assert st_copy == st + assert st_copy.num_vertices() == st.num_vertices() + assert st_copy.num_simplices() == st.num_simplices() + assert list(st_copy.get_filtration()) == list(st.get_filtration()) + + assert st.__is_persistence_defined() == True + assert st_copy.__is_persistence_defined() == False + + st_copy.assign_filtration([1, 2, 3], 2.) + assert list(st_copy.get_filtration()) == list(st.get_filtration()) + + # test double free + del st + del st_copy + +def test_simplex_tree_deep_copy_constructor(): + st = SimplexTree() + st.insert([1, 2, 3], 0.) + # persistence is not copied + st.compute_persistence() + + st_copy = SimplexTree(st, copy = True) + # TODO(VR): when #463 is merged, replace with + # assert st_copy == st + assert st_copy.num_vertices() == st.num_vertices() + assert st_copy.num_simplices() == st.num_simplices() + st_filt_list = list(st.get_filtration()) + assert list(st_copy.get_filtration()) == st_filt_list + + assert st.__is_persistence_defined() == True + assert st_copy.__is_persistence_defined() == False + + st_copy.remove_maximal_simplex([1, 2, 3]) + a_filt_list = list(st_copy.get_filtration()) + assert len(a_filt_list) < len(st_filt_list) + + for a_splx in a_filt_list: + assert a_splx in st_filt_list + + # test double free + del st + del st_copy + +def test_simplex_tree_shallow_copy(): + st = SimplexTree() + st.insert([1, 2, 3], 0.) + # persistence is not copied + st.compute_persistence() + + st_copy = SimplexTree(st, copy = False) + # TODO(VR): when #463 is merged, replace with + # assert st_copy == st + assert st_copy.num_vertices() == st.num_vertices() + assert st_copy.num_simplices() == st.num_simplices() + assert list(st_copy.get_filtration()) == list(st.get_filtration()) + + assert st.__is_persistence_defined() == True + assert st_copy.__is_persistence_defined() == False + + st_copy.assign_filtration([1, 2, 3], 2.) + assert list(st_copy.get_filtration()) == list(st.get_filtration()) + + # test double free + del st + del st_copy -- cgit v1.2.3 From 43981a4d487669fe2002337ab62b72dd9e83a64a Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 14 Feb 2022 11:08:00 +0100 Subject: Remove shallow copy --- .github/next_release.md | 2 +- src/python/gudhi/simplex_tree.pyx | 54 +++++++++++++----------------------- src/python/test/test_simplex_tree.py | 50 ++------------------------------- 3 files changed, 23 insertions(+), 83 deletions(-) (limited to 'src/python') diff --git a/.github/next_release.md b/.github/next_release.md index 3946404b..3d4761eb 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -14,7 +14,7 @@ Below is a list of changes made since GUDHI 3.5.0: - A more flexible Betti curve class capable of computing exact curves - [Simplex tree](https://gudhi.inria.fr/python/latest/simplex_tree_ref.html) - - `__copy__`, `__deepcopy__`, `copy` and copy constructors + - `__deepcopy__`, `copy` and copy constructors - Installation - Boost ≥ 1.66.0 is now required (was ≥ 1.56.0). diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index ed7c3b92..0213e363 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -30,7 +30,6 @@ cdef class SimplexTree: # unfortunately 'cdef public Simplex_tree_interface_full_featured* thisptr' is not possible # Use intptr_t instead to cast the pointer cdef public intptr_t thisptr - cdef bool __thisptr_to_be_deleted # Get the pointer casted as it should be cdef Simplex_tree_interface_full_featured* get_ptr(self) nogil: @@ -39,36 +38,32 @@ cdef class SimplexTree: cdef Simplex_tree_persistence_interface * pcohptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, other = None, copy = True): + def __init__(self, other = None): """SimplexTree constructor. - :param other: If `other` is a SimplexTree (default = None), the SimplexTree is constructed from a deep/shallow copy of `other`. + + :param other: If `other` is a `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 - :param copy: If `True`, the copy will be deep and if `False, the copy will be shallow. Default is `True`. - :type copy: bool - :returns: A simplex tree that is a (deep or shallow) copy of itself. + :returns: An empty or a copy simplex tree. :rtype: SimplexTree - :note: copy constructor requires :func:`compute_persistence` to be launched again as the result is not copied. + + :note: If the `SimplexTree` is a copy, it requires :func:`compute_persistence` to be launched again as the + persistence result is not copied. """ # The real cython constructor - def __cinit__(self, other = None, copy = True): + def __cinit__(self, other = None): cdef SimplexTree ostr if other and type(other) is SimplexTree: ostr = other - if copy: - self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) - else: - self.thisptr = ostr.thisptr - # Avoid double free - The original is in charge of deletion - self.__thisptr_to_be_deleted = False + self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) else: - self.__thisptr_to_be_deleted = True self.thisptr = (new Simplex_tree_interface_full_featured()) def __dealloc__(self): cdef Simplex_tree_interface_full_featured* ptr = self.get_ptr() # Avoid double free - The original is in charge of deletion - if ptr != NULL and self.__thisptr_to_be_deleted: + if ptr != NULL: del ptr if self.pcohptr != NULL: del self.pcohptr @@ -83,33 +78,24 @@ cdef class SimplexTree: """ return self.pcohptr != NULL - def copy(self, deep=True): + def copy(self): """ - :param deep: If `True`, the copy will be deep and if `False`, the copy will be shallow. Default is `True`. - :type deep: bool - :returns: A simplex tree that is a (deep or shallow) copy of itself. + :returns: A simplex tree that is a deep copy of itself. :rtype: SimplexTree - :note: copy requires :func:`compute_persistence` to be launched again as the result is not copied. + + :note: copy requires :func:`compute_persistence` to be launched again as the persistence result is not copied. """ stree = SimplexTree() cdef Simplex_tree_interface_full_featured* stree_ptr cdef Simplex_tree_interface_full_featured* self_ptr=self.get_ptr() - if deep: - with nogil: - stree_ptr = new Simplex_tree_interface_full_featured(dereference(self_ptr)) - - stree.thisptr = (stree_ptr) - else: - stree.thisptr = self.thisptr - # Avoid double free - The original is in charge of deletion - stree.__thisptr_to_be_deleted = False - return stree + with nogil: + stree_ptr = new Simplex_tree_interface_full_featured(dereference(self_ptr)) - def __copy__(self): - return self.copy(deep=False) + stree.thisptr = (stree_ptr) + return stree def __deepcopy__(self): - return self.copy(deep=True) + return self.copy() def filtration(self, simplex): """This function returns the filtration value for a given N-simplex in diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 6db6d8fb..62dcc865 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -454,7 +454,7 @@ def test_simplex_tree_deep_copy(): # persistence is not copied st.compute_persistence() - st_copy = st.copy(deep=True) + st_copy = st.copy() # TODO(VR): when #463 is merged, replace with # assert st_copy == st assert st_copy.num_vertices() == st.num_vertices() @@ -476,36 +476,13 @@ def test_simplex_tree_deep_copy(): del st del st_copy -def test_simplex_tree_shallow_copy(): - st = SimplexTree() - st.insert([1, 2, 3], 0.) - # persistence is not copied - st.compute_persistence() - - st_copy = st.copy(deep=False) - # TODO(VR): when #463 is merged, replace with - # assert st_copy == st - assert st_copy.num_vertices() == st.num_vertices() - assert st_copy.num_simplices() == st.num_simplices() - assert list(st_copy.get_filtration()) == list(st.get_filtration()) - - assert st.__is_persistence_defined() == True - assert st_copy.__is_persistence_defined() == False - - st_copy.assign_filtration([1, 2, 3], 2.) - assert list(st_copy.get_filtration()) == list(st.get_filtration()) - - # test double free - del st - del st_copy - def test_simplex_tree_deep_copy_constructor(): st = SimplexTree() st.insert([1, 2, 3], 0.) # persistence is not copied st.compute_persistence() - st_copy = SimplexTree(st, copy = True) + st_copy = SimplexTree(st) # TODO(VR): when #463 is merged, replace with # assert st_copy == st assert st_copy.num_vertices() == st.num_vertices() @@ -526,26 +503,3 @@ def test_simplex_tree_deep_copy_constructor(): # test double free del st del st_copy - -def test_simplex_tree_shallow_copy(): - st = SimplexTree() - st.insert([1, 2, 3], 0.) - # persistence is not copied - st.compute_persistence() - - st_copy = SimplexTree(st, copy = False) - # TODO(VR): when #463 is merged, replace with - # assert st_copy == st - assert st_copy.num_vertices() == st.num_vertices() - assert st_copy.num_simplices() == st.num_simplices() - assert list(st_copy.get_filtration()) == list(st.get_filtration()) - - assert st.__is_persistence_defined() == True - assert st_copy.__is_persistence_defined() == False - - st_copy.assign_filtration([1, 2, 3], 2.) - assert list(st_copy.get_filtration()) == list(st.get_filtration()) - - # test double free - del st - del st_copy -- cgit v1.2.3 From c7303733b28d3bc429d4bdfd030409e07430599d Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 14 Feb 2022 11:29:49 +0100 Subject: Rewrite test as #463 is merged. Rephrase constructor documentation --- src/python/gudhi/simplex_tree.pyx | 4 ++-- src/python/test/test_simplex_tree.py | 20 ++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 6701d98d..a2b8719a 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -41,9 +41,9 @@ cdef class SimplexTree: def __init__(self, other = None): """SimplexTree constructor. - :param other: If `other` is a `None` (default value), an empty `SimplexTree` is created. + :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 + :type other: SimplexTree (Optional) :returns: An empty or a copy simplex tree. :rtype: SimplexTree diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 32fc63ec..2c2e09a2 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -463,20 +463,18 @@ def test_equality_operator(): def test_simplex_tree_deep_copy(): st = SimplexTree() st.insert([1, 2, 3], 0.) - # persistence is not copied + # compute persistence only on the original st.compute_persistence() st_copy = st.copy() - # TODO(VR): when #463 is merged, replace with - # assert st_copy == st - assert st_copy.num_vertices() == st.num_vertices() - assert st_copy.num_simplices() == st.num_simplices() + assert st_copy == st st_filt_list = list(st.get_filtration()) - assert list(st_copy.get_filtration()) == st_filt_list + # check persistence is not copied assert st.__is_persistence_defined() == True assert st_copy.__is_persistence_defined() == False + # remove something in the copy and check the copy is included in the original st_copy.remove_maximal_simplex([1, 2, 3]) a_filt_list = list(st_copy.get_filtration()) assert len(a_filt_list) < len(st_filt_list) @@ -491,20 +489,18 @@ def test_simplex_tree_deep_copy(): def test_simplex_tree_deep_copy_constructor(): st = SimplexTree() st.insert([1, 2, 3], 0.) - # persistence is not copied + # compute persistence only on the original st.compute_persistence() st_copy = SimplexTree(st) - # TODO(VR): when #463 is merged, replace with - # assert st_copy == st - assert st_copy.num_vertices() == st.num_vertices() - assert st_copy.num_simplices() == st.num_simplices() + assert st_copy == st st_filt_list = list(st.get_filtration()) - assert list(st_copy.get_filtration()) == st_filt_list + # check persistence is not copied assert st.__is_persistence_defined() == True assert st_copy.__is_persistence_defined() == False + # remove something in the copy and check the copy is included in the original st_copy.remove_maximal_simplex([1, 2, 3]) a_filt_list = list(st_copy.get_filtration()) assert len(a_filt_list) < len(st_filt_list) -- cgit v1.2.3 From 1a0551c033e55024cd0f00302cd9df1f356bab44 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 14 Feb 2022 11:31:53 +0100 Subject: some left over --- src/python/gudhi/simplex_tree.pyx | 1 - 1 file changed, 1 deletion(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index a2b8719a..8f760422 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -62,7 +62,6 @@ cdef class SimplexTree: def __dealloc__(self): cdef Simplex_tree_interface_full_featured* ptr = self.get_ptr() - # Avoid double free - The original is in charge of deletion if ptr != NULL: del ptr if self.pcohptr != NULL: -- cgit v1.2.3 From d5ac245a6dc4ab2d6e30689fc5d95503c40b6187 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 14 Feb 2022 12:31:30 +0100 Subject: code review: ctor shall raise a TypeError when constructed from something else than a SimplexTree --- src/python/gudhi/simplex_tree.pyx | 10 +++++++--- src/python/test/test_simplex_tree.py | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 8f760422..e1685ded 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -47,6 +47,7 @@ cdef class SimplexTree: :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, it requires :func:`compute_persistence` to be launched again as the persistence result is not copied. """ @@ -54,9 +55,12 @@ cdef class SimplexTree: # The real cython constructor def __cinit__(self, other = None): cdef SimplexTree ostr - if other and type(other) is SimplexTree: - ostr = other - self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) + if other: + if type(other) is SimplexTree: + ostr = other + self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) + else: + raise TypeError("`other` argument requires to be of type `SimplexTree`, or `None`.") else: self.thisptr = (new Simplex_tree_interface_full_featured()) diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 2c2e09a2..a8180ce8 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -511,3 +511,7 @@ def test_simplex_tree_deep_copy_constructor(): # test double free del st del st_copy + +def test_simplex_tree_constructor_exception(): + with pytest.raises(TypeError): + st = SimplexTree(other = "Construction from a string shall raise an exception") -- cgit v1.2.3 From 362ac0955656d7c131e042901d6606fdeaab7fc9 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Mon, 14 Feb 2022 23:33:38 +0100 Subject: More details in the doc of weighted rips --- biblio/bibliography.bib | 25 ++++++++++++++++++++++++- src/python/gudhi/weighted_rips_complex.py | 6 ++++-- 2 files changed, 28 insertions(+), 3 deletions(-) (limited to 'src/python') diff --git a/biblio/bibliography.bib b/biblio/bibliography.bib index b5afff52..e75e8db2 100644 --- a/biblio/bibliography.bib +++ b/biblio/bibliography.bib @@ -1280,7 +1280,7 @@ year = "2011" publisher={Springer} } -@inproceedings{dtmfiltrations, +@inproceedings{dtmfiltrationsconf, author = {Hirokazu Anai and Fr{\'{e}}d{\'{e}}ric Chazal and Marc Glisse and @@ -1305,6 +1305,29 @@ year = "2011" bibsource = {dblp computer science bibliography, https://dblp.org} } +@InProceedings{dtmfiltrations, +author="Anai, Hirokazu +and Chazal, Fr{\'e}d{\'e}ric +and Glisse, Marc +and Ike, Yuichi +and Inakoshi, Hiroya +and Tinarrage, Rapha{\"e}l +and Umeda, Yuhei", +editor="Baas, Nils A. +and Carlsson, Gunnar E. +and Quick, Gereon +and Szymik, Markus +and Thaule, Marius", +title="DTM-Based Filtrations", +booktitle="Topological Data Analysis", +year="2020", +publisher="Springer International Publishing", +address="Cham", +pages="33--66", +isbn="978-3-030-43408-3", +doi="10.1007/978-3-030-43408-3_2", +} + @InProceedings{edgecollapsesocg2020, author = {Jean-Daniel Boissonnat and Siddharth Pritam}, title = {{Edge Collapse and Persistence of Flag Complexes}}, diff --git a/src/python/gudhi/weighted_rips_complex.py b/src/python/gudhi/weighted_rips_complex.py index 0541572b..16f63c3d 100644 --- a/src/python/gudhi/weighted_rips_complex.py +++ b/src/python/gudhi/weighted_rips_complex.py @@ -12,9 +12,11 @@ from gudhi import SimplexTree class WeightedRipsComplex: """ Class to generate a weighted Rips complex from a distance matrix and weights on vertices, - in the way described in :cite:`dtmfiltrations`. + in the way described in :cite:`dtmfiltrations` with `p=1`. The filtration value of vertex `i` is `2*weights[i]`, + and the filtration value of edge `ij` is `distance_matrix[i][j]+weights[i]+weights[j]`, + or the maximum of the filtrations of its extremities, whichever is largest. Remark that all the filtration values are doubled compared to the definition in the paper - for the consistency with RipsComplex. + for consistency with RipsComplex. """ def __init__(self, distance_matrix, -- cgit v1.2.3 From 0c919b84377637833477b3433406ec4478a0f6f0 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sat, 27 Nov 2021 16:13:08 +0100 Subject: Remove the old forward version of edge collapse --- .../include/gudhi/Flag_complex_edge_collapser.h | 356 +-------------------- src/python/test/test_simplex_tree.py | 17 +- 2 files changed, 21 insertions(+), 352 deletions(-) (limited to 'src/python') diff --git a/src/Collapse/include/gudhi/Flag_complex_edge_collapser.h b/src/Collapse/include/gudhi/Flag_complex_edge_collapser.h index 7479ada3..0c811073 100644 --- a/src/Collapse/include/gudhi/Flag_complex_edge_collapser.h +++ b/src/Collapse/include/gudhi/Flag_complex_edge_collapser.h @@ -6,6 +6,7 @@ * * Modification(s): * - 2020/03 Vincent Rouvreau: integration to the gudhi library + * - 2021 Marc Glisse: complete rewrite * - YYYY/MM Author: Description of the modification */ @@ -15,32 +16,18 @@ #include #include -#include #include #include -#include -#include // for EIGEN_VERSION_AT_LEAST - -#ifdef GUDHI_USE_TBB -#include -#endif - -#include -#include // for std::pair +#include #include #include #include #include -#include // for std::tie -#include // for std::includes -#include // for std::inserter -#include // for std::decay - -// Make compilation fail - required for external projects - https://github.com/GUDHI/gudhi-devel/issues/10 -#if !EIGEN_VERSION_AT_LEAST(3,1,0) -# error Edge Collapse is only available for Eigen3 >= 3.1.0 -#endif +#include +#include +#include +#include namespace Gudhi { @@ -50,311 +37,16 @@ namespace collapse { * * \brief Flag complex sparse matrix data structure. * - * \details - * This class stores a Flag complex - * in an Eigen sparse matrix. - * - * \tparam Vertex type must be a signed integer type. It admits a total order <. - * \tparam Filtration type for the value of the filtration function. Must be comparable with <. + * \tparam Vertex type must be an integer type. + * \tparam Filtration type for the value of the filtration function. */ -template -class Flag_complex_edge_collapser { - public: - /** \brief Re-define Vertex as Vertex_handle type to ease the interface with `Gudhi::Proximity_graph`. */ - using Vertex_handle = Vertex; - /** \brief Re-define Filtration as Filtration_value type to ease the interface with `Gudhi::Proximity_graph`. */ - using Filtration_value = Filtration; - - private: - // internal numbering of vertices and edges - using IVertex = std::size_t; - using Edge_index = std::size_t; - using IEdge = std::pair; - - // The sparse matrix data type - // (Eigen::SparseMatrix has slow insertions) - using Sparse_vector = Eigen::SparseVector; - using Sparse_row_matrix = std::vector; - - // Range of neighbors of a vertex - template - struct Neighbours { - class iterator : public boost::iterator_facade - { - public: - iterator():ptr(nullptr){} - iterator(Neighbours const*p):ptr(p){find_valid();} - private: - friend class boost::iterator_core_access; - Neighbours const*ptr; - void increment(){ - ++ptr->it; - find_valid(); - } - void find_valid(){ - auto& it = ptr->it; - do { - if(!it) { ptr=nullptr; break; } - if(IVertex(it.index()) == ptr->u) { - if(closed) break; - else continue; - } - Edge_index e = it.value(); - if(e <= ptr->ec->current_backward || ptr->ec->critical_edge_indicator_[e]) break; - } while(++it, true); - } - bool equal(iterator const& other) const { return ptr == other.ptr; } - IVertex dereference() const { return ptr->it.index(); } - }; - typedef iterator const_iterator; - mutable typename Sparse_vector::InnerIterator it; - Flag_complex_edge_collapser const*ec; - IVertex u; - iterator begin() const { return this; } - iterator end() const { return {}; } - explicit Neighbours(Flag_complex_edge_collapser const*p,IVertex u):it(p->sparse_row_adjacency_matrix_[u]),ec(p),u(u){} - }; - - // A range of row indices - using IVertex_vector = std::vector; - - public: - /** \brief Filtered_edge is a type to store an edge with its filtration value. */ - using Filtered_edge = std::tuple; - - private: - // Map from row index to its vertex handle - std::vector row_to_vertex_; - - // Index of the current edge in the backwards walk. Edges <= current_backward are part of the temporary graph, - // while edges > current_backward are removed unless critical_edge_indicator_. - Edge_index current_backward = -1; - - // Map from IEdge to its index - std::unordered_map> iedge_to_index_map_; - - // Boolean vector to indicate if the edge is critical. - std::vector critical_edge_indicator_; - - // Map from vertex handle to its row index - std::unordered_map vertex_to_row_; - - // Stores the Sparse matrix of Filtration values representing the original graph. - // The matrix rows and columns are indexed by IVertex. - Sparse_row_matrix sparse_row_adjacency_matrix_; - - // The input, a vector of filtered edges. - std::vector f_edge_vector_; - - // Edge is the actual edge (u,v), with Vertex_handle u and v, not IVertex. - bool edge_is_dominated(Vertex_handle u, Vertex_handle v) const - { - const IVertex rw_u = vertex_to_row_.at(u); - const IVertex rw_v = vertex_to_row_.at(v); -#ifdef DEBUG_TRACES - std::cout << "The edge {" << u << ", " << v << "} is going for domination check." << std::endl; -#endif // DEBUG_TRACES - auto common_neighbours = open_common_neighbours_row_index(rw_u, rw_v); -#ifdef DEBUG_TRACES - std::cout << "And its common neighbours are." << std::endl; - for (auto neighbour : common_neighbours) { - std::cout << row_to_vertex_[neighbour] << ", " ; - } - std::cout<< std::endl; -#endif // DEBUG_TRACES - if (common_neighbours.size() == 1) - return true; - else - for (auto rw_c : common_neighbours) { - auto neighbours_c = neighbours_row_index(rw_c); - // If neighbours_c contains the common neighbours. - if (std::includes(neighbours_c.begin(), neighbours_c.end(), - common_neighbours.begin(), common_neighbours.end())) - return true; - } - return false; - } - - // Returns the edges connecting u and v (extremities of crit) to their common neighbors (not themselves) - std::set three_clique_indices(Edge_index crit) { - std::set edge_indices; - - Vertex_handle u = std::get<0>(f_edge_vector_[crit]); - Vertex_handle v = std::get<1>(f_edge_vector_[crit]); - -#ifdef DEBUG_TRACES - std::cout << "The current critical edge to re-check criticality with filt value is : f {" << u << "," << v - << "} = " << std::get<2>(f_edge_vector_[crit]) << std::endl; -#endif // DEBUG_TRACES - auto rw_u = vertex_to_row_[u]; - auto rw_v = vertex_to_row_[v]; - - IVertex_vector common_neighbours = open_common_neighbours_row_index(rw_u, rw_v); - - for (auto rw_c : common_neighbours) { - IEdge e_with_new_nbhr_v = std::minmax(rw_u, rw_c); - IEdge e_with_new_nbhr_u = std::minmax(rw_v, rw_c); - edge_indices.emplace(iedge_to_index_map_[e_with_new_nbhr_v]); - edge_indices.emplace(iedge_to_index_map_[e_with_new_nbhr_u]); - } - return edge_indices; - } - - // Detect and set all edges that are becoming critical - template - void set_edge_critical(Edge_index indx, Filtration_value filt, FilteredEdgeOutput filtered_edge_output) { -#ifdef DEBUG_TRACES - std::cout << "The curent index with filtration value " << indx << ", " << filt << " is primary critical" << - std::endl; -#endif // DEBUG_TRACES - std::set effected_indices = three_clique_indices(indx); - // Cannot use boost::adaptors::reverse in such dynamic cases apparently - for (auto it = effected_indices.rbegin(); it != effected_indices.rend(); ++it) { - current_backward = *it; - Vertex_handle u = std::get<0>(f_edge_vector_[current_backward]); - Vertex_handle v = std::get<1>(f_edge_vector_[current_backward]); - // If current_backward is not critical so it should be processed, otherwise it stays in the graph - if (!critical_edge_indicator_[current_backward]) { - if (!edge_is_dominated(u, v)) { -#ifdef DEBUG_TRACES - std::cout << "The curent index became critical " << current_backward << std::endl; -#endif // DEBUG_TRACES - critical_edge_indicator_[current_backward] = true; - filtered_edge_output(u, v, filt); - std::set inner_effected_indcs = three_clique_indices(current_backward); - for (auto inr_idx : inner_effected_indcs) { - if(inr_idx < current_backward) // && !critical_edge_indicator_[inr_idx] - effected_indices.emplace(inr_idx); - } -#ifdef DEBUG_TRACES - std::cout << "The following edge is critical with filt value: {" << u << "," << v << "}; " - << filt << std::endl; -#endif // DEBUG_TRACES - } - } - } - // Clear the implicit "removed from graph" data structure - current_backward = -1; - } - - // Returns list of neighbors of a particular vertex. - template - auto neighbours_row_index(IVertex rw_u) const - { - return Neighbours(this, rw_u); - } - - // Returns the list of open neighbours of the edge :{u,v}. - IVertex_vector open_common_neighbours_row_index(IVertex rw_u, IVertex rw_v) const - { - auto non_zero_indices_u = neighbours_row_index(rw_u); - auto non_zero_indices_v = neighbours_row_index(rw_v); - IVertex_vector common; - std::set_intersection(non_zero_indices_u.begin(), non_zero_indices_u.end(), non_zero_indices_v.begin(), - non_zero_indices_v.end(), std::back_inserter(common)); - - return common; - } - - // Insert a vertex in the data structure - IVertex insert_vertex(Vertex_handle vertex) { - auto n = row_to_vertex_.size(); - auto result = vertex_to_row_.emplace(vertex, n); - // If it was not already inserted - Value won't be updated by emplace if it is already present - if (result.second) { - // Expand the matrix. The size of rows is irrelevant. - sparse_row_adjacency_matrix_.emplace_back((std::numeric_limits::max)()); - // Initializing the diagonal element of the adjency matrix corresponding to rw_b. - sparse_row_adjacency_matrix_[n].insert(n) = -1; // not an edge - // Must be done after reading its size() - row_to_vertex_.push_back(vertex); - } - return result.first->second; - } - - // Insert an edge in the data structure - // @exception std::invalid_argument In debug mode, if u == v - IEdge insert_new_edge(Vertex_handle u, Vertex_handle v, Edge_index idx) - { - GUDHI_CHECK((u != v), std::invalid_argument("Flag_complex_edge_collapser::insert_new_edge with u == v")); - // The edge must not be added before, it should be a new edge. - IVertex rw_u = insert_vertex(u); - IVertex rw_v = insert_vertex(v); -#ifdef DEBUG_TRACES - std::cout << "Inserting the edge " << u <<", " << v << std::endl; -#endif // DEBUG_TRACES - sparse_row_adjacency_matrix_[rw_u].insert(rw_v) = idx; - sparse_row_adjacency_matrix_[rw_v].insert(rw_u) = idx; - return std::minmax(rw_u, rw_v); - } - - public: - /** \brief Flag_complex_edge_collapser constructor from a range of filtered edges. - * - * @param[in] edges Range of Filtered edges range.There is no need the range to be sorted, as it will be performed in - * `Flag_complex_edge_collapser::process_edges`. - * - * \tparam FilteredEdgeRange must be a range for which std::begin and std::end return iterators on a - * `Flag_complex_edge_collapser::Filtered_edge`. - */ - template - Flag_complex_edge_collapser(const FilteredEdgeRange& edges) - : f_edge_vector_(std::begin(edges), std::end(edges)) { } - - /** \brief Performs edge collapse in a increasing sequence of the filtration value. - * - * \tparam filtered_edge_output is a functor that is called on the output edges, in non-decreasing order of - * filtration, as filtered_edge_output(u, v, f) where u and v are Vertex_handle representing the extremities of the - * edge, and f is its new Filtration_value. - */ - template - void process_edges(FilteredEdgeOutput filtered_edge_output) { - // Sort edges - auto sort_by_filtration = [](const Filtered_edge& edge_a, const Filtered_edge& edge_b) -> bool - { - return (std::get<2>(edge_a) < std::get<2>(edge_b)); - }; - -#ifdef GUDHI_USE_TBB - tbb::parallel_sort(f_edge_vector_.begin(), f_edge_vector_.end(), sort_by_filtration); -#else - std::sort(f_edge_vector_.begin(), f_edge_vector_.end(), sort_by_filtration); -#endif - - for (Edge_index endIdx = 0; endIdx < f_edge_vector_.size(); endIdx++) { - Filtered_edge fec = f_edge_vector_[endIdx]; - Vertex_handle u = std::get<0>(fec); - Vertex_handle v = std::get<1>(fec); - Filtration_value filt = std::get<2>(fec); - - // Inserts the edge in the sparse matrix to update the graph (G_i) - IEdge ie = insert_new_edge(u, v, endIdx); - - iedge_to_index_map_.emplace(ie, endIdx); - critical_edge_indicator_.push_back(false); - - if (!edge_is_dominated(u, v)) { - critical_edge_indicator_[endIdx] = true; - filtered_edge_output(u, v, filt); - if (endIdx > 1) - set_edge_critical(endIdx, filt, filtered_edge_output); - } - } - } - -}; - template -struct Flag_complex_edge_collapser2 { +struct Flag_complex_edge_collapser { using Filtered_edge = std::tuple; typedef std::pair Edge; struct Cmpi { template bool operator()(T const&a, U const&b)const{return b Ngb_list; - //typedef std::unordered_map Neighbors; - typedef std::vector Neighbors; // we still need to find the edge in the group then... + typedef std::vector Neighbors; Neighbors neighbors; // closed neighborhood std::size_t num_vertices; std::vector> res; @@ -571,7 +263,6 @@ struct Flag_complex_edge_collapser2 { } } if(dominator==-1) break; - // investigate what we can do without sorting... bool still_dominated; do { if(e_ngb_later_begin == e_ngb_later_end) { @@ -635,29 +326,12 @@ end_move: * \ingroup edge_collapse * */ -template auto flag_complex_collapse_edges(const FilteredEdgeRange& edges) { - auto first_edge_itr = std::begin(edges); - using Vertex_handle = std::decay_t(*first_edge_itr))>; - using Filtration_value = std::decay_t(*first_edge_itr))>; - using Edge_collapser = Flag_complex_edge_collapser; - std::vector remaining_edges; - if (first_edge_itr != std::end(edges)) { - Edge_collapser edge_collapser(edges); - edge_collapser.process_edges( - [&remaining_edges](Vertex_handle u, Vertex_handle v, Filtration_value filtration) { - // insert the edge - remaining_edges.emplace_back(u, v, filtration); - }); - } - return remaining_edges; -} - -// Would it help to label the points according to some spatial sorting? -template auto flag_complex_collapse_edges2(FilteredEdgeRange&& edges, Delay&&delay) { +template auto flag_complex_collapse_edges(FilteredEdgeRange&& edges, Delay&&delay) { + // Would it help to label the points according to some spatial sorting? auto first_edge_itr = std::begin(edges); using Vertex = std::decay_t(*first_edge_itr))>; using Filtration_value = std::decay_t(*first_edge_itr))>; - using Edge_collapser = Flag_complex_edge_collapser2; + using Edge_collapser = Flag_complex_edge_collapser; if (first_edge_itr != std::end(edges)) { std::vector edges2; if(std::is_same>::value) @@ -672,8 +346,8 @@ template auto flag_complex_collapse_edges2 } return std::vector(); } -template auto flag_complex_collapse_edges2(const FilteredEdgeRange& edges) { - return flag_complex_collapse_edges2(edges, [](auto const&d){return d;}); +template auto flag_complex_collapse_edges(const FilteredEdgeRange& edges) { + return flag_complex_collapse_edges(edges, [](auto const&d){return d;}); } } // namespace collapse diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 9766ecfe..f130cf1f 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -8,7 +8,7 @@ - YYYY/MM Author: Description of the modification """ -from gudhi import SimplexTree, __GUDHI_USE_EIGEN3 +from gudhi import SimplexTree import numpy as np import pytest @@ -354,16 +354,11 @@ def test_collapse_edges(): assert st.num_simplices() == 10 - if __GUDHI_USE_EIGEN3: - st.collapse_edges() - assert st.num_simplices() == 9 - assert st.find([1, 3]) == False - for simplex in st.get_skeleton(0): - assert simplex[1] == 1. - else: - # If no Eigen3, collapse_edges throws an exception - with pytest.raises(RuntimeError): - st.collapse_edges() + st.collapse_edges() + assert st.num_simplices() == 9 + assert st.find([0, 2]) == False # [1, 3] would be fine as well + for simplex in st.get_skeleton(0): + assert simplex[1] == 1. def test_reset_filtration(): st = SimplexTree() -- cgit v1.2.3 From 25d98bfa6470acf4f2a121196925b0b15ed41d4e Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 18 Feb 2022 21:33:28 +0100 Subject: edge collapse doesn't use eigen anymore --- src/python/include/Simplex_tree_interface.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'src/python') diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 629f6083..4d8f8537 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -15,9 +15,7 @@ #include #include #include -#ifdef GUDHI_USE_EIGEN3 #include -#endif #include #include @@ -164,7 +162,6 @@ class Simplex_tree_interface : public Simplex_tree { } Simplex_tree_interface* collapse_edges(int nb_collapse_iteration) { -#ifdef GUDHI_USE_EIGEN3 using Filtered_edge = std::tuple; std::vector edges; for (Simplex_handle sh : Base::skeleton_simplex_range(1)) { @@ -178,7 +175,7 @@ class Simplex_tree_interface : public Simplex_tree { } for (int iteration = 0; iteration < nb_collapse_iteration; iteration++) { - edges = Gudhi::collapse::flag_complex_collapse_edges(edges); + 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 @@ -190,9 +187,6 @@ class Simplex_tree_interface : public Simplex_tree { collapsed_stree_ptr->insert({std::get<0>(remaining_edge), std::get<1>(remaining_edge)}, std::get<2>(remaining_edge)); } return collapsed_stree_ptr; -#else - throw std::runtime_error("Unable to collapse edges as it requires Eigen3 >= 3.1.0."); -#endif } // Iterator over the simplex tree -- cgit v1.2.3 From ad7d77d799aa5eeb4e4d051741b8e6d7b8a87722 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 22 Feb 2022 21:39:46 +0100 Subject: Remove mention of eigen --- src/python/gudhi/simplex_tree.pyx | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 6393343f..b9c2a42f 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -630,9 +630,6 @@ cdef class SimplexTree: :param nb_iterations: The number of edge collapse iterations to perform. Default is 1. :type nb_iterations: int - - :note: collapse_edges method requires `Eigen `_ >= 3.1.0 and an exception is thrown - if this method is not available. """ # Backup old pointer cdef Simplex_tree_interface_full_featured* ptr = self.get_ptr() -- cgit v1.2.3 From 4e63e52758daa7ef27782b4f129b7e55fa73de43 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 28 Feb 2022 09:50:08 +0100 Subject: code review: use 'isinstance' instead of 'type' --- src/python/gudhi/simplex_tree.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index e1685ded..711796d4 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -56,7 +56,7 @@ cdef class SimplexTree: def __cinit__(self, other = None): cdef SimplexTree ostr if other: - if type(other) is SimplexTree: + if isinstance(other, SimplexTree): ostr = other self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) else: -- cgit v1.2.3 From e7ba8967e9b5c3c7260522003d8f87e643b7912e Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 28 Feb 2022 10:56:47 +0100 Subject: doc review: reformulate the copy notes --- src/python/gudhi/simplex_tree.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 711796d4..91e079aa 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -48,8 +48,8 @@ cdef class SimplexTree: :rtype: SimplexTree :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`. - :note: If the `SimplexTree` is a copy, it requires :func:`compute_persistence` to be launched again as the - persistence result is not copied. + :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 @@ -86,7 +86,8 @@ cdef class SimplexTree: :returns: A simplex tree that is a deep copy of itself. :rtype: SimplexTree - :note: copy requires :func:`compute_persistence` to be launched again as the persistence result is not copied. + :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 = SimplexTree() cdef Simplex_tree_interface_full_featured* stree_ptr -- cgit v1.2.3 From 41a976cc85ab36dc2df748b9d6900d77e76b2fa7 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 28 Feb 2022 14:58:24 +0100 Subject: code review: copy simplex tree c++ pointer in a factorized nogil function --- src/python/gudhi/simplex_tree.pyx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 91e079aa..b8fabf78 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -54,11 +54,9 @@ cdef class SimplexTree: # The real cython constructor def __cinit__(self, other = None): - cdef SimplexTree ostr if other: if isinstance(other, SimplexTree): - ostr = other - self.thisptr = (new Simplex_tree_interface_full_featured(dereference(ostr.get_ptr()))) + self.thisptr = _get_copy_intptr(other) else: raise TypeError("`other` argument requires to be of type `SimplexTree`, or `None`.") else: @@ -90,12 +88,7 @@ cdef class SimplexTree: :func:`compute_persistence` on it even if you had already computed it in the original. """ stree = SimplexTree() - cdef Simplex_tree_interface_full_featured* stree_ptr - cdef Simplex_tree_interface_full_featured* self_ptr=self.get_ptr() - with nogil: - stree_ptr = new Simplex_tree_interface_full_featured(dereference(self_ptr)) - - stree.thisptr = (stree_ptr) + stree.thisptr = _get_copy_intptr(self) return stree def __deepcopy__(self): @@ -687,3 +680,6 @@ cdef class SimplexTree: :rtype: bool """ return dereference(self.get_ptr()) == dereference(other.get_ptr()) + +cdef intptr_t _get_copy_intptr(SimplexTree stree) nogil: + return (new Simplex_tree_interface_full_featured(dereference(stree.get_ptr()))) -- cgit v1.2.3 From 9799031c52fbe89d70e844382308e4a151e28d14 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 2 Mar 2022 12:30:24 +0100 Subject: Purge GUDHI_USE_EIGEN3 --- src/python/CMakeLists.txt | 6 +----- src/python/gudhi/__init__.py.in | 4 ---- 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src/python') diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index fbfb0d1f..0b51df41 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -148,10 +148,6 @@ if(PYTHONINTERP_FOUND) add_gudhi_debug_info("Eigen3 version ${EIGEN3_VERSION}") # No problem, even if no CGAL found set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DCGAL_EIGEN3_ENABLED', ") - set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DGUDHI_USE_EIGEN3', ") - set(GUDHI_USE_EIGEN3 "True") - else (EIGEN3_FOUND) - set(GUDHI_USE_EIGEN3 "False") endif (EIGEN3_FOUND) set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'off_reader', ") @@ -595,4 +591,4 @@ if(PYTHONINTERP_FOUND) else(PYTHONINTERP_FOUND) message("++ Python module will not be compiled because no Python interpreter was found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python" CACHE INTERNAL "GUDHI_MISSING_MODULES") -endif(PYTHONINTERP_FOUND) \ No newline at end of file +endif(PYTHONINTERP_FOUND) diff --git a/src/python/gudhi/__init__.py.in b/src/python/gudhi/__init__.py.in index 3043201a..79e12fbc 100644 --- a/src/python/gudhi/__init__.py.in +++ b/src/python/gudhi/__init__.py.in @@ -23,10 +23,6 @@ __all__ = [@GUDHI_PYTHON_MODULES@ @GUDHI_PYTHON_MODULES_EXTRA@] __available_modules = '' __missing_modules = '' -# For unitary tests purpose -# could use "if 'collapse_edges' in gudhi.__all__" when collapse edges will have a python module -__GUDHI_USE_EIGEN3 = @GUDHI_USE_EIGEN3@ - # Try to import * from gudhi.__module_name for default modules. # Extra modules require an explicit import by the user (mostly because of # unusual dependencies, but also to avoid cluttering namespace gudhi and -- cgit v1.2.3 From 8667b75601cc0420f3b196d5b44ec93fd6058057 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 21 Mar 2022 17:14:30 +0100 Subject: Add some c++ tests and use unitary_tests_utils. Fix python test --- .../simplex_tree_graph_expansion_unit_test.cpp | 192 ++++++++++++++++----- src/python/test/test_simplex_tree.py | 11 +- 2 files changed, 151 insertions(+), 52 deletions(-) (limited to 'src/python') 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 881a06ae..6d63d8ae 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 @@ -9,33 +9,62 @@ */ #include -#include -#include -#include -#include // std::pair, std::make_pair -#include // float comparison -#include -#include // greater +#include #define BOOST_TEST_DYN_LINK -#define BOOST_TEST_MODULE "simplex_tree" +#define BOOST_TEST_MODULE "simplex_tree_graph_expansion" #include #include -// ^ -// /!\ Nothing else from Simplex_tree shall be included to test includes are well defined. #include "gudhi/Simplex_tree.h" +#include using namespace Gudhi; typedef boost::mpl::list, Simplex_tree> list_of_tested_variants; +BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_all_is_blocked, typeST, list_of_tested_variants) { + std::clog << "********************************************************************\n"; + std::clog << "simplex_tree_expansion_all_is_blocked\n"; + std::clog << "********************************************************************\n"; + using Simplex_handle = typename typeST::Simplex_handle; + // Construct the Simplex Tree with a 1-skeleton graph example + typeST simplex_tree; + + simplex_tree.insert_simplex({0, 1}, 0.); + simplex_tree.insert_simplex({0, 2}, 1.); + simplex_tree.insert_simplex({0, 3}, 2.); + simplex_tree.insert_simplex({1, 2}, 3.); + simplex_tree.insert_simplex({1, 3}, 4.); + simplex_tree.insert_simplex({2, 3}, 5.); + simplex_tree.insert_simplex({2, 4}, 6.); + simplex_tree.insert_simplex({3, 6}, 7.); + simplex_tree.insert_simplex({4, 5}, 8.); + simplex_tree.insert_simplex({4, 6}, 9.); + simplex_tree.insert_simplex({5, 6}, 10.); + simplex_tree.insert_simplex({6}, 10.); -bool AreAlmostTheSame(float a, float b) { - return std::fabs(a - b) < std::numeric_limits::epsilon(); + typeST stree_copy = simplex_tree; + + simplex_tree.expansion_with_blockers(3, [&](Simplex_handle sh){ return true; }); + + std::clog << "* The complex contains " << simplex_tree.num_simplices() << " simplices"; + std::clog << " - dimension " << simplex_tree.dimension() << "\n"; + std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; + for (auto f_simplex : simplex_tree.filtration_simplex_range()) { + std::clog << " " << "[" << simplex_tree.filtration(f_simplex) << "] "; + for (auto vertex : simplex_tree.simplex_vertex_range(f_simplex)) + std::clog << "(" << vertex << ")"; + std::clog << std::endl; + } + + BOOST_CHECK(stree_copy == simplex_tree); } BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_blockers_3, typeST, list_of_tested_variants) { + std::clog << "********************************************************************\n"; + std::clog << "simplex_tree_expansion_with_blockers_3\n"; + std::clog << "********************************************************************\n"; using Simplex_handle = typename typeST::Simplex_handle; // Construct the Simplex Tree with a 1-skeleton graph example typeST simplex_tree; @@ -72,9 +101,6 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_blockers_3, typeST, li return result; }); - std::clog << "********************************************************************\n"; - std::clog << "simplex_tree_expansion_with_blockers_3\n"; - std::clog << "********************************************************************\n"; std::clog << "* The complex contains " << simplex_tree.num_simplices() << " simplices"; std::clog << " - dimension " << simplex_tree.dimension() << "\n"; std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; @@ -89,15 +115,23 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_blockers_3, typeST, li BOOST_CHECK(simplex_tree.dimension() == 3); // {4, 5, 6} shall be blocked BOOST_CHECK(simplex_tree.find({4, 5, 6}) == simplex_tree.null_simplex()); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,2})), 4.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,3})), 5.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,2,3})), 6.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({1,2,3})), 6.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,2,3})), 7.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,2})), + static_cast(4.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,3})), + static_cast(5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,2,3})), + static_cast(6.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({1,2,3})), + static_cast(6.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,2,3})), + static_cast(7.)); } BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_blockers_2, typeST, list_of_tested_variants) { + std::clog << "********************************************************************\n"; + std::clog << "simplex_tree_expansion_with_blockers_2\n"; + std::clog << "********************************************************************\n"; using Simplex_handle = typename typeST::Simplex_handle; // Construct the Simplex Tree with a 1-skeleton graph example typeST simplex_tree; @@ -134,9 +168,6 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_blockers_2, typeST, li return result; }); - std::clog << "********************************************************************\n"; - std::clog << "simplex_tree_expansion_with_blockers_2\n"; - std::clog << "********************************************************************\n"; std::clog << "* The complex contains " << simplex_tree.num_simplices() << " simplices"; std::clog << " - dimension " << simplex_tree.dimension() << "\n"; std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; @@ -151,14 +182,22 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_blockers_2, typeST, li BOOST_CHECK(simplex_tree.dimension() == 2); // {4, 5, 6} shall be blocked BOOST_CHECK(simplex_tree.find({4, 5, 6}) == simplex_tree.null_simplex()); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,2})), 4.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,3})), 5.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,2,3})), 6.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({1,2,3})), 6.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,2})), + static_cast(4.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,3})), + static_cast(5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,2,3})), + static_cast(6.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({1,2,3})), + static_cast(6.)); BOOST_CHECK(simplex_tree.find({0,1,2,3}) == simplex_tree.null_simplex()); } -BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion, typeST, list_of_tested_variants) { +BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_with_find_simplex_blockers, typeST, list_of_tested_variants) { + std::clog << "********************************************************************\n"; + std::clog << "simplex_tree_expansion_with_find_simplex_blockers\n"; + std::clog << "********************************************************************\n"; + using Simplex_handle = typename typeST::Simplex_handle; // Construct the Simplex Tree with a 1-skeleton graph example typeST simplex_tree; @@ -175,10 +214,66 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion, typeST, list_of_tested_var simplex_tree.insert_simplex({5, 6}, 10.); simplex_tree.insert_simplex({6}, 10.); - simplex_tree.expansion(3); + simplex_tree.expansion_with_blockers(3, [&](Simplex_handle sh){ + bool result = false; + std::clog << "Blocker on ["; + std::vector simplex; + // User can loop on the vertices from the given simplex_handle i.e. + for (auto vertex : simplex_tree.simplex_vertex_range(sh)) { + // We block the expansion, if the vertex '1' is in the given list of vertices + if (vertex == 1) + result = true; + std::clog << vertex << ", "; + simplex.push_back(vertex); + } + std::clog << "] => " << result << std::endl; + // Not efficient but test it works - required by the python interface + BOOST_CHECK(simplex_tree.find(simplex) == sh); + return result; + }); + + std::clog << "* The complex contains " << simplex_tree.num_simplices() << " simplices"; + std::clog << " - dimension " << simplex_tree.dimension() << "\n"; + std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; + for (auto f_simplex : simplex_tree.filtration_simplex_range()) { + std::clog << " " << "[" << simplex_tree.filtration(f_simplex) << "] "; + for (auto vertex : simplex_tree.simplex_vertex_range(f_simplex)) + std::clog << "(" << vertex << ")"; + std::clog << std::endl; + } + + BOOST_CHECK(simplex_tree.num_simplices() == 20); + BOOST_CHECK(simplex_tree.dimension() == 2); + + // {1, 2, 3}, {0, 1, 2} and {0, 1, 3} shall be blocked as it contains vertex 1 + BOOST_CHECK(simplex_tree.find({4, 5, 6}) != simplex_tree.null_simplex()); + BOOST_CHECK(simplex_tree.find({1, 2, 3}) == simplex_tree.null_simplex()); + BOOST_CHECK(simplex_tree.find({0, 2, 3}) != simplex_tree.null_simplex()); + BOOST_CHECK(simplex_tree.find({0, 1, 2}) == simplex_tree.null_simplex()); + BOOST_CHECK(simplex_tree.find({0, 1, 3}) == simplex_tree.null_simplex()); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_3, typeST, list_of_tested_variants) { std::clog << "********************************************************************\n"; std::clog << "simplex_tree_expansion_3\n"; std::clog << "********************************************************************\n"; + // Construct the Simplex Tree with a 1-skeleton graph example + typeST simplex_tree; + + simplex_tree.insert_simplex({0, 1}, 0.); + simplex_tree.insert_simplex({0, 2}, 1.); + simplex_tree.insert_simplex({0, 3}, 2.); + simplex_tree.insert_simplex({1, 2}, 3.); + simplex_tree.insert_simplex({1, 3}, 4.); + simplex_tree.insert_simplex({2, 3}, 5.); + simplex_tree.insert_simplex({2, 4}, 6.); + simplex_tree.insert_simplex({3, 6}, 7.); + simplex_tree.insert_simplex({4, 5}, 8.); + simplex_tree.insert_simplex({4, 6}, 9.); + simplex_tree.insert_simplex({5, 6}, 10.); + simplex_tree.insert_simplex({6}, 10.); + + simplex_tree.expansion(3); std::clog << "* The complex contains " << simplex_tree.num_simplices() << " simplices"; std::clog << " - dimension " << simplex_tree.dimension() << "\n"; std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; @@ -192,16 +287,25 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion, typeST, list_of_tested_var BOOST_CHECK(simplex_tree.num_simplices() == 24); BOOST_CHECK(simplex_tree.dimension() == 3); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({4,5,6})), 10.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,2})), 3.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,3})), 4.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,2,3})), 5.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({1,2,3})), 5.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,2,3})), 5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({4,5,6})), + static_cast(10.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,2})), + static_cast(3.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,3})), + static_cast(4.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,2,3})), + static_cast(5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({1,2,3})), + static_cast(5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,2,3})), + static_cast(5.)); } BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_2, typeST, list_of_tested_variants) { + std::clog << "********************************************************************\n"; + std::clog << "simplex_tree_expansion_2\n"; + std::clog << "********************************************************************\n"; // Construct the Simplex Tree with a 1-skeleton graph example typeST simplex_tree; @@ -220,9 +324,6 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_2, typeST, list_of_tested_v simplex_tree.expansion(2); - std::clog << "********************************************************************\n"; - std::clog << "simplex_tree_expansion_2\n"; - std::clog << "********************************************************************\n"; std::clog << "* The complex contains " << simplex_tree.num_simplices() << " simplices"; std::clog << " - dimension " << simplex_tree.dimension() << "\n"; std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; @@ -236,10 +337,15 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(simplex_tree_expansion_2, typeST, list_of_tested_v BOOST_CHECK(simplex_tree.num_simplices() == 23); BOOST_CHECK(simplex_tree.dimension() == 2); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({4,5,6})), 10.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,2})), 3.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,1,3})), 4.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({0,2,3})), 5.)); - BOOST_CHECK(AreAlmostTheSame(simplex_tree.filtration(simplex_tree.find({1,2,3})), 5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({4,5,6})), + static_cast(10.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,2})), + static_cast(3.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,1,3})), + static_cast(4.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({0,2,3})), + static_cast(5.)); + GUDHI_TEST_FLOAT_EQUALITY_CHECK(simplex_tree.filtration(simplex_tree.find({1,2,3})), + static_cast(5.)); BOOST_CHECK(simplex_tree.find({0,1,2,3}) == simplex_tree.null_simplex()); } diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index d8173b52..eb481a49 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -531,8 +531,6 @@ def test_expansion_with_blocker(): st.insert([5,6],10) st.insert([6],10) - # One cannot modify filtration inside blocker - segfault - Let's record accepted simplices - accepted_simp = [] def blocker(simplex): try: # Block all simplices that countains vertex 6 @@ -541,12 +539,10 @@ def test_expansion_with_blocker(): return True except ValueError: print(simplex, ' is accepted') - accepted_simp.append(simplex) + st.assign_filtration(simplex, st.filtration(simplex) + 1.) return False st.expansion_with_blocker(2, blocker) - for simplex in accepted_simp: - st.assign_filtration(simplex, st.filtration(simplex) + 1.) assert st.num_simplices() == 22 assert st.dimension() == 2 assert st.find([4,5,6]) == False @@ -555,10 +551,7 @@ def test_expansion_with_blocker(): assert st.filtration([0,2,3]) == 6. assert st.filtration([1,2,3]) == 6. - accepted_simp = [] st.expansion_with_blocker(3, blocker) - for simplex in accepted_simp: - st.assign_filtration(simplex, st.filtration(simplex) + 1.) assert st.num_simplices() == 23 assert st.dimension() == 3 assert st.find([4,5,6]) == False @@ -566,4 +559,4 @@ def test_expansion_with_blocker(): assert st.filtration([0,1,3]) == 5. assert st.filtration([0,2,3]) == 6. assert st.filtration([1,2,3]) == 6. - assert st.filtration([0,1,2,3]) == 6. + assert st.filtration([0,1,2,3]) == 7. -- cgit v1.2.3 From 40ca49a254b09c7ddba5ed7dbb9b0878fc70afb1 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Thu, 24 Mar 2022 21:24:53 +0100 Subject: Rewrite python doc --- src/python/gudhi/simplex_tree.pyx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index b9c2a42f..3d7c28b6 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -621,12 +621,14 @@ cdef class SimplexTree: return (normal0, normals, infinite0, infinites) def collapse_edges(self, nb_iterations = 1): - """Assuming the simplex tree is a 1-skeleton graph, this method collapse edges (simplices of higher dimension - are ignored) and resets the simplex tree from the remaining edges. - A good candidate is to build a simplex tree on top of a :class:`~gudhi.RipsComplex` of dimension 1 before - collapsing edges + """Assuming the complex is a graph (simplices of higher dimension are ignored), this method implicitly + interprets it as the 1-skeleton of a flag complex, and replaces it with another (smaller) graph whose + expansion has the same persistent homology, using a technique known as edge collapses + (see :cite:`edgecollapsearxiv`). + + A natural application is to get a simplex tree of dimension 1 from :class:`~gudhi.RipsComplex`, + then collapse edges, perform :meth:`expansion()` and finally compute persistence (cf. :download:`rips_complex_edge_collapse_example.py <../example/rips_complex_edge_collapse_example.py>`). - For implementation details, please refer to :cite:`edgecollapsesocg2020`. :param nb_iterations: The number of edge collapse iterations to perform. Default is 1. :type nb_iterations: int -- cgit v1.2.3 From 924302f092d6d08c59500c5f6e75bcad9416581a Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 28 Mar 2022 08:39:21 +0200 Subject: doc review: remove segfault note as it is no more the case --- src/python/gudhi/simplex_tree.pyx | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 644110c1..fc704358 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -487,9 +487,6 @@ cdef class SimplexTree: 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. - Note that you cannot update the filtration value of the simplex during the evaluation of `blocker_func`, as it - would segfault. - :param max_dim: Expansion maximal dimension value. :type max_dim: int :param blocker_func: Blocker oracle. -- cgit v1.2.3 From f6a45247e9a9f126c214d1b1003ae19fb2cc84a3 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 28 Mar 2022 09:00:23 +0200 Subject: doc review: add a warning about phantom simplices for blocker_func --- src/python/gudhi/simplex_tree.pyx | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/python') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index fc704358..a4914184 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -487,6 +487,11 @@ cdef class SimplexTree: 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 `block_simplex`, so + if you examine the complex in `block_simplex`, you may hit a few simplices of the same dimension that have + not been vetted by `block_simplex` 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. -- cgit v1.2.3 From 495f3d9b51f5ed48151118fedcc61f5067f04d51 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Wed, 30 Mar 2022 12:22:56 +0200 Subject: get/set float_relative_precision and its documentation --- src/python/doc/alpha_complex_user.rst | 3 ++- src/python/gudhi/alpha_complex.pyx | 28 ++++++++++++++++++++++++++++ src/python/include/Alpha_complex_interface.h | 10 ++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/doc/alpha_complex_user.rst b/src/python/doc/alpha_complex_user.rst index cfd22742..b060c86e 100644 --- a/src/python/doc/alpha_complex_user.rst +++ b/src/python/doc/alpha_complex_user.rst @@ -27,7 +27,8 @@ Remarks If you pass :code:`precision = 'exact'` to :func:`~gudhi.AlphaComplex.__init__`, the filtration values are the exact ones converted to float. This can be very slow. If you pass :code:`precision = 'safe'` (the default), the filtration values are only - guaranteed to have a small multiplicative error compared to the exact value. + guaranteed to have a small multiplicative error compared to the exact value, see + :func:`~gudhi.AlphaComplex.set_float_relative_precision` to modify the precision. A drawback, when computing persistence, is that an empty exact interval [10^12,10^12] may become a non-empty approximate interval [10^12,10^12+10^6]. Using :code:`precision = 'fast'` makes the computations slightly faster, and the combinatorics are still exact, but diff --git a/src/python/gudhi/alpha_complex.pyx b/src/python/gudhi/alpha_complex.pyx index a4888914..9af62b43 100644 --- a/src/python/gudhi/alpha_complex.pyx +++ b/src/python/gudhi/alpha_complex.pyx @@ -31,6 +31,10 @@ cdef extern from "Alpha_complex_interface.h" namespace "Gudhi": Alpha_complex_interface(vector[vector[double]] points, vector[double] weights, bool fast_version, bool exact_version) nogil except + vector[double] get_point(int vertex) nogil except + void create_simplex_tree(Simplex_tree_interface_full_featured* simplex_tree, double max_alpha_square, bool default_filtration_value) nogil except + + @staticmethod + void set_float_relative_precision(double precision) nogil + @staticmethod + double get_float_relative_precision() nogil # AlphaComplex python interface cdef class AlphaComplex: @@ -133,3 +137,27 @@ cdef class AlphaComplex: self.this_ptr.create_simplex_tree(stree_int_ptr, mas, compute_filtration) return stree + + @staticmethod + def set_float_relative_precision(precision): + """ + :param precision: When the AlphaComplex is constructed with :code:`precision = 'safe'` (the default), + one can set the float relative precision of filtration values computed in + :func:`~gudhi.AlphaComplex.create_simplex_tree`. + Default is :code:`1e-5` (cf. :func:`~gudhi.AlphaComplex.get_float_relative_precision`). + For more details, please refer to + `CGAL::Lazy_exact_nt::set_relative_precision_of_to_double `_ + :type precision: float + """ + if precision <=0. or precision >= 1.: + raise ValueError("Relative precision value must be strictly greater than 0 and strictly lower than 1") + Alpha_complex_interface.set_float_relative_precision(precision) + + @staticmethod + def get_float_relative_precision(): + """ + :returns: The float relative precision of filtration values computation in + :func:`~gudhi.AlphaComplex.create_simplex_tree`. + :rtype: float + """ + return Alpha_complex_interface.get_float_relative_precision() diff --git a/src/python/include/Alpha_complex_interface.h b/src/python/include/Alpha_complex_interface.h index 671af4a4..469b91ce 100644 --- a/src/python/include/Alpha_complex_interface.h +++ b/src/python/include/Alpha_complex_interface.h @@ -57,6 +57,16 @@ class Alpha_complex_interface { alpha_ptr_->create_simplex_tree(simplex_tree, max_alpha_square, default_filtration_value); } + static void set_float_relative_precision(double precision) { + // cf. Exact_alpha_complex_dD kernel type in Alpha_complex_factory.h + CGAL::Epeck_d::FT::set_relative_precision_of_to_double(precision); + } + + static double get_float_relative_precision() { + // cf. Exact_alpha_complex_dD kernel type in Alpha_complex_factory.h + return CGAL::Epeck_d::FT::get_relative_precision_of_to_double(); + } + private: std::unique_ptr alpha_ptr_; }; -- cgit v1.2.3 From 37dcedb59cd2a6b70119688ea24c978749d63154 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Thu, 31 Mar 2022 16:37:20 +0200 Subject: Test the feature --- src/python/test/test_alpha_complex.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src/python') diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index f15284f3..f81e6137 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -286,3 +286,30 @@ def _weighted_doc_example(precision): def test_weighted_doc_example(): for precision in ['fast', 'safe', 'exact']: _weighted_doc_example(precision) + +def test_float_relative_precision(): + assert AlphaComplex.get_float_relative_precision() == 1e-5 + # Must be > 0. + with pytest.raises(ValueError): + AlphaComplex.set_float_relative_precision(0.) + # Must be < 1. + with pytest.raises(ValueError): + AlphaComplex.set_float_relative_precision(1.) + + points = [[1, 1], [7, 0], [4, 6], [9, 6], [0, 14], [2, 19], [9, 17]] + st = AlphaComplex(points=points).create_simplex_tree() + filtrations = list(st.get_filtration()) + + # Get a better precision + AlphaComplex.set_float_relative_precision(1e-15) + assert AlphaComplex.get_float_relative_precision() == 1e-15 + + st = AlphaComplex(points=points).create_simplex_tree() + filtrations_better_resolution = list(st.get_filtration()) + + assert len(filtrations) == len(filtrations_better_resolution) + for idx in range(len(filtrations)): + # check simplex is the same + assert filtrations[idx][0] == filtrations_better_resolution[idx][0] + # check filtration is about the same with a relative precision of the worst case + assert filtrations[idx][1] == pytest.approx(filtrations_better_resolution[idx][1], rel=1e-5) -- cgit v1.2.3 From f4b543bb4891b1c2aa104e45cc5a0d07b56e6977 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Thu, 31 Mar 2022 21:15:18 +0200 Subject: code review: no need to nogil for cheap functions. Repeat get_float_relative_precision is only for safe mode --- src/python/gudhi/alpha_complex.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/alpha_complex.pyx b/src/python/gudhi/alpha_complex.pyx index 9af62b43..e63796db 100644 --- a/src/python/gudhi/alpha_complex.pyx +++ b/src/python/gudhi/alpha_complex.pyx @@ -32,9 +32,9 @@ cdef extern from "Alpha_complex_interface.h" namespace "Gudhi": vector[double] get_point(int vertex) nogil except + void create_simplex_tree(Simplex_tree_interface_full_featured* simplex_tree, double max_alpha_square, bool default_filtration_value) nogil except + @staticmethod - void set_float_relative_precision(double precision) nogil + void set_float_relative_precision(double precision) @staticmethod - double get_float_relative_precision() nogil + double get_float_relative_precision() # AlphaComplex python interface cdef class AlphaComplex: @@ -157,7 +157,8 @@ cdef class AlphaComplex: def get_float_relative_precision(): """ :returns: The float relative precision of filtration values computation in - :func:`~gudhi.AlphaComplex.create_simplex_tree`. + :func:`~gudhi.AlphaComplex.create_simplex_tree` when the AlphaComplex is constructed with + :code:`precision = 'safe'` (the default). :rtype: float """ return Alpha_complex_interface.get_float_relative_precision() -- cgit v1.2.3 From 25172af7cbe5a3ea3017bed6fde53be471a7e4bf Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Tue, 5 Apr 2022 13:35:11 +0200 Subject: nogil for float_relative_precision methods --- src/python/gudhi/alpha_complex.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/alpha_complex.pyx b/src/python/gudhi/alpha_complex.pyx index e63796db..375e1561 100644 --- a/src/python/gudhi/alpha_complex.pyx +++ b/src/python/gudhi/alpha_complex.pyx @@ -32,9 +32,9 @@ cdef extern from "Alpha_complex_interface.h" namespace "Gudhi": vector[double] get_point(int vertex) nogil except + void create_simplex_tree(Simplex_tree_interface_full_featured* simplex_tree, double max_alpha_square, bool default_filtration_value) nogil except + @staticmethod - void set_float_relative_precision(double precision) + void set_float_relative_precision(double precision) nogil @staticmethod - double get_float_relative_precision() + double get_float_relative_precision() nogil # AlphaComplex python interface cdef class AlphaComplex: -- cgit v1.2.3 From 55b15a7c8617b770b51f61861951005a3e989327 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Wed, 6 Apr 2022 10:54:23 +0200 Subject: code review: Rewrite warnings in case of import errors --- src/python/gudhi/persistence_graphical_tools.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 70d550ab..3c166a56 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -97,8 +97,8 @@ def _matplotlib_can_use_tex(): from matplotlib import checkdep_usetex return checkdep_usetex(True) - except ImportError: - warnings.warn("This function is not available, you may be missing matplotlib.") + except ImportError as import_error: + warnings.warn(f"This function is not available.\nModuleNotFoundError: No module named '{import_error.name}'.") def plot_persistence_barcode( @@ -233,8 +233,8 @@ def plot_persistence_barcode( axes.axis([axis_start, infinity, 0, ind]) return axes - except ImportError: - warnings.warn("This function is not available, you may be missing matplotlib.") + except ImportError as import_error: + warnings.warn(f"This function is not available.\nModuleNotFoundError: No module named '{import_error.name}'.") def plot_persistence_diagram( @@ -384,8 +384,8 @@ def plot_persistence_diagram( axes.axis([axis_start, axis_end, axis_start, infinity + delta / 2]) return axes - except ImportError: - warnings.warn("This function is not available, you may be missing matplotlib.") + except ImportError as import_error: + warnings.warn(f"This function is not available.\nModuleNotFoundError: No module named '{import_error.name}'.") def plot_persistence_density( @@ -549,5 +549,5 @@ def plot_persistence_density( return axes - except ImportError: - warnings.warn("This function is not available, you may be missing matplotlib and/or scipy.") + except ImportError as import_error: + warnings.warn(f"This function is not available.\nModuleNotFoundError: No module named '{import_error.name}'.") -- cgit v1.2.3 From 5857c571388a4349934a266ca187b2c2ac10818c Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Thu, 21 Apr 2022 14:44:22 +0200 Subject: Make Windows compilation fails on error (#598) Use '-DEIGEN_DEFAULT_DENSE_INDEX_TYPE=int' as a workaround --- .github/workflows/pip-build-windows.yml | 8 ++++---- .github/workflows/pip-packaging-windows.yml | 8 ++++---- azure-pipelines.yml | 17 ++++++++++++----- src/cmake/modules/GUDHI_third_party_libraries.cmake | 9 +++++++++ src/python/CMakeLists.txt | 12 +++++++++++- 5 files changed, 40 insertions(+), 14 deletions(-) (limited to 'src/python') diff --git a/.github/workflows/pip-build-windows.yml b/.github/workflows/pip-build-windows.yml index 954b59d5..30b0bd94 100644 --- a/.github/workflows/pip-build-windows.yml +++ b/.github/workflows/pip-build-windows.yml @@ -30,14 +30,14 @@ jobs: run: | mkdir build cd ".\build\" - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows .. + cmake -DCMAKE_BUILD_TYPE=Release -DFORCE_EIGEN_DEFAULT_DENSE_INDEX_TYPE_TO_INT=ON -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows .. Get-Location dir cd ".\src\python\" - cp "C:\vcpkg\installed\x64-windows\bin\mpfr-6.dll" ".\gudhi\" - cp "C:\vcpkg\installed\x64-windows\bin\gmp.dll" ".\gudhi\" + cp "C:\vcpkg\installed\x64-windows\bin\mpfr*.dll" ".\gudhi\" + cp "C:\vcpkg\installed\x64-windows\bin\gmp*.dll" ".\gudhi\" python setup.py bdist_wheel - ls dist + ls ".\dist\" cd ".\dist\" Get-ChildItem *.whl | ForEach-Object{python -m pip install --user $_.Name} - name: Test python wheel diff --git a/.github/workflows/pip-packaging-windows.yml b/.github/workflows/pip-packaging-windows.yml index 962ae68a..142a114c 100644 --- a/.github/workflows/pip-packaging-windows.yml +++ b/.github/workflows/pip-packaging-windows.yml @@ -33,14 +33,14 @@ jobs: run: | mkdir build cd ".\build\" - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows .. + cmake -DCMAKE_BUILD_TYPE=Release -DFORCE_EIGEN_DEFAULT_DENSE_INDEX_TYPE_TO_INT=ON -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows .. Get-Location dir cd ".\src\python\" - cp "C:\vcpkg\installed\x64-windows\bin\mpfr-6.dll" ".\gudhi\" - cp "C:\vcpkg\installed\x64-windows\bin\gmp.dll" ".\gudhi\" + cp "C:\vcpkg\installed\x64-windows\bin\mpfr*.dll" ".\gudhi\" + cp "C:\vcpkg\installed\x64-windows\bin\gmp*.dll" ".\gudhi\" python setup.py bdist_wheel - ls dist + ls ".\dist\" cd ".\dist\" Get-ChildItem *.whl | ForEach-Object{python -m pip install --user $_.Name} - name: Test python wheel diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 21664244..a39f6d5c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,7 +30,7 @@ jobs: - bash: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE:STRING=$(cmakeBuildType) -DWITH_GUDHI_TEST=ON -DWITH_GUDHI_UTILITIES=ON -DWITH_GUDHI_PYTHON=ON .. + cmake -DCMAKE_BUILD_TYPE:STRING=$(cmakeBuildType) -DWITH_GUDHI_EXAMPLE=ON -DWITH_GUDHI_TEST=ON -DWITH_GUDHI_UTILITIES=ON -DWITH_GUDHI_PYTHON=ON .. make make doxygen ctest --output-on-failure @@ -64,20 +64,27 @@ jobs: vcpkg install boost-filesystem:x64-windows boost-test:x64-windows boost-program-options:x64-windows tbb:x64-windows eigen3:x64-windows cgal:x64-windows displayName: 'Install build dependencies' - script: | - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64 + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64 + IF %errorlevel% NEQ 0 exit /b %errorlevel% mkdir build cd build - cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Release $(cmakeVcpkgFlags) $(cmakeFlags) .. + cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release -DFORCE_EIGEN_DEFAULT_DENSE_INDEX_TYPE_TO_INT=ON $(cmakeVcpkgFlags) $(cmakeFlags) .. + IF %errorlevel% NEQ 0 exit /b %errorlevel% MSBuild GUDHIdev.sln /m /p:Configuration=Release /p:Platform=x64 + IF %errorlevel% NEQ 0 exit /b %errorlevel% ctest --output-on-failure -C Release -E diff_files + IF %errorlevel% NEQ 0 exit /b %errorlevel% cmake -DWITH_GUDHI_PYTHON=ON . + IF %errorlevel% NEQ 0 exit /b %errorlevel% cd src\python - copy "C:\vcpkg\installed\x64-windows\bin\mpfr-6.dll" ".\gudhi\" - copy "C:\vcpkg\installed\x64-windows\bin\gmp.dll" ".\gudhi\" + copy "C:\vcpkg\installed\x64-windows\bin\mpfr*.dll" ".\gudhi\" + copy "C:\vcpkg\installed\x64-windows\bin\gmp*.dll" ".\gudhi\" copy "C:\vcpkg\installed\x64-windows\bin\tbb.dll" ".\gudhi\" copy "C:\vcpkg\installed\x64-windows\bin\tbbmalloc.dll" ".\gudhi\" python setup.py build_ext --inplace + IF %errorlevel% NEQ 0 exit /b %errorlevel% SET PYTHONPATH=%CD%;%PYTHONPATH% echo %PYTHONPATH% ctest --output-on-failure -C Release + IF %errorlevel% NEQ 0 exit /b %errorlevel% displayName: 'Build and test' diff --git a/src/cmake/modules/GUDHI_third_party_libraries.cmake b/src/cmake/modules/GUDHI_third_party_libraries.cmake index 7c982b3b..6a94d1f5 100644 --- a/src/cmake/modules/GUDHI_third_party_libraries.cmake +++ b/src/cmake/modules/GUDHI_third_party_libraries.cmake @@ -19,6 +19,15 @@ if(GMP_FOUND) endif() endif() +# from windows vcpkg eigen 3.4.0#2 : build fails with +# error C2440: '': cannot convert from 'Eigen::EigenBase::Index' to '__gmp_expr' +# cf. https://gitlab.com/libeigen/eigen/-/issues/2476 +# Workaround is to compile with '-DEIGEN_DEFAULT_DENSE_INDEX_TYPE=int' +if (FORCE_EIGEN_DEFAULT_DENSE_INDEX_TYPE_TO_INT) + message("++ User explicit demand to force EIGEN_DEFAULT_DENSE_INDEX_TYPE to int") + add_definitions(-DEIGEN_DEFAULT_DENSE_INDEX_TYPE=int) +endif() + # In CMakeLists.txt, when include(${CGAL_USE_FILE}), CMAKE_CXX_FLAGS are overwritten. # cf. http://doc.cgal.org/latest/Manual/installation.html#title40 # A workaround is to include(${CGAL_USE_FILE}) before adding "-std=c++11". diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index fbfb0d1f..e31af02e 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -180,6 +180,15 @@ if(PYTHONINTERP_FOUND) set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'alpha_complex', ") endif () + # from windows vcpkg eigen 3.4.0#2 : build fails with + # error C2440: '': cannot convert from 'Eigen::EigenBase::Index' to '__gmp_expr' + # cf. https://gitlab.com/libeigen/eigen/-/issues/2476 + # Workaround is to compile with '-DEIGEN_DEFAULT_DENSE_INDEX_TYPE=int' + if (FORCE_EIGEN_DEFAULT_DENSE_INDEX_TYPE_TO_INT) + set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DEIGEN_DEFAULT_DENSE_INDEX_TYPE=int', ") + endif() + + add_gudhi_debug_info("Boost version ${Boost_VERSION}") if(CGAL_FOUND) if(NOT CGAL_VERSION VERSION_LESS 5.3.0) @@ -215,13 +224,14 @@ if(PYTHONINTERP_FOUND) endif(NOT GMP_LIBRARIES_DIR) add_GUDHI_PYTHON_lib_dir(${GMP_LIBRARIES_DIR}) message("** Add gmp ${GMP_LIBRARIES_DIR}") + # When FORCE_CGAL_NOT_TO_BUILD_WITH_GMPXX is set, not defining CGAL_USE_GMPXX is sufficient enough if(GMPXX_FOUND) add_gudhi_debug_info("GMPXX_LIBRARIES = ${GMPXX_LIBRARIES}") set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DCGAL_USE_GMPXX', ") add_GUDHI_PYTHON_lib("${GMPXX_LIBRARIES}") add_GUDHI_PYTHON_lib_dir(${GMPXX_LIBRARIES_DIR}) message("** Add gmpxx ${GMPXX_LIBRARIES_DIR}") - endif(GMPXX_FOUND) + endif() endif(GMP_FOUND) if(MPFR_FOUND) add_gudhi_debug_info("MPFR_LIBRARIES = ${MPFR_LIBRARIES}") -- cgit v1.2.3 From 79278cfc0aecbfdbf8a66e79ccef44534abb2399 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Fri, 29 Apr 2022 17:48:19 +0200 Subject: plot_persistence_diagram and plot_persistence_barcode improvements --- src/python/gudhi/persistence_graphical_tools.py | 68 ++++++------------------- 1 file changed, 16 insertions(+), 52 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 3c166a56..604018d1 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -105,7 +105,7 @@ def plot_persistence_barcode( persistence=[], persistence_file="", alpha=0.6, - max_intervals=1000, + max_intervals=20000, inf_delta=0.1, legend=False, colormap=None, @@ -127,7 +127,7 @@ def plot_persistence_barcode( :type alpha: float. :param max_intervals: maximal number of intervals to display. Selected intervals are those with the longest life time. Set it - to 0 to see all. Default value is 1000. + to 0 to see all. Default value is 20000. :type max_intervals: int. :param inf_delta: Infinity is placed at :code:`((max_death - min_birth) x inf_delta)` above :code:`max_death` value. A reasonable value is @@ -189,36 +189,11 @@ def plot_persistence_barcode( _, axes = plt.subplots(1, 1) if colormap == None: colormap = plt.cm.Set1.colors - ind = 0 - - # Draw horizontal bars in loop - for interval in reversed(persistence): - try: - if float(interval[1][1]) != float("inf"): - # Finite death case - axes.barh( - ind, - (interval[1][1] - interval[1][0]), - height=0.8, - left=interval[1][0], - alpha=alpha, - color=colormap[interval[0]], - linewidth=0, - ) - else: - # Infinite death case for diagram to be nicer - axes.barh( - ind, - (infinity - interval[1][0]), - height=0.8, - left=interval[1][0], - alpha=alpha, - color=colormap[interval[0]], - linewidth=0, - ) - ind = ind + 1 - except IndexError: - pass + + x=[birth for (dim,(birth,death)) in persistence] + y=[(death - birth) if death != float("inf") else (infinity - birth) for (dim,(birth,death)) in persistence] + c=[colormap[dim] for (dim,(birth,death)) in persistence] + axes.barh(list(reversed(range(len(x)))), y, height=0.8, left=x, alpha=alpha, color=c, linewidth=0) if legend: dimensions = list(set(item[0] for item in persistence)) @@ -229,8 +204,8 @@ def plot_persistence_barcode( axes.set_title("Persistence barcode", fontsize=fontsize) # Ends plot on infinity value and starts a little bit before min_birth - if ind != 0: - axes.axis([axis_start, infinity, 0, ind]) + if len(x) != 0: + axes.axis([axis_start, infinity, 0, len(x)]) return axes except ImportError as import_error: @@ -242,7 +217,7 @@ def plot_persistence_diagram( persistence_file="", alpha=0.6, band=0.0, - max_intervals=1000, + max_intervals=1000000, inf_delta=0.1, legend=False, colormap=None, @@ -266,7 +241,7 @@ def plot_persistence_diagram( :type band: float. :param max_intervals: maximal number of intervals to display. Selected intervals are those with the longest life time. Set it - to 0 to see all. Default value is 1000. + to 0 to see all. Default value is 1000000. :type max_intervals: int. :param inf_delta: Infinity is placed at :code:`((max_death - min_birth) x inf_delta)` above :code:`max_death` value. A reasonable value is @@ -346,22 +321,11 @@ def plot_persistence_diagram( # line display of equation : birth = death axes.plot([axis_start, axis_end], [axis_start, axis_end], linewidth=1.0, color="k") - # Draw points in loop - pts_at_infty = False # Records presence of pts at infty - for interval in reversed(persistence): - try: - if float(interval[1][1]) != float("inf"): - # Finite death case - axes.scatter( - interval[1][0], interval[1][1], alpha=alpha, color=colormap[interval[0]], - ) - else: - pts_at_infty = True - # Infinite death case for diagram to be nicer - axes.scatter(interval[1][0], infinity, alpha=alpha, color=colormap[interval[0]]) - except IndexError: - pass - if pts_at_infty: + x=[birth for (dim,(birth,death)) in persistence] + y=[death if death != float("inf") else infinity for (dim,(birth,death)) in persistence] + c=[colormap[dim] for (dim,(birth,death)) in persistence] + axes.scatter(x,y,alpha=alpha,color=c) + if float("inf") in (death for (dim,(birth,death)) in persistence): # infinity line and text axes.plot([axis_start, axis_end], [infinity, infinity], linewidth=1.0, color="k", alpha=alpha) # Infinity label -- cgit v1.2.3 From 3952b61604db976f4864b85ebdafa0962766b8bc Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Tue, 10 May 2022 10:00:02 +0200 Subject: Fix limit tests for plot (and warning in test) --- src/python/gudhi/persistence_graphical_tools.py | 32 +++++++++++++++------- .../test/test_persistence_graphical_tools.py | 6 +++- 2 files changed, 27 insertions(+), 11 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 604018d1..930df825 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -190,12 +190,17 @@ def plot_persistence_barcode( if colormap == None: colormap = plt.cm.Set1.colors - x=[birth for (dim,(birth,death)) in persistence] - y=[(death - birth) if death != float("inf") else (infinity - birth) for (dim,(birth,death)) in persistence] - c=[colormap[dim] for (dim,(birth,death)) in persistence] + non_empty_diagram = len(persistence[0]) > 0 + if non_empty_diagram: + x=[birth for (dim,(birth,death)) in persistence] + y=[(death - birth) if death != float("inf") else (infinity - birth) for (dim,(birth,death)) in persistence] + c=[colormap[dim] for (dim,(birth,death)) in persistence] + else: + x, y, c = [], [], [] + axes.barh(list(reversed(range(len(x)))), y, height=0.8, left=x, alpha=alpha, color=c, linewidth=0) - if legend: + if non_empty_diagram and legend: dimensions = list(set(item[0] for item in persistence)) axes.legend( handles=[mpatches.Patch(color=colormap[dim], label=str(dim)) for dim in dimensions], loc="lower right", @@ -321,11 +326,16 @@ def plot_persistence_diagram( # line display of equation : birth = death axes.plot([axis_start, axis_end], [axis_start, axis_end], linewidth=1.0, color="k") - x=[birth for (dim,(birth,death)) in persistence] - y=[death if death != float("inf") else infinity for (dim,(birth,death)) in persistence] - c=[colormap[dim] for (dim,(birth,death)) in persistence] + non_empty_diagram = len(persistence[0]) > 0 + if non_empty_diagram: + x=[birth for (dim,(birth,death)) in persistence] + y=[death if death != float("inf") else infinity for (dim,(birth,death)) in persistence] + c=[colormap[dim] for (dim,(birth,death)) in persistence] + else: + x, y, c = [], [], [] + axes.scatter(x,y,alpha=alpha,color=c) - if float("inf") in (death for (dim,(birth,death)) in persistence): + if non_empty_diagram and float("inf") in (death for (dim,(birth,death)) in persistence): # infinity line and text axes.plot([axis_start, axis_end], [infinity, infinity], linewidth=1.0, color="k", alpha=alpha) # Infinity label @@ -337,7 +347,7 @@ def plot_persistence_diagram( axes.set_yticks(yt) axes.set_yticklabels(ytl) - if legend: + if non_empty_diagram and legend: dimensions = list(set(item[0] for item in persistence)) axes.legend(handles=[mpatches.Patch(color=colormap[dim], label=str(dim)) for dim in dimensions]) @@ -482,6 +492,7 @@ def plot_persistence_density( zi = k(np.vstack([xi.flatten(), yi.flatten()])) # Make the plot img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading="auto") + non_empty_diagram = True # IndexError on empty diagrams, ValueError on only inf death values except (IndexError, ValueError): @@ -489,6 +500,7 @@ def plot_persistence_density( birth_max = 1.0 death_min = 0.0 death_max = 1.0 + non_empty_diagram = False pass # line display of equation : birth = death @@ -504,7 +516,7 @@ def plot_persistence_density( ) ) - if legend: + if non_empty_diagram and legend: plt.colorbar(img, ax=axes) axes.set_xlabel("Birth", fontsize=fontsize) diff --git a/src/python/test/test_persistence_graphical_tools.py b/src/python/test/test_persistence_graphical_tools.py index 7d9bae90..994791d7 100644 --- a/src/python/test/test_persistence_graphical_tools.py +++ b/src/python/test/test_persistence_graphical_tools.py @@ -15,7 +15,7 @@ import pytest def test_array_handler(): - diags = np.array([[1, 2], [3, 4], [5, 6]], np.float) + diags = np.array([[1, 2], [3, 4], [5, 6]], float) arr_diags = gd.persistence_graphical_tools._array_handler(diags) for idx in range(len(diags)): assert arr_diags[idx][0] == 0 @@ -98,8 +98,12 @@ def test_limit_to_max_intervals(): def _limit_plot_persistence(function): pplot = function(persistence=[()]) assert issubclass(type(pplot), plt.axes.SubplotBase) + pplot = function(persistence=[()], legend=True) + assert issubclass(type(pplot), plt.axes.SubplotBase) pplot = function(persistence=[(0, float("inf"))]) assert issubclass(type(pplot), plt.axes.SubplotBase) + pplot = function(persistence=[(0, float("inf"))], legend=True) + assert issubclass(type(pplot), plt.axes.SubplotBase) def test_limit_plot_persistence(): -- cgit v1.2.3 From 393216cc99af79526b1bb90ae4da21116884bcbd Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 16 May 2022 13:47:16 +0200 Subject: code review: limit test was not respecting the one described in the documentation --- src/python/gudhi/persistence_graphical_tools.py | 51 +++++++++------------- .../test/test_persistence_graphical_tools.py | 4 +- 2 files changed, 23 insertions(+), 32 deletions(-) (limited to 'src/python') diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 930df825..7ed11360 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -190,17 +190,13 @@ def plot_persistence_barcode( if colormap == None: colormap = plt.cm.Set1.colors - non_empty_diagram = len(persistence[0]) > 0 - if non_empty_diagram: - x=[birth for (dim,(birth,death)) in persistence] - y=[(death - birth) if death != float("inf") else (infinity - birth) for (dim,(birth,death)) in persistence] - c=[colormap[dim] for (dim,(birth,death)) in persistence] - else: - x, y, c = [], [], [] + x=[birth for (dim,(birth,death)) in persistence] + y=[(death - birth) if death != float("inf") else (infinity - birth) for (dim,(birth,death)) in persistence] + c=[colormap[dim] for (dim,(birth,death)) in persistence] axes.barh(list(reversed(range(len(x)))), y, height=0.8, left=x, alpha=alpha, color=c, linewidth=0) - if non_empty_diagram and legend: + if legend: dimensions = list(set(item[0] for item in persistence)) axes.legend( handles=[mpatches.Patch(color=colormap[dim], label=str(dim)) for dim in dimensions], loc="lower right", @@ -326,16 +322,12 @@ def plot_persistence_diagram( # line display of equation : birth = death axes.plot([axis_start, axis_end], [axis_start, axis_end], linewidth=1.0, color="k") - non_empty_diagram = len(persistence[0]) > 0 - if non_empty_diagram: - x=[birth for (dim,(birth,death)) in persistence] - y=[death if death != float("inf") else infinity for (dim,(birth,death)) in persistence] - c=[colormap[dim] for (dim,(birth,death)) in persistence] - else: - x, y, c = [], [], [] + x=[birth for (dim,(birth,death)) in persistence] + y=[death if death != float("inf") else infinity for (dim,(birth,death)) in persistence] + c=[colormap[dim] for (dim,(birth,death)) in persistence] axes.scatter(x,y,alpha=alpha,color=c) - if non_empty_diagram and float("inf") in (death for (dim,(birth,death)) in persistence): + if float("inf") in (death for (dim,(birth,death)) in persistence): # infinity line and text axes.plot([axis_start, axis_end], [infinity, infinity], linewidth=1.0, color="k", alpha=alpha) # Infinity label @@ -347,7 +339,7 @@ def plot_persistence_diagram( axes.set_yticks(yt) axes.set_yticklabels(ytl) - if non_empty_diagram and legend: + if legend: dimensions = list(set(item[0] for item in persistence)) axes.legend(handles=[mpatches.Patch(color=colormap[dim], label=str(dim)) for dim in dimensions]) @@ -459,16 +451,15 @@ def plot_persistence_density( _, axes = plt.subplots(1, 1) try: - if len(persistence) > 0: - # if not read from file but given by an argument - persistence = _array_handler(persistence) - persistence_dim = np.array( - [ - (dim_interval[1][0], dim_interval[1][1]) - for dim_interval in persistence - if (dim_interval[0] == dimension) or (dimension is None) - ] - ) + # if not read from file but given by an argument + persistence = _array_handler(persistence) + persistence_dim = np.array( + [ + (dim_interval[1][0], dim_interval[1][1]) + for dim_interval in persistence + if (dim_interval[0] == dimension) or (dimension is None) + ] + ) persistence_dim = persistence_dim[np.isfinite(persistence_dim[:, 1])] persistence_dim = np.array( _limit_to_max_intervals( @@ -492,7 +483,7 @@ def plot_persistence_density( zi = k(np.vstack([xi.flatten(), yi.flatten()])) # Make the plot img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap, shading="auto") - non_empty_diagram = True + plot_success = True # IndexError on empty diagrams, ValueError on only inf death values except (IndexError, ValueError): @@ -500,7 +491,7 @@ def plot_persistence_density( birth_max = 1.0 death_min = 0.0 death_max = 1.0 - non_empty_diagram = False + plot_success = False pass # line display of equation : birth = death @@ -516,7 +507,7 @@ def plot_persistence_density( ) ) - if non_empty_diagram and legend: + if plot_success and legend: plt.colorbar(img, ax=axes) axes.set_xlabel("Birth", fontsize=fontsize) diff --git a/src/python/test/test_persistence_graphical_tools.py b/src/python/test/test_persistence_graphical_tools.py index 994791d7..44decdd7 100644 --- a/src/python/test/test_persistence_graphical_tools.py +++ b/src/python/test/test_persistence_graphical_tools.py @@ -96,9 +96,9 @@ def test_limit_to_max_intervals(): def _limit_plot_persistence(function): - pplot = function(persistence=[()]) + pplot = function(persistence=[]) assert issubclass(type(pplot), plt.axes.SubplotBase) - pplot = function(persistence=[()], legend=True) + pplot = function(persistence=[], legend=True) assert issubclass(type(pplot), plt.axes.SubplotBase) pplot = function(persistence=[(0, float("inf"))]) assert issubclass(type(pplot), plt.axes.SubplotBase) -- cgit v1.2.3 From 563295694afda3dfa37cccb63a73a9f22131480e Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau Date: Mon, 16 May 2022 17:14:29 +0200 Subject: code review: use isinstance(x, T) instead of issubclass(type(x),T) --- src/python/test/test_persistence_graphical_tools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/python') diff --git a/src/python/test/test_persistence_graphical_tools.py b/src/python/test/test_persistence_graphical_tools.py index 44decdd7..c19836b7 100644 --- a/src/python/test/test_persistence_graphical_tools.py +++ b/src/python/test/test_persistence_graphical_tools.py @@ -97,13 +97,13 @@ def test_limit_to_max_intervals(): def _limit_plot_persistence(function): pplot = function(persistence=[]) - assert issubclass(type(pplot), plt.axes.SubplotBase) + assert isinstance(pplot, plt.axes.SubplotBase) pplot = function(persistence=[], legend=True) - assert issubclass(type(pplot), plt.axes.SubplotBase) + assert isinstance(pplot, plt.axes.SubplotBase) pplot = function(persistence=[(0, float("inf"))]) - assert issubclass(type(pplot), plt.axes.SubplotBase) + assert isinstance(pplot, plt.axes.SubplotBase) pplot = function(persistence=[(0, float("inf"))], legend=True) - assert issubclass(type(pplot), plt.axes.SubplotBase) + assert isinstance(pplot, plt.axes.SubplotBase) def test_limit_plot_persistence(): -- cgit v1.2.3 From 5eecc6cf6dabbb41d63be60adb7cde7a07a0b27c Mon Sep 17 00:00:00 2001 From: albert-github Date: Fri, 20 May 2022 18:25:17 +0200 Subject: Documentation: Updating redirected links Updating simple links in documentation sources for which we got during link checking: ``` This is a permanent redirect. The link should be updated. ``` --- biblio/bibliography.bib | 16 ++++++++-------- src/Nerve_GIC/doc/Intro_graph_induced_complex.h | 2 +- src/common/doc/installation.h | 12 ++++++------ src/python/doc/nerve_gic_complex_user.rst | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src/python') diff --git a/biblio/bibliography.bib b/biblio/bibliography.bib index ec8772aa..8c9ee84a 100644 --- a/biblio/bibliography.bib +++ b/biblio/bibliography.bib @@ -14,7 +14,7 @@ publisher = {JMLR.org}, title = {{Statistical analysis and parameter selection for Mapper}}, volume = {19}, year = {2018}, -url = {http://jmlr.org/papers/v19/17-291.html}, +url = {https://jmlr.org/papers/v19/17-291.html}, } @inproceedings{Dey13, @@ -151,10 +151,10 @@ language={English}, %% hal-00922572, version 2 -%% http://hal.inria.fr/hal-00922572 +%% https://hal.inria.fr/hal-00922572 @techreport{boissonnat:hal-00922572, hal_id = {hal-00922572}, - url = {http://hal.inria.fr/hal-00922572}, + url = {https://hal.inria.fr/hal-00922572}, title = {Computing Persistent Homology with Various Coefficient Fields in a Single Pass}, author = {Boissonnat, Jean-Daniel and Maria, Cl{\'e}ment}, abstract = {{In this article, we introduce the multi-field persistence diagram for the persistence homology of a filtered complex. It encodes compactly the superimposition of the persistence diagrams of the complex with several field coefficients, and provides a substantially more precise description of the topology of the filtered complex. Specifically, the multi-field persistence diagram encodes the Betti numbers of integral homology and the prime divisors of the torsion coefficients of the underlying shape. Moreover, it enjoys similar stability properties as the ones of standard persistence diagrams, with the appropriate notion of distance. These properties make the multi-field persistence diagram a useful tool in computational topology.}}, @@ -167,7 +167,7 @@ language={English}, number = {RR-8436}, year = {2013}, month = Dec, - pdf = {http://hal.inria.fr/hal-00922572/PDF/RR-8436.pdf}, + pdf = {https://hal.inria.fr/hal-00922572/PDF/RR-8436.pdf}, } @@ -323,7 +323,7 @@ language={English}, %------------------------------------------------------------------ @article{rips2012, hal_id = {hal-00785072}, - url = {http://hal.archives-ouvertes.fr/hal-00785072}, + url = {https://hal.archives-ouvertes.fr/hal-00785072}, title = {{Vietoris-Rips Complexes also Provide Topologically Correct Reconstructions of Sampled Shapes}}, author = {Attali, Dominique and Lieutier, Andr{\'e} and Salinas, David}, keywords = {Shape reconstruction \sep Rips complexes \sep clique complexes \sep \v Cech complexes ; homotopy equivalence ; collapses ; high dimensions}, @@ -1115,7 +1115,7 @@ language={English} author = {Nicholas J. Cavanna and Mahmoodreza Jahanseir and Donald R. Sheehy}, booktitle = {Proceedings of the Canadian Conference on Computational Geometry}, title = {A Geometric Perspective on Sparse Filtrations}, - url = {http://research.cs.queensu.ca/cccg2015/CCCG15-papers/01.pdf}, + url = {https://research.cs.queensu.ca/cccg2015/CCCG15-papers/01.pdf}, year = {2015} } @@ -1151,7 +1151,7 @@ language={English} editor = {Lars Arge and J{\'a}nos Pach}, publisher = {Schloss Dagstuhl--Leibniz-Zentrum fuer Informatik}, address = {Dagstuhl, Germany}, - URL = {http://drops.dagstuhl.de/opus/volltexte/2015/5098}, + URL = {https://drops.dagstuhl.de/opus/volltexte/2015/5098}, URN = {urn:nbn:de:0030-drops-50981}, doi = {10.4230/LIPIcs.SOCG.2015.642}, annote = {Keywords: Simplicial complex, Compact data structures, Automaton, NP-hard} @@ -1164,7 +1164,7 @@ language={English} journal = {CoRR}, volume = {abs/1607.08449}, year = {2016}, - url = {http://arxiv.org/abs/1607.08449}, + url = {https://arxiv.org/abs/1607.08449}, archivePrefix = {arXiv}, eprint = {1607.08449}, timestamp = {Mon, 13 Aug 2018 16:46:26 +0200}, diff --git a/src/Nerve_GIC/doc/Intro_graph_induced_complex.h b/src/Nerve_GIC/doc/Intro_graph_induced_complex.h index a6098860..e1ab7cb3 100644 --- a/src/Nerve_GIC/doc/Intro_graph_induced_complex.h +++ b/src/Nerve_GIC/doc/Intro_graph_induced_complex.h @@ -24,7 +24,7 @@ namespace cover_complex { * Visualizations of the simplicial complexes can be done with either * neato (from graphviz), * geomview, - * KeplerMapper. + * KeplerMapper. * Input point clouds are assumed to be \ref FileFormatsOFF "OFF files" * * \section covers Covers diff --git a/src/common/doc/installation.h b/src/common/doc/installation.h index 67d026bd..7502f394 100644 --- a/src/common/doc/installation.h +++ b/src/common/doc/installation.h @@ -5,8 +5,8 @@ * Examples of GUDHI headers inclusion can be found in \ref utilities. * * \section compiling Compiling - * The library uses c++14 and requires Boost ≥ 1.66.0 - * and CMake ≥ 3.5. + * The library uses c++14 and requires Boost ≥ 1.66.0 + * and CMake ≥ 3.5. * It is a multi-platform library and compiles on Linux, Mac OSX and Visual Studio 2015. * * \subsection utilities Utilities and examples @@ -56,7 +56,7 @@ make \endverbatim * The multi-field persistent homology algorithm requires GMP which is a free library for arbitrary-precision * arithmetic, operating on signed integers, rational numbers, and floating point numbers. * - * The following example requires the GNU Multiple Precision Arithmetic + * The following example requires the GNU Multiple Precision Arithmetic * Library (GMP) and will not be built if GMP is not installed: * \li * Persistent_cohomology/rips_multifield_persistence.cpp @@ -131,10 +131,10 @@ make \endverbatim * * \subsection eigen Eigen * Some GUDHI modules (cf. \ref main_page "modules list"), and few examples require - * Eigen is a C++ template library for linear algebra: + * Eigen is a C++ template library for linear algebra: * matrices, vectors, numerical solvers, and related algorithms. * - * The following examples/utilities require the Eigen and will not be + * The following examples/utilities require the Eigen and will not be * built if Eigen is not installed: * \li * Alpha_complex/Alpha_complex_from_off.cpp @@ -180,7 +180,7 @@ make \endverbatim * Coxeter_triangulation/manifold_tracing_flat_torus_with_boundary.cpp * * \subsection tbb Threading Building Blocks - * Intel® TBB lets you easily write parallel + * Intel® TBB lets you easily write parallel * C++ programs that take full advantage of multicore performance, that are portable and composable, and that have * future-proof scalability. * diff --git a/src/python/doc/nerve_gic_complex_user.rst b/src/python/doc/nerve_gic_complex_user.rst index 0b820abf..8633cadb 100644 --- a/src/python/doc/nerve_gic_complex_user.rst +++ b/src/python/doc/nerve_gic_complex_user.rst @@ -12,7 +12,7 @@ Definition Visualizations of the simplicial complexes can be done with either neato (from `graphviz `_), `geomview `_, -`KeplerMapper `_. +`KeplerMapper `_. Input point clouds are assumed to be OFF files (cf. `OFF file format `_). Covers -- cgit v1.2.3 From 5520a72287239a6b39d771df3b2511307709a4df Mon Sep 17 00:00:00 2001 From: albert-github Date: Sat, 21 May 2022 14:31:24 +0200 Subject: Documentation: Non persistent links During the link check we found some references to `#title..` in respect to links to CGAL, these links work at the moment but are, by CGAL, generated links during runtime based on the HTML `` tags. When a new paragraph or chapter is added these numbers will change and thus will the link point to the wrong place. All these items have also a more persistent reference anchor that is generated based on the `` of the section (e.g. `\section `). Therefore it is better to used the references based on the ``. --- src/Alpha_complex/doc/Intro_alpha_complex.h | 6 +++--- src/Alpha_complex/include/gudhi/Alpha_complex_3d.h | 2 +- src/Alpha_complex/utilities/alphacomplex.md | 4 ++-- src/python/doc/alpha_complex_user.rst | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/python') diff --git a/src/Alpha_complex/doc/Intro_alpha_complex.h b/src/Alpha_complex/doc/Intro_alpha_complex.h index 5ab23720..ca48ebd2 100644 --- a/src/Alpha_complex/doc/Intro_alpha_complex.h +++ b/src/Alpha_complex/doc/Intro_alpha_complex.h @@ -164,11 +164,11 @@ Table of Contents * Requires: \ref eigen ≥ 3.1.0 and \ref cgal ≥ 5.1.0. * * A weighted version for Alpha complex is available (cf. Alpha_complex). It is like a usual Alpha complex, but based - * on a CGAL regular triangulation instead + * on a CGAL regular triangulation instead * of Delaunay. * * This example builds the CGAL weighted alpha shapes from a small molecule, and initializes the alpha complex with - * it. This example is taken from CGAL 3d + * it. This example is taken from CGAL 3d * weighted alpha shapes. * * Then, it is asked to display information about the alpha complex. @@ -212,7 +212,7 @@ Table of Contents * Gudhi::alpha_complex::complexity::EXACT. * * This example builds the CGAL 3d weighted alpha shapes from a small molecule, and initializes the alpha complex with - * it. This example is taken from CGAL 3d + * it. This example is taken from CGAL 3d * weighted alpha shapes. * * Then, it is asked to display information about the alpha complex. diff --git a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h index b3dbc9bb..562ef139 100644 --- a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h +++ b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h @@ -98,7 +98,7 @@ struct Value_from_iterator { * \tparam Periodic Boolean used to set/unset the periodic version of Alpha_complex_3d. Default value is false. * * For the weighted version, weights values are explained on CGAL - * Alpha shapes 3d and + * Alpha shapes 3d and * Regular * triangulation documentation. * diff --git a/src/Alpha_complex/utilities/alphacomplex.md b/src/Alpha_complex/utilities/alphacomplex.md index 0d3c6027..1e3b8fab 100644 --- a/src/Alpha_complex/utilities/alphacomplex.md +++ b/src/Alpha_complex/utilities/alphacomplex.md @@ -64,7 +64,7 @@ N.B.: * Weights values are explained on CGAL [dD Triangulations](https://doc.cgal.org/latest/Triangulation/index.html) and -[Regular triangulation](https://doc.cgal.org/latest/Triangulation/index.html#title20) documentation. +[Regular triangulation](https://doc.cgal.org/latest/Triangulation/index.html#TriangulationSecRT) documentation. ## alpha_complex_3d_persistence ## @@ -131,6 +131,6 @@ N.B.: * `alpha_complex_3d_persistence` only accepts OFF files in dimension 3. * Filtration values are alpha square values. * Weights values are explained on CGAL -[Alpha shape](https://doc.cgal.org/latest/Alpha_shapes_3/index.html#title0) +[Alpha shape](https://doc.cgal.org/latest/Alpha_shapes_3/index.html#Alpha_shapes_3Definitions) and [Regular triangulation](https://doc.cgal.org/latest/Triangulation_3/index.html#Triangulation3secclassRegulartriangulation) documentation. diff --git a/src/python/doc/alpha_complex_user.rst b/src/python/doc/alpha_complex_user.rst index b060c86e..4082cd9d 100644 --- a/src/python/doc/alpha_complex_user.rst +++ b/src/python/doc/alpha_complex_user.rst @@ -178,11 +178,11 @@ Weighted version ^^^^^^^^^^^^^^^^ A weighted version for Alpha complex is available. It is like a usual Alpha complex, but based on a -`CGAL regular triangulation `_. +`CGAL regular triangulation `_. This example builds the weighted alpha-complex of a small molecule, where atoms have different sizes. It is taken from -`CGAL 3d weighted alpha shapes `_. +`CGAL 3d weighted alpha shapes `_. Then, it is asked to display information about the alpha complex. -- cgit v1.2.3 From a3a3749ac3a1d960d99ad7ce0d7f1463365ee657 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Mon, 23 May 2022 09:23:41 +0200 Subject: [skip ci] example from a small molecule --- src/python/doc/alpha_complex_user.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/python') diff --git a/src/python/doc/alpha_complex_user.rst b/src/python/doc/alpha_complex_user.rst index 4082cd9d..9e67d38a 100644 --- a/src/python/doc/alpha_complex_user.rst +++ b/src/python/doc/alpha_complex_user.rst @@ -182,7 +182,7 @@ A weighted version for Alpha complex is available. It is like a usual Alpha comp This example builds the weighted alpha-complex of a small molecule, where atoms have different sizes. It is taken from -`CGAL 3d weighted alpha shapes `_. +`CGAL 3d weighted alpha shapes `_. Then, it is asked to display information about the alpha complex. -- cgit v1.2.3