From a064f5698fedbe13f6c343cb0b82e0f4d72caffb Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Mon, 27 Jan 2020 17:37:31 +0100 Subject: A first naive iterator implementation with yield --- src/Simplex_tree/example/simple_simplex_tree.cpp | 4 ++++ src/python/gudhi/simplex_tree.pxd | 8 ++++++- src/python/gudhi/simplex_tree.pyx | 18 +++++++-------- src/python/include/Simplex_tree_interface.h | 28 ++++++++++++++---------- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/Simplex_tree/example/simple_simplex_tree.cpp b/src/Simplex_tree/example/simple_simplex_tree.cpp index 4353939f..92ab923b 100644 --- a/src/Simplex_tree/example/simple_simplex_tree.cpp +++ b/src/Simplex_tree/example/simple_simplex_tree.cpp @@ -165,6 +165,10 @@ int main(int argc, char* const argv[]) { // ++ GENERAL VARIABLE SET + //std::vector::const_iterator + std::vector::const_iterator begin = simplexTree.filtration_simplex_range().begin(); + auto end = simplexTree.filtration_simplex_range().end(); + std::cout << "********************************************************************\n"; // Display the Simplex_tree - Can not be done in the middle of 2 inserts std::cout << "* The complex contains " << simplexTree.num_simplices() << " simplices\n"; diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 96d14079..caf3c459 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -21,6 +21,9 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_options_full_featured: pass + cdef cppclass Simplex_tree_simplex_handle "Gudhi::Simplex_tree_interface::Simplex_handle": + pass + cdef cppclass Simplex_tree_interface_full_featured "Gudhi::Simplex_tree_interface": Simplex_tree() double simplex_filtration(vector[int] simplex) @@ -34,7 +37,6 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": bool find_simplex(vector[int] simplex) bool insert_simplex_and_subfaces(vector[int] simplex, double filtration) - vector[pair[vector[int], double]] get_filtration() vector[pair[vector[int], double]] get_skeleton(int dimension) vector[pair[vector[int], double]] get_star(vector[int] simplex) vector[pair[vector[int], double]] get_cofaces(vector[int] simplex, @@ -43,6 +45,10 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": void remove_maximal_simplex(vector[int] simplex) bool prune_above_filtration(double filtration) bool make_filtration_non_decreasing() + # Iterators over Simplex tree + pair[vector[int], double] get_simplex_filtration(Simplex_tree_simplex_handle f_simplex) + vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_begin() + vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_end() 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 b18627c4..478139de 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -7,6 +7,7 @@ # Modification(s): # - YYYY/MM Author: Description of the modification +from cython.operator import dereference, preincrement from libc.stdint cimport intptr_t from numpy import array as np_array cimport simplex_tree @@ -214,15 +215,14 @@ cdef class SimplexTree: :returns: The simplices sorted by increasing filtration values. :rtype: list of tuples(simplex, filtration) """ - cdef vector[pair[vector[int], double]] filtration \ - = self.get_ptr().get_filtration() - ct = [] - for filtered_complex in filtration: - v = [] - for vertex in filtered_complex.first: - v.append(vertex) - ct.append((v, filtered_complex.second)) - return ct + cdef vector[Simplex_tree_simplex_handle].const_iterator it = self.get_ptr().get_filtration_iterator_begin() + cdef vector[Simplex_tree_simplex_handle].const_iterator end = self.get_ptr().get_filtration_iterator_end() + + while True: + yield(self.get_ptr().get_simplex_filtration(dereference(it))) + preincrement(it) + if it == end: + raise StopIteration def get_skeleton(self, dimension): """This function returns the (simplices of the) skeleton of a maximum diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 06f31341..843966cd 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -33,7 +33,8 @@ class Simplex_tree_interface : public Simplex_tree { using Simplex_handle = typename Base::Simplex_handle; using Insertion_result = typename std::pair; using Simplex = std::vector; - using Filtered_simplices = std::vector>; + using Filtered_simplex = std::pair; + using Filtered_simplices = std::vector; public: bool find_simplex(const Simplex& vh) { @@ -82,17 +83,12 @@ class Simplex_tree_interface : public Simplex_tree { Base::initialize_filtration(); } - Filtered_simplices get_filtration() { - Base::initialize_filtration(); - Filtered_simplices filtrations; - for (auto f_simplex : Base::filtration_simplex_range()) { - Simplex simplex; - for (auto vertex : Base::simplex_vertex_range(f_simplex)) { - simplex.insert(simplex.begin(), vertex); - } - filtrations.push_back(std::make_pair(simplex, Base::filtration(f_simplex))); + Filtered_simplex get_simplex_filtration(Simplex_handle f_simplex) { + Simplex simplex; + for (auto vertex : Base::simplex_vertex_range(f_simplex)) { + simplex.insert(simplex.begin(), vertex); } - return filtrations; + return std::make_pair(simplex, Base::filtration(f_simplex)); } Filtered_simplices get_skeleton(int dimension) { @@ -135,6 +131,16 @@ class Simplex_tree_interface : public Simplex_tree { Base::initialize_filtration(); pcoh = new Gudhi::Persistent_cohomology_interface(*this); } + + // Iterator over the simplex tree + typename std::vector::const_iterator get_filtration_iterator_begin() { + Base::initialize_filtration(); + return Base::filtration_simplex_range().begin(); + } + + typename std::vector::const_iterator get_filtration_iterator_end() { + return Base::filtration_simplex_range().end(); + } }; } // namespace Gudhi -- cgit v1.2.3 From 0b77fdd5d9bd057103cb23020089a6628c1f14e6 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Mon, 27 Jan 2020 17:39:48 +0100 Subject: Rollback unnecessary --- src/Simplex_tree/example/simple_simplex_tree.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Simplex_tree/example/simple_simplex_tree.cpp b/src/Simplex_tree/example/simple_simplex_tree.cpp index 92ab923b..4353939f 100644 --- a/src/Simplex_tree/example/simple_simplex_tree.cpp +++ b/src/Simplex_tree/example/simple_simplex_tree.cpp @@ -165,10 +165,6 @@ int main(int argc, char* const argv[]) { // ++ GENERAL VARIABLE SET - //std::vector::const_iterator - std::vector::const_iterator begin = simplexTree.filtration_simplex_range().begin(); - auto end = simplexTree.filtration_simplex_range().end(); - std::cout << "********************************************************************\n"; // Display the Simplex_tree - Can not be done in the middle of 2 inserts std::cout << "* The complex contains " << simplexTree.num_simplices() << " simplices\n"; -- cgit v1.2.3 From ef2c5b53e88321f07ad93496f00dde16dc20f018 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 28 Jan 2020 11:05:39 +0100 Subject: Code review: rename get_simplex_filtration with get_simplex_and_filtration. Remove exception raise. Fix failed tests. Reword documentation --- .../example/alpha_complex_from_points_example.py | 5 +- .../example/rips_complex_from_points_example.py | 5 +- src/python/example/simplex_tree_example.py | 5 +- src/python/gudhi/simplex_tree.pxd | 2 +- src/python/gudhi/simplex_tree.pyx | 10 +-- src/python/include/Simplex_tree_interface.h | 6 +- src/python/test/test_alpha_complex.py | 50 ++++++------ src/python/test/test_euclidean_witness_complex.py | 46 ++++++----- src/python/test/test_rips_complex.py | 53 +++++++------ src/python/test/test_simplex_tree.py | 90 +++++++++++----------- src/python/test/test_tangential_complex.py | 19 +++-- 11 files changed, 161 insertions(+), 130 deletions(-) diff --git a/src/python/example/alpha_complex_from_points_example.py b/src/python/example/alpha_complex_from_points_example.py index 844d7a82..465632eb 100755 --- a/src/python/example/alpha_complex_from_points_example.py +++ b/src/python/example/alpha_complex_from_points_example.py @@ -47,7 +47,10 @@ else: print("[4] Not found...") print("dimension=", simplex_tree.dimension()) -print("filtrations=", simplex_tree.get_filtration()) +print("filtrations=") +for simplex_with_filtration in simplex_tree.get_filtration(): + print("(%s, %.2f)" % tuple(simplex_with_filtration)) + print("star([0])=", simplex_tree.get_star([0])) print("coface([0], 1)=", simplex_tree.get_cofaces([0], 1)) diff --git a/src/python/example/rips_complex_from_points_example.py b/src/python/example/rips_complex_from_points_example.py index 59d8a261..c05703c6 100755 --- a/src/python/example/rips_complex_from_points_example.py +++ b/src/python/example/rips_complex_from_points_example.py @@ -22,6 +22,9 @@ rips = gudhi.RipsComplex(points=[[0, 0], [1, 0], [0, 1], [1, 1]], max_edge_lengt simplex_tree = rips.create_simplex_tree(max_dimension=1) -print("filtrations=", simplex_tree.get_filtration()) +print("filtrations=") +for simplex_with_filtration in simplex_tree.get_filtration(): + print("(%s, %.2f)" % tuple(simplex_with_filtration)) + print("star([0])=", simplex_tree.get_star([0])) print("coface([0], 1)=", simplex_tree.get_cofaces([0], 1)) diff --git a/src/python/example/simplex_tree_example.py b/src/python/example/simplex_tree_example.py index 30de00da..7f20c389 100755 --- a/src/python/example/simplex_tree_example.py +++ b/src/python/example/simplex_tree_example.py @@ -39,7 +39,10 @@ else: print("dimension=", st.dimension()) st.initialize_filtration() -print("filtration=", st.get_filtration()) +print("filtration=") +for simplex_with_filtration in st.get_filtration(): + print("(%s, %.2f)" % tuple(simplex_with_filtration)) + print("filtration[1, 2]=", st.filtration([1, 2])) print("filtration[4, 2]=", st.filtration([4, 2])) diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index caf3c459..1b0dc881 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -46,7 +46,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": bool prune_above_filtration(double filtration) bool make_filtration_non_decreasing() # Iterators over Simplex tree - pair[vector[int], double] get_simplex_filtration(Simplex_tree_simplex_handle f_simplex) + pair[vector[int], double] get_simplex_and_filtration(Simplex_tree_simplex_handle f_simplex) vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_begin() vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_end() diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 478139de..22978b6e 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -209,20 +209,18 @@ cdef class SimplexTree: filtration) def get_filtration(self): - """This function returns a list of all simplices with their given + """This function returns a generator with simplices and their given filtration values. :returns: The simplices sorted by increasing filtration values. - :rtype: list of tuples(simplex, filtration) + :rtype: generator with tuples(simplex, filtration) """ cdef vector[Simplex_tree_simplex_handle].const_iterator it = self.get_ptr().get_filtration_iterator_begin() cdef vector[Simplex_tree_simplex_handle].const_iterator end = self.get_ptr().get_filtration_iterator_end() - while True: - yield(self.get_ptr().get_simplex_filtration(dereference(it))) + while it != end: + yield(self.get_ptr().get_simplex_and_filtration(dereference(it))) preincrement(it) - if it == end: - raise StopIteration def get_skeleton(self, dimension): """This function returns the (simplices of the) skeleton of a maximum diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 843966cd..c0bbc3d9 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -33,8 +33,8 @@ class Simplex_tree_interface : public Simplex_tree { using Simplex_handle = typename Base::Simplex_handle; using Insertion_result = typename std::pair; using Simplex = std::vector; - using Filtered_simplex = std::pair; - using Filtered_simplices = std::vector; + using Simplex_and_filtration = std::pair; + using Filtered_simplices = std::vector; public: bool find_simplex(const Simplex& vh) { @@ -83,7 +83,7 @@ class Simplex_tree_interface : public Simplex_tree { Base::initialize_filtration(); } - Filtered_simplex get_simplex_filtration(Simplex_handle f_simplex) { + Simplex_and_filtration get_simplex_and_filtration(Simplex_handle f_simplex) { Simplex simplex; for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex.insert(simplex.begin(), vertex); diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index 3761fe16..ceead919 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -40,19 +40,21 @@ def test_infinite_alpha(): assert simplex_tree.num_simplices() == 11 assert simplex_tree.num_vertices() == 4 - assert simplex_tree.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([2], 0.0), - ([3], 0.0), - ([0, 1], 0.25), - ([0, 2], 0.25), - ([1, 3], 0.25), - ([2, 3], 0.25), - ([1, 2], 0.5), - ([0, 1, 2], 0.5), - ([1, 2, 3], 0.5), - ] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([3], 0.0)) + assert(next(filtration_generator) == ([0, 1], 0.25)) + assert(next(filtration_generator) == ([0, 2], 0.25)) + assert(next(filtration_generator) == ([1, 3], 0.25)) + assert(next(filtration_generator) == ([2, 3], 0.25)) + assert(next(filtration_generator) == ([1, 2], 0.5)) + assert(next(filtration_generator) == ([0, 1, 2], 0.5)) + assert(next(filtration_generator) == ([1, 2, 3], 0.5)) + with pytest.raises(StopIteration): + next(filtration_generator) + assert simplex_tree.get_star([0]) == [ ([0], 0.0), ([0, 1], 0.25), @@ -105,16 +107,18 @@ def test_filtered_alpha(): else: assert False - assert simplex_tree.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([2], 0.0), - ([3], 0.0), - ([0, 1], 0.25), - ([0, 2], 0.25), - ([1, 3], 0.25), - ([2, 3], 0.25), - ] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([3], 0.0)) + assert(next(filtration_generator) == ([0, 1], 0.25)) + assert(next(filtration_generator) == ([0, 2], 0.25)) + assert(next(filtration_generator) == ([1, 3], 0.25)) + assert(next(filtration_generator) == ([2, 3], 0.25)) + with pytest.raises(StopIteration): + next(filtration_generator) + assert simplex_tree.get_star([0]) == [([0], 0.0), ([0, 1], 0.25), ([0, 2], 0.25)] assert simplex_tree.get_cofaces([0], 1) == [([0, 1], 0.25), ([0, 2], 0.25)] diff --git a/src/python/test/test_euclidean_witness_complex.py b/src/python/test/test_euclidean_witness_complex.py index c18d2484..16ff1ef4 100755 --- a/src/python/test/test_euclidean_witness_complex.py +++ b/src/python/test/test_euclidean_witness_complex.py @@ -9,6 +9,7 @@ """ import gudhi +import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -40,15 +41,16 @@ def test_witness_complex(): assert landmarks[1] == euclidean_witness_complex.get_point(1) assert landmarks[2] == euclidean_witness_complex.get_point(2) - assert simplex_tree.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([0, 1], 0.0), - ([2], 0.0), - ([0, 2], 0.0), - ([1, 2], 0.0), - ([0, 1, 2], 0.0), - ] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([0, 1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([0, 2], 0.0)) + assert(next(filtration_generator) == ([1, 2], 0.0)) + assert(next(filtration_generator) == ([0, 1, 2], 0.0)) + with pytest.raises(StopIteration): + next(filtration_generator) def test_empty_euclidean_strong_witness_complex(): @@ -78,18 +80,24 @@ def test_strong_witness_complex(): assert landmarks[1] == euclidean_strong_witness_complex.get_point(1) assert landmarks[2] == euclidean_strong_witness_complex.get_point(2) - assert simplex_tree.get_filtration() == [([0], 0.0), ([1], 0.0), ([2], 0.0)] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + with pytest.raises(StopIteration): + next(filtration_generator) simplex_tree = euclidean_strong_witness_complex.create_simplex_tree( max_alpha_square=100.0 ) - assert simplex_tree.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([2], 0.0), - ([1, 2], 15.0), - ([0, 2], 34.0), - ([0, 1], 37.0), - ([0, 1, 2], 37.0), - ] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([1, 2], 15.0)) + assert(next(filtration_generator) == ([0, 2], 34.0)) + assert(next(filtration_generator) == ([0, 1], 37.0)) + assert(next(filtration_generator) == ([0, 1, 2], 37.0)) + with pytest.raises(StopIteration): + next(filtration_generator) diff --git a/src/python/test/test_rips_complex.py b/src/python/test/test_rips_complex.py index b02a68e1..bd31c47c 100755 --- a/src/python/test/test_rips_complex.py +++ b/src/python/test/test_rips_complex.py @@ -10,6 +10,7 @@ from gudhi import RipsComplex from math import sqrt +import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -32,18 +33,20 @@ def test_rips_from_points(): assert simplex_tree.num_simplices() == 10 assert simplex_tree.num_vertices() == 4 - assert simplex_tree.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([2], 0.0), - ([3], 0.0), - ([0, 1], 1.0), - ([0, 2], 1.0), - ([1, 3], 1.0), - ([2, 3], 1.0), - ([1, 2], 1.4142135623730951), - ([0, 3], 1.4142135623730951), - ] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([3], 0.0)) + assert(next(filtration_generator) == ([0, 1], 1.0)) + assert(next(filtration_generator) == ([0, 2], 1.0)) + assert(next(filtration_generator) == ([1, 3], 1.0)) + assert(next(filtration_generator) == ([2, 3], 1.0)) + assert(next(filtration_generator) == ([1, 2], 1.4142135623730951)) + assert(next(filtration_generator) == ([0, 3], 1.4142135623730951)) + with pytest.raises(StopIteration): + next(filtration_generator) + assert simplex_tree.get_star([0]) == [ ([0], 0.0), ([0, 1], 1.0), @@ -95,18 +98,20 @@ def test_rips_from_distance_matrix(): assert simplex_tree.num_simplices() == 10 assert simplex_tree.num_vertices() == 4 - assert simplex_tree.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([2], 0.0), - ([3], 0.0), - ([0, 1], 1.0), - ([0, 2], 1.0), - ([1, 3], 1.0), - ([2, 3], 1.0), - ([1, 2], 1.4142135623730951), - ([0, 3], 1.4142135623730951), - ] + filtration_generator = simplex_tree.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([3], 0.0)) + assert(next(filtration_generator) == ([0, 1], 1.0)) + assert(next(filtration_generator) == ([0, 2], 1.0)) + assert(next(filtration_generator) == ([1, 3], 1.0)) + assert(next(filtration_generator) == ([2, 3], 1.0)) + assert(next(filtration_generator) == ([1, 2], 1.4142135623730951)) + assert(next(filtration_generator) == ([0, 3], 1.4142135623730951)) + with pytest.raises(StopIteration): + next(filtration_generator) + assert simplex_tree.get_star([0]) == [ ([0], 0.0), ([0, 1], 1.0), diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 1822c43b..0f3db7ac 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -9,6 +9,7 @@ """ from gudhi import SimplexTree +import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -126,55 +127,58 @@ def test_expansion(): assert st.num_vertices() == 7 assert st.num_simplices() == 17 - assert st.get_filtration() == [ - ([2], 0.1), - ([3], 0.1), - ([2, 3], 0.1), - ([0], 0.2), - ([0, 2], 0.2), - ([1], 0.3), - ([0, 1], 0.3), - ([1, 3], 0.4), - ([1, 2], 0.5), - ([5], 0.6), - ([6], 0.6), - ([5, 6], 0.6), - ([4], 0.7), - ([2, 4], 0.7), - ([0, 3], 0.8), - ([4, 6], 0.9), - ([3, 6], 1.0), - ] + + filtration_generator = st.get_filtration() + assert(next(filtration_generator) == ([2], 0.1)) + assert(next(filtration_generator) == ([3], 0.1)) + assert(next(filtration_generator) == ([2, 3], 0.1)) + assert(next(filtration_generator) == ([0], 0.2)) + assert(next(filtration_generator) == ([0, 2], 0.2)) + assert(next(filtration_generator) == ([1], 0.3)) + assert(next(filtration_generator) == ([0, 1], 0.3)) + assert(next(filtration_generator) == ([1, 3], 0.4)) + assert(next(filtration_generator) == ([1, 2], 0.5)) + assert(next(filtration_generator) == ([5], 0.6)) + assert(next(filtration_generator) == ([6], 0.6)) + assert(next(filtration_generator) == ([5, 6], 0.6)) + assert(next(filtration_generator) == ([4], 0.7)) + assert(next(filtration_generator) == ([2, 4], 0.7)) + assert(next(filtration_generator) == ([0, 3], 0.8)) + assert(next(filtration_generator) == ([4, 6], 0.9)) + assert(next(filtration_generator) == ([3, 6], 1.0)) + with pytest.raises(StopIteration): + next(filtration_generator) st.expansion(3) assert st.num_vertices() == 7 assert st.num_simplices() == 22 st.initialize_filtration() - assert st.get_filtration() == [ - ([2], 0.1), - ([3], 0.1), - ([2, 3], 0.1), - ([0], 0.2), - ([0, 2], 0.2), - ([1], 0.3), - ([0, 1], 0.3), - ([1, 3], 0.4), - ([1, 2], 0.5), - ([0, 1, 2], 0.5), - ([1, 2, 3], 0.5), - ([5], 0.6), - ([6], 0.6), - ([5, 6], 0.6), - ([4], 0.7), - ([2, 4], 0.7), - ([0, 3], 0.8), - ([0, 1, 3], 0.8), - ([0, 2, 3], 0.8), - ([0, 1, 2, 3], 0.8), - ([4, 6], 0.9), - ([3, 6], 1.0), - ] + filtration_generator = st.get_filtration() + assert(next(filtration_generator) == ([2], 0.1)) + assert(next(filtration_generator) == ([3], 0.1)) + assert(next(filtration_generator) == ([2, 3], 0.1)) + assert(next(filtration_generator) == ([0], 0.2)) + assert(next(filtration_generator) == ([0, 2], 0.2)) + assert(next(filtration_generator) == ([1], 0.3)) + assert(next(filtration_generator) == ([0, 1], 0.3)) + assert(next(filtration_generator) == ([1, 3], 0.4)) + assert(next(filtration_generator) == ([1, 2], 0.5)) + assert(next(filtration_generator) == ([0, 1, 2], 0.5)) + assert(next(filtration_generator) == ([1, 2, 3], 0.5)) + assert(next(filtration_generator) == ([5], 0.6)) + assert(next(filtration_generator) == ([6], 0.6)) + assert(next(filtration_generator) == ([5, 6], 0.6)) + assert(next(filtration_generator) == ([4], 0.7)) + assert(next(filtration_generator) == ([2, 4], 0.7)) + assert(next(filtration_generator) == ([0, 3], 0.8)) + assert(next(filtration_generator) == ([0, 1, 3], 0.8)) + assert(next(filtration_generator) == ([0, 2, 3], 0.8)) + assert(next(filtration_generator) == ([0, 1, 2, 3], 0.8)) + assert(next(filtration_generator) == ([4, 6], 0.9)) + assert(next(filtration_generator) == ([3, 6], 1.0)) + with pytest.raises(StopIteration): + next(filtration_generator) def test_automatic_dimension(): diff --git a/src/python/test/test_tangential_complex.py b/src/python/test/test_tangential_complex.py index e650e99c..90e2c75b 100755 --- a/src/python/test/test_tangential_complex.py +++ b/src/python/test/test_tangential_complex.py @@ -9,6 +9,7 @@ """ from gudhi import TangentialComplex, SimplexTree +import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -37,14 +38,16 @@ def test_tangential(): assert st.num_simplices() == 6 assert st.num_vertices() == 4 - assert st.get_filtration() == [ - ([0], 0.0), - ([1], 0.0), - ([2], 0.0), - ([0, 2], 0.0), - ([3], 0.0), - ([1, 3], 0.0), - ] + filtration_generator = st.get_filtration() + assert(next(filtration_generator) == ([0], 0.0)) + assert(next(filtration_generator) == ([1], 0.0)) + assert(next(filtration_generator) == ([2], 0.0)) + assert(next(filtration_generator) == ([0, 2], 0.0)) + assert(next(filtration_generator) == ([3], 0.0)) + assert(next(filtration_generator) == ([1, 3], 0.0)) + with pytest.raises(StopIteration): + next(filtration_generator) + assert st.get_cofaces([0], 1) == [([0, 2], 0.0)] assert point_list[0] == tc.get_point(0) -- cgit v1.2.3 From 68b6e3f3d641cd4a1e86f08bff96e417cc17ac59 Mon Sep 17 00:00:00 2001 From: takeshimeonerespect Date: Fri, 31 Jan 2020 08:08:43 +0100 Subject: timedelay added on fork --- src/python/CMakeLists.txt | 5 +++ src/python/doc/point_cloud.rst | 7 ++++ src/python/gudhi/point_cloud/timedelay.py | 56 +++++++++++++++++++++++++++++++ src/python/test/test_point_cloud.py | 35 +++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/python/gudhi/point_cloud/timedelay.py create mode 100755 src/python/test/test_point_cloud.py diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index b558d4c4..b23ec8a9 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -52,6 +52,7 @@ if(PYTHONINTERP_FOUND) # Modules that should not be auto-imported in __init__.py set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'representations', ") set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'wasserstein', ") + set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'point_cloud', ") add_gudhi_debug_info("Python version ${PYTHON_VERSION_STRING}") add_gudhi_debug_info("Cython version ${CYTHON_VERSION}") @@ -221,6 +222,7 @@ endif(CGAL_FOUND) file(COPY "gudhi/persistence_graphical_tools.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") file(COPY "gudhi/representations" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi/") file(COPY "gudhi/wasserstein.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") + file(COPY "gudhi/point_cloud" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") add_custom_command( OUTPUT gudhi.so @@ -399,6 +401,9 @@ endif(CGAL_FOUND) add_gudhi_py_test(test_representations) endif() + # Point cloud + add_gudhi_py_test(test_point_cloud) + # Documentation generation is available through sphinx - requires all modules if(SPHINX_PATH) if(MATPLOTLIB_FOUND) diff --git a/src/python/doc/point_cloud.rst b/src/python/doc/point_cloud.rst index d668428a..55c74ff3 100644 --- a/src/python/doc/point_cloud.rst +++ b/src/python/doc/point_cloud.rst @@ -20,3 +20,10 @@ Subsampling :members: :special-members: :show-inheritance: + +TimeDelayEmbedding +------------------ + +.. autoclass:: gudhi.point_cloud.timedelay.TimeDelayEmbedding + :members: + diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py new file mode 100644 index 00000000..5c7ba542 --- /dev/null +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -0,0 +1,56 @@ +import numpy as np + +class TimeDelayEmbedding: + """Point cloud transformation class. + + Embeds time-series data in the R^d according to Takens' Embedding Theorem + and obtains the coordinates of each point. + + Parameters + ---------- + dim : int, optional (default=3) + `d` of R^d to be embedded. + + delay : int, optional (default=1) + Time-Delay embedding. + + skip : int, optional (default=1) + How often to skip embedded points. + + """ + def __init__(self, dim=3, delay=1, skip=1): + self._dim = dim + self._delay = delay + self._skip = skip + + def __call__(self, *args, **kwargs): + return self.transform(*args, **kwargs) + + def _transform(self, ts): + """Guts of transform method.""" + return ts[ + np.add.outer( + np.arange(0, len(ts)-self._delay*(self._dim-1), self._skip), + np.arange(0, self._dim*self._delay, self._delay)) + ] + + def transform(self, ts): + """Transform method. + + Parameters + ---------- + ts : list[float] or list[list[float]] + A single or multiple time-series data. + + Returns + ------- + point clouds : list[list[float, float, float]] or list[list[list[float, float, float]]] + Makes point cloud every a single time-series data. + """ + ndts = np.array(ts) + if ndts.ndim == 1: + # for single. + return self._transform(ndts).tolist() + else: + # for multiple. + return np.apply_along_axis(self._transform, 1, ndts).tolist() diff --git a/src/python/test/test_point_cloud.py b/src/python/test/test_point_cloud.py new file mode 100755 index 00000000..2ee0c1fb --- /dev/null +++ b/src/python/test/test_point_cloud.py @@ -0,0 +1,35 @@ +from gudhi.point_cloud.timedelay import TimeDelayEmbedding + +def test_normal(): + # Sample array + ts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # Normal case. + prep = TimeDelayEmbedding() + attractor = prep(ts) + assert (attractor[0] == [1, 2, 3]) + assert (attractor[1] == [2, 3, 4]) + assert (attractor[2] == [3, 4, 5]) + assert (attractor[3] == [4, 5, 6]) + assert (attractor[4] == [5, 6, 7]) + assert (attractor[5] == [6, 7, 8]) + assert (attractor[6] == [7, 8, 9]) + assert (attractor[7] == [8, 9, 10]) + # Delay = 3 + prep = TimeDelayEmbedding(delay=3) + attractor = prep(ts) + assert (attractor[0] == [1, 4, 7]) + assert (attractor[1] == [2, 5, 8]) + assert (attractor[2] == [3, 6, 9]) + assert (attractor[3] == [4, 7, 10]) + # Skip = 3 + prep = TimeDelayEmbedding(skip=3) + attractor = prep(ts) + assert (attractor[0] == [1, 2, 3]) + assert (attractor[1] == [4, 5, 6]) + assert (attractor[2] == [7, 8, 9]) + # Delay = 2 / Skip = 2 + prep = TimeDelayEmbedding(delay=2, skip=2) + attractor = prep(ts) + assert (attractor[0] == [1, 3, 5]) + assert (attractor[1] == [3, 5, 7]) + assert (attractor[2] == [5, 7, 9]) -- cgit v1.2.3 From d6afaa8300daa6204282a7d34df6bea33ea59fd2 Mon Sep 17 00:00:00 2001 From: takeshimeonerespect <58589594+takeshimeonerespect@users.noreply.github.com> Date: Mon, 3 Feb 2020 14:13:52 +0900 Subject: Update timedelay.py --- src/python/gudhi/point_cloud/timedelay.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py index 5c7ba542..f283916d 100644 --- a/src/python/gudhi/point_cloud/timedelay.py +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -1,3 +1,11 @@ +# 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): Martin Royer, Yuichi Ike, Masatoshi Takenouchi +# +# Copyright (C) 2020 Inria, Copyright (C) 2020 Fujitsu Laboratories Ltd. +# Modification(s): +# - YYYY/MM Author: Description of the modification + import numpy as np class TimeDelayEmbedding: -- cgit v1.2.3 From eded147ffffe5b7143cad19ecd134fb7a63991a3 Mon Sep 17 00:00:00 2001 From: takenouchi Date: Tue, 4 Feb 2020 14:08:19 +0900 Subject: change a file name --- src/python/test/test_time_delay.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 src/python/test/test_time_delay.py diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py new file mode 100755 index 00000000..2ee0c1fb --- /dev/null +++ b/src/python/test/test_time_delay.py @@ -0,0 +1,35 @@ +from gudhi.point_cloud.timedelay import TimeDelayEmbedding + +def test_normal(): + # Sample array + ts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # Normal case. + prep = TimeDelayEmbedding() + attractor = prep(ts) + assert (attractor[0] == [1, 2, 3]) + assert (attractor[1] == [2, 3, 4]) + assert (attractor[2] == [3, 4, 5]) + assert (attractor[3] == [4, 5, 6]) + assert (attractor[4] == [5, 6, 7]) + assert (attractor[5] == [6, 7, 8]) + assert (attractor[6] == [7, 8, 9]) + assert (attractor[7] == [8, 9, 10]) + # Delay = 3 + prep = TimeDelayEmbedding(delay=3) + attractor = prep(ts) + assert (attractor[0] == [1, 4, 7]) + assert (attractor[1] == [2, 5, 8]) + assert (attractor[2] == [3, 6, 9]) + assert (attractor[3] == [4, 7, 10]) + # Skip = 3 + prep = TimeDelayEmbedding(skip=3) + attractor = prep(ts) + assert (attractor[0] == [1, 2, 3]) + assert (attractor[1] == [4, 5, 6]) + assert (attractor[2] == [7, 8, 9]) + # Delay = 2 / Skip = 2 + prep = TimeDelayEmbedding(delay=2, skip=2) + attractor = prep(ts) + assert (attractor[0] == [1, 3, 5]) + assert (attractor[1] == [3, 5, 7]) + assert (attractor[2] == [5, 7, 9]) -- cgit v1.2.3 From 5ddb724824798fe194a66285e29ea4c5cc2713e2 Mon Sep 17 00:00:00 2001 From: takeshimeonerespect Date: Tue, 4 Feb 2020 14:24:27 +0900 Subject: Delete test_point_cloud.py --- src/python/test/test_point_cloud.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100755 src/python/test/test_point_cloud.py diff --git a/src/python/test/test_point_cloud.py b/src/python/test/test_point_cloud.py deleted file mode 100755 index 2ee0c1fb..00000000 --- a/src/python/test/test_point_cloud.py +++ /dev/null @@ -1,35 +0,0 @@ -from gudhi.point_cloud.timedelay import TimeDelayEmbedding - -def test_normal(): - # Sample array - ts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - # Normal case. - prep = TimeDelayEmbedding() - attractor = prep(ts) - assert (attractor[0] == [1, 2, 3]) - assert (attractor[1] == [2, 3, 4]) - assert (attractor[2] == [3, 4, 5]) - assert (attractor[3] == [4, 5, 6]) - assert (attractor[4] == [5, 6, 7]) - assert (attractor[5] == [6, 7, 8]) - assert (attractor[6] == [7, 8, 9]) - assert (attractor[7] == [8, 9, 10]) - # Delay = 3 - prep = TimeDelayEmbedding(delay=3) - attractor = prep(ts) - assert (attractor[0] == [1, 4, 7]) - assert (attractor[1] == [2, 5, 8]) - assert (attractor[2] == [3, 6, 9]) - assert (attractor[3] == [4, 7, 10]) - # Skip = 3 - prep = TimeDelayEmbedding(skip=3) - attractor = prep(ts) - assert (attractor[0] == [1, 2, 3]) - assert (attractor[1] == [4, 5, 6]) - assert (attractor[2] == [7, 8, 9]) - # Delay = 2 / Skip = 2 - prep = TimeDelayEmbedding(delay=2, skip=2) - attractor = prep(ts) - assert (attractor[0] == [1, 3, 5]) - assert (attractor[1] == [3, 5, 7]) - assert (attractor[2] == [5, 7, 9]) -- cgit v1.2.3 From 596355344e6205d02110e38a0cb7e0a94e8dbd27 Mon Sep 17 00:00:00 2001 From: takenouchi Date: Thu, 6 Feb 2020 16:00:47 +0900 Subject: modify CMakeLists.txt --- src/python/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index b23ec8a9..798e2907 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -401,8 +401,8 @@ endif(CGAL_FOUND) add_gudhi_py_test(test_representations) endif() - # Point cloud - add_gudhi_py_test(test_point_cloud) + # Time Delay + add_gudhi_py_test(test_time_delay) # Documentation generation is available through sphinx - requires all modules if(SPHINX_PATH) -- cgit v1.2.3 From 3253abd27129595f7fcd2be4c2285a93aea98690 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Tue, 11 Feb 2020 17:05:08 +0100 Subject: Update src/python/gudhi/simplex_tree.pyx Co-Authored-By: Marc Glisse --- src/python/gudhi/simplex_tree.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 22978b6e..308b3d2d 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -219,7 +219,7 @@ cdef class SimplexTree: cdef vector[Simplex_tree_simplex_handle].const_iterator end = self.get_ptr().get_filtration_iterator_end() while it != end: - yield(self.get_ptr().get_simplex_and_filtration(dereference(it))) + yield self.get_ptr().get_simplex_and_filtration(dereference(it)) preincrement(it) def get_skeleton(self, dimension): -- cgit v1.2.3 From 3ea44646f04648d1a456a0fb9526035101fc17ea Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 11 Feb 2020 17:20:24 +0100 Subject: Code review: non-optimal way to test filtration generator --- src/python/test/test_alpha_complex.py | 49 ++++++------- src/python/test/test_euclidean_witness_complex.py | 45 +++++------- src/python/test/test_rips_complex.py | 50 +++++++------ src/python/test/test_simplex_tree.py | 88 +++++++++++------------ src/python/test/test_tangential_complex.py | 17 +++-- 5 files changed, 117 insertions(+), 132 deletions(-) diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index ceead919..77121302 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -40,20 +40,19 @@ def test_infinite_alpha(): assert simplex_tree.num_simplices() == 11 assert simplex_tree.num_vertices() == 4 - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([3], 0.0)) - assert(next(filtration_generator) == ([0, 1], 0.25)) - assert(next(filtration_generator) == ([0, 2], 0.25)) - assert(next(filtration_generator) == ([1, 3], 0.25)) - assert(next(filtration_generator) == ([2, 3], 0.25)) - assert(next(filtration_generator) == ([1, 2], 0.5)) - assert(next(filtration_generator) == ([0, 1, 2], 0.5)) - assert(next(filtration_generator) == ([1, 2, 3], 0.5)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(simplex_tree.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([2], 0.0), + ([3], 0.0), + ([0, 1], 0.25), + ([0, 2], 0.25), + ([1, 3], 0.25), + ([2, 3], 0.25), + ([1, 2], 0.5), + ([0, 1, 2], 0.5), + ([1, 2, 3], 0.5), + ] assert simplex_tree.get_star([0]) == [ ([0], 0.0), @@ -107,18 +106,16 @@ def test_filtered_alpha(): else: assert False - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([3], 0.0)) - assert(next(filtration_generator) == ([0, 1], 0.25)) - assert(next(filtration_generator) == ([0, 2], 0.25)) - assert(next(filtration_generator) == ([1, 3], 0.25)) - assert(next(filtration_generator) == ([2, 3], 0.25)) - with pytest.raises(StopIteration): - next(filtration_generator) - + assert list(simplex_tree.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([2], 0.0), + ([3], 0.0), + ([0, 1], 0.25), + ([0, 2], 0.25), + ([1, 3], 0.25), + ([2, 3], 0.25), + ] assert simplex_tree.get_star([0]) == [([0], 0.0), ([0, 1], 0.25), ([0, 2], 0.25)] assert simplex_tree.get_cofaces([0], 1) == [([0, 1], 0.25), ([0, 2], 0.25)] diff --git a/src/python/test/test_euclidean_witness_complex.py b/src/python/test/test_euclidean_witness_complex.py index 16ff1ef4..47196a2a 100755 --- a/src/python/test/test_euclidean_witness_complex.py +++ b/src/python/test/test_euclidean_witness_complex.py @@ -41,16 +41,15 @@ def test_witness_complex(): assert landmarks[1] == euclidean_witness_complex.get_point(1) assert landmarks[2] == euclidean_witness_complex.get_point(2) - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([0, 1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([0, 2], 0.0)) - assert(next(filtration_generator) == ([1, 2], 0.0)) - assert(next(filtration_generator) == ([0, 1, 2], 0.0)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(simplex_tree.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([0, 1], 0.0), + ([2], 0.0), + ([0, 2], 0.0), + ([1, 2], 0.0), + ([0, 1, 2], 0.0), + ] def test_empty_euclidean_strong_witness_complex(): @@ -80,24 +79,18 @@ def test_strong_witness_complex(): assert landmarks[1] == euclidean_strong_witness_complex.get_point(1) assert landmarks[2] == euclidean_strong_witness_complex.get_point(2) - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(simplex_tree.get_filtration()) == [([0], 0.0), ([1], 0.0), ([2], 0.0)] simplex_tree = euclidean_strong_witness_complex.create_simplex_tree( max_alpha_square=100.0 ) - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([1, 2], 15.0)) - assert(next(filtration_generator) == ([0, 2], 34.0)) - assert(next(filtration_generator) == ([0, 1], 37.0)) - assert(next(filtration_generator) == ([0, 1, 2], 37.0)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(simplex_tree.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([2], 0.0), + ([1, 2], 15.0), + ([0, 2], 34.0), + ([0, 1], 37.0), + ([0, 1, 2], 37.0), + ] diff --git a/src/python/test/test_rips_complex.py b/src/python/test/test_rips_complex.py index bd31c47c..f5c086cb 100755 --- a/src/python/test/test_rips_complex.py +++ b/src/python/test/test_rips_complex.py @@ -33,19 +33,18 @@ def test_rips_from_points(): assert simplex_tree.num_simplices() == 10 assert simplex_tree.num_vertices() == 4 - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([3], 0.0)) - assert(next(filtration_generator) == ([0, 1], 1.0)) - assert(next(filtration_generator) == ([0, 2], 1.0)) - assert(next(filtration_generator) == ([1, 3], 1.0)) - assert(next(filtration_generator) == ([2, 3], 1.0)) - assert(next(filtration_generator) == ([1, 2], 1.4142135623730951)) - assert(next(filtration_generator) == ([0, 3], 1.4142135623730951)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(simplex_tree.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([2], 0.0), + ([3], 0.0), + ([0, 1], 1.0), + ([0, 2], 1.0), + ([1, 3], 1.0), + ([2, 3], 1.0), + ([1, 2], 1.4142135623730951), + ([0, 3], 1.4142135623730951), + ] assert simplex_tree.get_star([0]) == [ ([0], 0.0), @@ -98,19 +97,18 @@ def test_rips_from_distance_matrix(): assert simplex_tree.num_simplices() == 10 assert simplex_tree.num_vertices() == 4 - filtration_generator = simplex_tree.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([3], 0.0)) - assert(next(filtration_generator) == ([0, 1], 1.0)) - assert(next(filtration_generator) == ([0, 2], 1.0)) - assert(next(filtration_generator) == ([1, 3], 1.0)) - assert(next(filtration_generator) == ([2, 3], 1.0)) - assert(next(filtration_generator) == ([1, 2], 1.4142135623730951)) - assert(next(filtration_generator) == ([0, 3], 1.4142135623730951)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(simplex_tree.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([2], 0.0), + ([3], 0.0), + ([0, 1], 1.0), + ([0, 2], 1.0), + ([1, 3], 1.0), + ([2, 3], 1.0), + ([1, 2], 1.4142135623730951), + ([0, 3], 1.4142135623730951), + ] assert simplex_tree.get_star([0]) == [ ([0], 0.0), diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 0f3db7ac..fa42f2ac 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -128,57 +128,55 @@ def test_expansion(): assert st.num_vertices() == 7 assert st.num_simplices() == 17 - filtration_generator = st.get_filtration() - assert(next(filtration_generator) == ([2], 0.1)) - assert(next(filtration_generator) == ([3], 0.1)) - assert(next(filtration_generator) == ([2, 3], 0.1)) - assert(next(filtration_generator) == ([0], 0.2)) - assert(next(filtration_generator) == ([0, 2], 0.2)) - assert(next(filtration_generator) == ([1], 0.3)) - assert(next(filtration_generator) == ([0, 1], 0.3)) - assert(next(filtration_generator) == ([1, 3], 0.4)) - assert(next(filtration_generator) == ([1, 2], 0.5)) - assert(next(filtration_generator) == ([5], 0.6)) - assert(next(filtration_generator) == ([6], 0.6)) - assert(next(filtration_generator) == ([5, 6], 0.6)) - assert(next(filtration_generator) == ([4], 0.7)) - assert(next(filtration_generator) == ([2, 4], 0.7)) - assert(next(filtration_generator) == ([0, 3], 0.8)) - assert(next(filtration_generator) == ([4, 6], 0.9)) - assert(next(filtration_generator) == ([3, 6], 1.0)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(st.get_filtration()) == [ + ([2], 0.1), + ([3], 0.1), + ([2, 3], 0.1), + ([0], 0.2), + ([0, 2], 0.2), + ([1], 0.3), + ([0, 1], 0.3), + ([1, 3], 0.4), + ([1, 2], 0.5), + ([5], 0.6), + ([6], 0.6), + ([5, 6], 0.6), + ([4], 0.7), + ([2, 4], 0.7), + ([0, 3], 0.8), + ([4, 6], 0.9), + ([3, 6], 1.0), + ] st.expansion(3) assert st.num_vertices() == 7 assert st.num_simplices() == 22 st.initialize_filtration() - filtration_generator = st.get_filtration() - assert(next(filtration_generator) == ([2], 0.1)) - assert(next(filtration_generator) == ([3], 0.1)) - assert(next(filtration_generator) == ([2, 3], 0.1)) - assert(next(filtration_generator) == ([0], 0.2)) - assert(next(filtration_generator) == ([0, 2], 0.2)) - assert(next(filtration_generator) == ([1], 0.3)) - assert(next(filtration_generator) == ([0, 1], 0.3)) - assert(next(filtration_generator) == ([1, 3], 0.4)) - assert(next(filtration_generator) == ([1, 2], 0.5)) - assert(next(filtration_generator) == ([0, 1, 2], 0.5)) - assert(next(filtration_generator) == ([1, 2, 3], 0.5)) - assert(next(filtration_generator) == ([5], 0.6)) - assert(next(filtration_generator) == ([6], 0.6)) - assert(next(filtration_generator) == ([5, 6], 0.6)) - assert(next(filtration_generator) == ([4], 0.7)) - assert(next(filtration_generator) == ([2, 4], 0.7)) - assert(next(filtration_generator) == ([0, 3], 0.8)) - assert(next(filtration_generator) == ([0, 1, 3], 0.8)) - assert(next(filtration_generator) == ([0, 2, 3], 0.8)) - assert(next(filtration_generator) == ([0, 1, 2, 3], 0.8)) - assert(next(filtration_generator) == ([4, 6], 0.9)) - assert(next(filtration_generator) == ([3, 6], 1.0)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(st.get_filtration()) == [ + ([2], 0.1), + ([3], 0.1), + ([2, 3], 0.1), + ([0], 0.2), + ([0, 2], 0.2), + ([1], 0.3), + ([0, 1], 0.3), + ([1, 3], 0.4), + ([1, 2], 0.5), + ([0, 1, 2], 0.5), + ([1, 2, 3], 0.5), + ([5], 0.6), + ([6], 0.6), + ([5, 6], 0.6), + ([4], 0.7), + ([2, 4], 0.7), + ([0, 3], 0.8), + ([0, 1, 3], 0.8), + ([0, 2, 3], 0.8), + ([0, 1, 2, 3], 0.8), + ([4, 6], 0.9), + ([3, 6], 1.0), + ] def test_automatic_dimension(): diff --git a/src/python/test/test_tangential_complex.py b/src/python/test/test_tangential_complex.py index 90e2c75b..fc500c45 100755 --- a/src/python/test/test_tangential_complex.py +++ b/src/python/test/test_tangential_complex.py @@ -38,15 +38,14 @@ def test_tangential(): assert st.num_simplices() == 6 assert st.num_vertices() == 4 - filtration_generator = st.get_filtration() - assert(next(filtration_generator) == ([0], 0.0)) - assert(next(filtration_generator) == ([1], 0.0)) - assert(next(filtration_generator) == ([2], 0.0)) - assert(next(filtration_generator) == ([0, 2], 0.0)) - assert(next(filtration_generator) == ([3], 0.0)) - assert(next(filtration_generator) == ([1, 3], 0.0)) - with pytest.raises(StopIteration): - next(filtration_generator) + assert list(st.get_filtration()) == [ + ([0], 0.0), + ([1], 0.0), + ([2], 0.0), + ([0, 2], 0.0), + ([3], 0.0), + ([1, 3], 0.0), + ] assert st.get_cofaces([0], 1) == [([0, 2], 0.0)] -- cgit v1.2.3 From 79de1437cb2fa0ab69465a2f2feabe09a12056eb Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Tue, 11 Feb 2020 17:51:40 +0100 Subject: Update src/python/include/Simplex_tree_interface.h Co-Authored-By: Marc Glisse --- src/python/include/Simplex_tree_interface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index c0bbc3d9..878919cc 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -88,7 +88,7 @@ class Simplex_tree_interface : public Simplex_tree { for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex.insert(simplex.begin(), vertex); } - return std::make_pair(simplex, Base::filtration(f_simplex)); + return std::make_pair(std::move(simplex), Base::filtration(f_simplex)); } Filtered_simplices get_skeleton(int dimension) { -- cgit v1.2.3 From 1edb818b38ace05b230319227e60838b796ddfc5 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 13 Feb 2020 11:08:44 +0100 Subject: simplex tree skeleton iterator --- src/python/gudhi/simplex_tree.pxd | 10 +++++++++- src/python/gudhi/simplex_tree.pyx | 15 ++++++--------- src/python/include/Simplex_tree_interface.h | 23 ++++++++++------------- src/python/test/test_simplex_tree.py | 8 ++++---- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 1b0dc881..66c173a6 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -24,6 +24,13 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_simplex_handle "Gudhi::Simplex_tree_interface::Simplex_handle": pass + cdef cppclass Simplex_tree_skeleton_iterator "Gudhi::Simplex_tree_interface::Skeleton_simplex_iterator": + Simplex_tree_skeleton_iterator() + Simplex_tree_simplex_handle& operator*() + Simplex_tree_skeleton_iterator operator++() + bint operator!=(Simplex_tree_skeleton_iterator) + + cdef cppclass Simplex_tree_interface_full_featured "Gudhi::Simplex_tree_interface": Simplex_tree() double simplex_filtration(vector[int] simplex) @@ -37,7 +44,6 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": bool find_simplex(vector[int] simplex) bool insert_simplex_and_subfaces(vector[int] simplex, double filtration) - vector[pair[vector[int], double]] get_skeleton(int dimension) vector[pair[vector[int], double]] get_star(vector[int] simplex) vector[pair[vector[int], double]] get_cofaces(vector[int] simplex, int dimension) @@ -49,6 +55,8 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": pair[vector[int], double] get_simplex_and_filtration(Simplex_tree_simplex_handle f_simplex) vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_begin() vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_end() + Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) + Simplex_tree_skeleton_iterator get_skeleton_iterator_end(int dimension) 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 308b3d2d..efac2d80 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -231,15 +231,12 @@ cdef class SimplexTree: :returns: The (simplices of the) skeleton of a maximum dimension. :rtype: list of tuples(simplex, filtration) """ - cdef vector[pair[vector[int], double]] skeleton \ - = self.get_ptr().get_skeleton(dimension) - ct = [] - for filtered_simplex in skeleton: - v = [] - for vertex in filtered_simplex.first: - v.append(vertex) - ct.append((v, filtered_simplex.second)) - return ct + cdef Simplex_tree_skeleton_iterator it = self.get_ptr().get_skeleton_iterator_begin(dimension) + cdef Simplex_tree_skeleton_iterator end = self.get_ptr().get_skeleton_iterator_end(dimension) + + while it != end: + yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + preincrement(it) def get_star(self, simplex): """This function returns the star of a given N-simplex. diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 878919cc..55d5af97 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -35,6 +35,7 @@ class Simplex_tree_interface : public Simplex_tree { using Simplex = std::vector; using Simplex_and_filtration = std::pair; using Filtered_simplices = std::vector; + using Skeleton_simplex_iterator = typename Base::Skeleton_simplex_iterator; public: bool find_simplex(const Simplex& vh) { @@ -91,18 +92,6 @@ class Simplex_tree_interface : public Simplex_tree { return std::make_pair(std::move(simplex), Base::filtration(f_simplex)); } - Filtered_simplices get_skeleton(int dimension) { - Filtered_simplices skeletons; - for (auto f_simplex : Base::skeleton_simplex_range(dimension)) { - Simplex simplex; - for (auto vertex : Base::simplex_vertex_range(f_simplex)) { - simplex.insert(simplex.begin(), vertex); - } - skeletons.push_back(std::make_pair(simplex, Base::filtration(f_simplex))); - } - return skeletons; - } - Filtered_simplices get_star(const Simplex& simplex) { Filtered_simplices star; for (auto f_simplex : Base::star_simplex_range(Base::find(simplex))) { @@ -134,13 +123,21 @@ class Simplex_tree_interface : public Simplex_tree { // Iterator over the simplex tree typename std::vector::const_iterator get_filtration_iterator_begin() { - Base::initialize_filtration(); + // Base::initialize_filtration(); already performed in filtration_simplex_range return Base::filtration_simplex_range().begin(); } typename std::vector::const_iterator get_filtration_iterator_end() { return Base::filtration_simplex_range().end(); } + + Skeleton_simplex_iterator get_skeleton_iterator_begin(int dimension) { + return Base::skeleton_simplex_range(dimension).begin(); + } + + Skeleton_simplex_iterator get_skeleton_iterator_end(int dimension) { + return Base::skeleton_simplex_range(dimension).end(); + } }; } // namespace Gudhi diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index fa42f2ac..eca3807b 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -56,7 +56,7 @@ def test_insertion(): assert st.filtration([1]) == 0.0 # skeleton test - assert st.get_skeleton(2) == [ + assert list(st.get_skeleton(2)) == [ ([0, 1, 2], 4.0), ([0, 1], 0.0), ([0, 2], 4.0), @@ -65,7 +65,7 @@ def test_insertion(): ([1], 0.0), ([2], 4.0), ] - assert st.get_skeleton(1) == [ + assert list(st.get_skeleton(1)) == [ ([0, 1], 0.0), ([0, 2], 4.0), ([0], 0.0), @@ -73,12 +73,12 @@ def test_insertion(): ([1], 0.0), ([2], 4.0), ] - assert st.get_skeleton(0) == [([0], 0.0), ([1], 0.0), ([2], 4.0)] + assert list(st.get_skeleton(0)) == [([0], 0.0), ([1], 0.0), ([2], 4.0)] # remove_maximal_simplex test assert st.get_cofaces([0, 1, 2], 1) == [] st.remove_maximal_simplex([0, 1, 2]) - assert st.get_skeleton(2) == [ + assert list(st.get_skeleton(2)) == [ ([0, 1], 0.0), ([0, 2], 4.0), ([0], 0.0), -- cgit v1.2.3 From a6a4f375822cf3e2ca1866d78472e4350140ddbc Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 11:02:56 +0900 Subject: Add __init__.py --- src/python/gudhi/point_cloud/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/python/gudhi/point_cloud/__init__.py diff --git a/src/python/gudhi/point_cloud/__init__.py b/src/python/gudhi/point_cloud/__init__.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 9cc9e1cf3cd9ea42908324d410ef68fa12e8e832 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 11:08:50 +0900 Subject: Update timedelay.py --- src/python/gudhi/point_cloud/timedelay.py | 66 ++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py index f283916d..d899da67 100644 --- a/src/python/gudhi/point_cloud/timedelay.py +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -10,30 +10,55 @@ import numpy as np class TimeDelayEmbedding: """Point cloud transformation class. - Embeds time-series data in the R^d according to Takens' Embedding Theorem and obtains the coordinates of each point. - Parameters ---------- dim : int, optional (default=3) `d` of R^d to be embedded. - delay : int, optional (default=1) Time-Delay embedding. - skip : int, optional (default=1) How often to skip embedded points. - + Given delay=3 and skip=2, an point cloud which is obtained by embedding + a single time-series data into R^3 is as follows. + + .. code-block:: none + + time-series = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + point clouds = [[1, 4, 7], + [3, 6, 9]] + """ def __init__(self, dim=3, delay=1, skip=1): self._dim = dim self._delay = delay self._skip = skip - def __call__(self, *args, **kwargs): - return self.transform(*args, **kwargs) + def __call__(self, ts): + """Transform method for single time-series data. + Parameters + ---------- + ts : list[float] + A single time-series data. + Returns + ------- + point clouds : list[list[float, float, float]] + Makes point cloud every a single time-series data. + Raises + ------- + TypeError + If the parameter's type does not match the desired type. + """ + ndts = np.array(ts) + if ndts.ndim == 1: + return self._transform(ndts) + else: + raise TypeError("Expects 1-dimensional array.") + def fit(self, ts, y=None): + return self + def _transform(self, ts): """Guts of transform method.""" return ts[ @@ -43,22 +68,27 @@ class TimeDelayEmbedding: ] def transform(self, ts): - """Transform method. - + """Transform method for multiple time-series data. Parameters ---------- - ts : list[float] or list[list[float]] - A single or multiple time-series data. - + ts : list[list[float]] + Multiple time-series data. + Attributes + ---------- + ndts : + The ndts means that all time series need to have exactly + the same size. Returns ------- - point clouds : list[list[float, float, float]] or list[list[list[float, float, float]]] + point clouds : list[list[list[float, float, float]]] Makes point cloud every a single time-series data. + Raises + ------- + TypeError + If the parameter's type does not match the desired type. """ ndts = np.array(ts) - if ndts.ndim == 1: - # for single. - return self._transform(ndts).tolist() + if ndts.ndim == 2: + return np.apply_along_axis(self._transform, 1, ndts) else: - # for multiple. - return np.apply_along_axis(self._transform, 1, ndts).tolist() + raise TypeError("Expects 2-dimensional array.") -- cgit v1.2.3 From 2253fd03bb49aea455309f6d633a6edeb2362d79 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 17:52:07 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index 2ee0c1fb..d2ffbf40 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -6,30 +6,30 @@ def test_normal(): # Normal case. prep = TimeDelayEmbedding() attractor = prep(ts) - assert (attractor[0] == [1, 2, 3]) - assert (attractor[1] == [2, 3, 4]) - assert (attractor[2] == [3, 4, 5]) - assert (attractor[3] == [4, 5, 6]) - assert (attractor[4] == [5, 6, 7]) - assert (attractor[5] == [6, 7, 8]) - assert (attractor[6] == [7, 8, 9]) - assert (attractor[7] == [8, 9, 10]) + assert (attractor[0] == np.array[1, 2, 3]) + assert (attractor[1] == np.array[2, 3, 4]) + assert (attractor[2] == np.array[3, 4, 5]) + assert (attractor[3] == np.array[4, 5, 6]) + assert (attractor[4] == np.array[5, 6, 7]) + assert (attractor[5] == np.array[6, 7, 8]) + assert (attractor[6] == np.array[7, 8, 9]) + assert (attractor[7] == np.array[8, 9, 10]) # Delay = 3 prep = TimeDelayEmbedding(delay=3) attractor = prep(ts) - assert (attractor[0] == [1, 4, 7]) - assert (attractor[1] == [2, 5, 8]) - assert (attractor[2] == [3, 6, 9]) - assert (attractor[3] == [4, 7, 10]) + assert (attractor[0] == np.array[1, 4, 7]) + assert (attractor[1] == np.array[2, 5, 8]) + assert (attractor[2] == np.array[3, 6, 9]) + assert (attractor[3] == np.array[4, 7, 10]) # Skip = 3 prep = TimeDelayEmbedding(skip=3) attractor = prep(ts) - assert (attractor[0] == [1, 2, 3]) - assert (attractor[1] == [4, 5, 6]) - assert (attractor[2] == [7, 8, 9]) + assert (attractor[0] == np.array[1, 2, 3]) + assert (attractor[1] == np.array[4, 5, 6]) + assert (attractor[2] == np.array[7, 8, 9]) # Delay = 2 / Skip = 2 prep = TimeDelayEmbedding(delay=2, skip=2) attractor = prep(ts) - assert (attractor[0] == [1, 3, 5]) - assert (attractor[1] == [3, 5, 7]) - assert (attractor[2] == [5, 7, 9]) + assert (attractor[0] == np.array[1, 3, 5]) + assert (attractor[1] == np.array[3, 5, 7]) + assert (attractor[2] == np.array[5, 7, 9]) -- cgit v1.2.3 From f58a4120b70487aede3cb4e81fbb15171e34fa37 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 18:24:18 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index d2ffbf40..1cdf56f9 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -6,30 +6,30 @@ def test_normal(): # Normal case. prep = TimeDelayEmbedding() attractor = prep(ts) - assert (attractor[0] == np.array[1, 2, 3]) - assert (attractor[1] == np.array[2, 3, 4]) - assert (attractor[2] == np.array[3, 4, 5]) - assert (attractor[3] == np.array[4, 5, 6]) - assert (attractor[4] == np.array[5, 6, 7]) - assert (attractor[5] == np.array[6, 7, 8]) - assert (attractor[6] == np.array[7, 8, 9]) - assert (attractor[7] == np.array[8, 9, 10]) + assert (attractor[0] == np.array([1, 2, 3])) + assert (attractor[1] == np.array([2, 3, 4])) + assert (attractor[2] == np.array([3, 4, 5])) + assert (attractor[3] == np.array([4, 5, 6])) + assert (attractor[4] == np.array([5, 6, 7])) + assert (attractor[5] == np.array([6, 7, 8])) + assert (attractor[6] == np.array([7, 8, 9])) + assert (attractor[7] == np.array([8, 9, 10])) # Delay = 3 prep = TimeDelayEmbedding(delay=3) attractor = prep(ts) - assert (attractor[0] == np.array[1, 4, 7]) - assert (attractor[1] == np.array[2, 5, 8]) - assert (attractor[2] == np.array[3, 6, 9]) - assert (attractor[3] == np.array[4, 7, 10]) + assert (attractor[0] == np.array([1, 4, 7])) + assert (attractor[1] == np.array([2, 5, 8])) + assert (attractor[2] == np.array([3, 6, 9])) + assert (attractor[3] == np.array([4, 7, 10])) # Skip = 3 prep = TimeDelayEmbedding(skip=3) attractor = prep(ts) - assert (attractor[0] == np.array[1, 2, 3]) - assert (attractor[1] == np.array[4, 5, 6]) - assert (attractor[2] == np.array[7, 8, 9]) + assert (attractor[0] == np.array([1, 2, 3])) + assert (attractor[1] == np.array([4, 5, 6])) + assert (attractor[2] == np.array([7, 8, 9])) # Delay = 2 / Skip = 2 prep = TimeDelayEmbedding(delay=2, skip=2) attractor = prep(ts) - assert (attractor[0] == np.array[1, 3, 5]) - assert (attractor[1] == np.array[3, 5, 7]) - assert (attractor[2] == np.array[5, 7, 9]) + assert (attractor[0] == np.array([1, 3, 5])) + assert (attractor[1] == np.array([3, 5, 7])) + assert (attractor[2] == np.array([5, 7, 9])) -- cgit v1.2.3 From 1c0f48fb26bb2e606dfe0a22e62618357686e2c2 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 18:49:27 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index 1cdf56f9..3b586ad2 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -1,4 +1,5 @@ from gudhi.point_cloud.timedelay import TimeDelayEmbedding +import numpy as np def test_normal(): # Sample array -- cgit v1.2.3 From 39873c0cf43ca7352dddeab8c1cc6a3fc40a2e58 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 19:08:50 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index 3b586ad2..7b6562a5 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -7,7 +7,8 @@ def test_normal(): # Normal case. prep = TimeDelayEmbedding() attractor = prep(ts) - assert (attractor[0] == np.array([1, 2, 3])) + assert (attractor[0] == np.array([1, 2, 3]) + print(attractor[0].all())) assert (attractor[1] == np.array([2, 3, 4])) assert (attractor[2] == np.array([3, 4, 5])) assert (attractor[3] == np.array([4, 5, 6])) -- cgit v1.2.3 From 7c6966ee9821aaeb60d282616445a47071ac1fee Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 19:20:25 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index 7b6562a5..f652fc88 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -7,31 +7,30 @@ def test_normal(): # Normal case. prep = TimeDelayEmbedding() attractor = prep(ts) - assert (attractor[0] == np.array([1, 2, 3]) - print(attractor[0].all())) - assert (attractor[1] == np.array([2, 3, 4])) - assert (attractor[2] == np.array([3, 4, 5])) - assert (attractor[3] == np.array([4, 5, 6])) - assert (attractor[4] == np.array([5, 6, 7])) - assert (attractor[5] == np.array([6, 7, 8])) - assert (attractor[6] == np.array([7, 8, 9])) - assert (attractor[7] == np.array([8, 9, 10])) + assert (attractor[0].all() == np.array([1, 2, 3])) + assert (attractor[1].all() == np.array([2, 3, 4])) + assert (attractor[2].all() == np.array([3, 4, 5])) + assert (attractor[3].all() == np.array([4, 5, 6])) + assert (attractor[4].all() == np.array([5, 6, 7])) + assert (attractor[5].all() == np.array([6, 7, 8])) + assert (attractor[6].all() == np.array([7, 8, 9])) + assert (attractor[7].all() == np.array([8, 9, 10])) # Delay = 3 prep = TimeDelayEmbedding(delay=3) attractor = prep(ts) - assert (attractor[0] == np.array([1, 4, 7])) - assert (attractor[1] == np.array([2, 5, 8])) - assert (attractor[2] == np.array([3, 6, 9])) - assert (attractor[3] == np.array([4, 7, 10])) + assert (attractor[0].all() == np.array([1, 4, 7])) + assert (attractor[1].all() == np.array([2, 5, 8])) + assert (attractor[2].all() == np.array([3, 6, 9])) + assert (attractor[3].all() == np.array([4, 7, 10])) # Skip = 3 prep = TimeDelayEmbedding(skip=3) attractor = prep(ts) - assert (attractor[0] == np.array([1, 2, 3])) - assert (attractor[1] == np.array([4, 5, 6])) - assert (attractor[2] == np.array([7, 8, 9])) + assert (attractor[0].all() == np.array([1, 2, 3])) + assert (attractor[1].all() == np.array([4, 5, 6])) + assert (attractor[2].all() == np.array([7, 8, 9])) # Delay = 2 / Skip = 2 prep = TimeDelayEmbedding(delay=2, skip=2) attractor = prep(ts) - assert (attractor[0] == np.array([1, 3, 5])) - assert (attractor[1] == np.array([3, 5, 7])) - assert (attractor[2] == np.array([5, 7, 9])) + assert (attractor[0].all() == np.array([1, 3, 5])) + assert (attractor[1].all() == np.array([3, 5, 7])) + assert (attractor[2].all() == np.array([5, 7, 9])) -- cgit v1.2.3 From 5023aa0ff30474a96783152844e7fb0ed52e0c98 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Fri, 14 Feb 2020 20:25:14 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index f652fc88..5464a185 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -7,30 +7,30 @@ def test_normal(): # Normal case. prep = TimeDelayEmbedding() attractor = prep(ts) - assert (attractor[0].all() == np.array([1, 2, 3])) - assert (attractor[1].all() == np.array([2, 3, 4])) - assert (attractor[2].all() == np.array([3, 4, 5])) - assert (attractor[3].all() == np.array([4, 5, 6])) - assert (attractor[4].all() == np.array([5, 6, 7])) - assert (attractor[5].all() == np.array([6, 7, 8])) - assert (attractor[6].all() == np.array([7, 8, 9])) - assert (attractor[7].all() == np.array([8, 9, 10])) + assert (attractor[0] == np.array([1, 2, 3])).all() + assert (attractor[1] == np.array([2, 3, 4])).all() + assert (attractor[2] == np.array([3, 4, 5])).all() + assert (attractor[3] == np.array([4, 5, 6])).all() + assert (attractor[4] == np.array([5, 6, 7])).all() + assert (attractor[5] == np.array([6, 7, 8])).all() + assert (attractor[6] == np.array([7, 8, 9])).all() + assert (attractor[7] == np.array([8, 9, 10])).all() # Delay = 3 prep = TimeDelayEmbedding(delay=3) attractor = prep(ts) - assert (attractor[0].all() == np.array([1, 4, 7])) - assert (attractor[1].all() == np.array([2, 5, 8])) - assert (attractor[2].all() == np.array([3, 6, 9])) - assert (attractor[3].all() == np.array([4, 7, 10])) + assert (attractor[0] == np.array([1, 4, 7])).all() + assert (attractor[1] == np.array([2, 5, 8])).all() + assert (attractor[2] == np.array([3, 6, 9])).all() + assert (attractor[3] == np.array([4, 7, 10])).all() # Skip = 3 prep = TimeDelayEmbedding(skip=3) attractor = prep(ts) - assert (attractor[0].all() == np.array([1, 2, 3])) - assert (attractor[1].all() == np.array([4, 5, 6])) - assert (attractor[2].all() == np.array([7, 8, 9])) + assert (attractor[0] == np.array([1, 2, 3])).all() + assert (attractor[1] == np.array([4, 5, 6])).all() + assert (attractor[2] == np.array([7, 8, 9])).all() # Delay = 2 / Skip = 2 prep = TimeDelayEmbedding(delay=2, skip=2) attractor = prep(ts) - assert (attractor[0].all() == np.array([1, 3, 5])) - assert (attractor[1].all() == np.array([3, 5, 7])) - assert (attractor[2].all() == np.array([5, 7, 9])) + assert (attractor[0] == np.array([1, 3, 5])).all() + assert (attractor[1] == np.array([3, 5, 7])).all() + assert (attractor[2] == np.array([5, 7, 9])).all() -- cgit v1.2.3 From 34e1ae726e27fdd7c41f6d80d8ed7f6504dc3a0d Mon Sep 17 00:00:00 2001 From: tlacombe Date: Fri, 14 Feb 2020 18:16:27 +0100 Subject: Global improvement of rendering with Python tools --- src/python/gudhi/persistence_graphical_tools.py | 92 +++++++++++++++++++++---- 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 246280de..4a690241 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -5,6 +5,7 @@ # Copyright (C) 2016 Inria # # Modification(s): +# - 2020/02 Theo Lacombe: Added more options for improved rendering and more flexibility. # - YYYY/MM Author: Description of the modification from os import path @@ -43,6 +44,7 @@ def __min_birth_max_death(persistence, band=0.0): max_death += band return (min_birth, max_death) + def plot_persistence_barcode( persistence=[], persistence_file="", @@ -52,7 +54,9 @@ def plot_persistence_barcode( inf_delta=0.1, legend=False, colormap=None, - axes=None + axes=None, + fontsize=16, + title="Persistence barcode" ): """This function plots the persistence bar code from persistence values list or from a :doc:`persistence file `. @@ -81,11 +85,18 @@ def plot_persistence_barcode( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` + :param fontsize: Fontsize to use in axis. + :type fontsize: int + :param title: title for the plot. + :type title: string :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt import matplotlib.patches as mpatches + from matplotlib import rc + plt.rc('text', usetex=True) + plt.rc('font', family='serif') if persistence_file != "": if path.isfile(persistence_file): @@ -163,7 +174,7 @@ def plot_persistence_barcode( loc="lower right", ) - axes.set_title("Persistence barcode") + axes.set_title(title) # Ends plot on infinity value and starts a little bit before min_birth axes.axis([axis_start, infinity, 0, ind]) @@ -183,7 +194,11 @@ def plot_persistence_diagram( inf_delta=0.1, legend=False, colormap=None, - axes=None + axes=None, + aspect_equal=False, + fontsize=16, + title="Persistence diagram", + greyblock=True ): """This function plots the persistence diagram from persistence values list or from a :doc:`persistence file `. @@ -214,11 +229,23 @@ def plot_persistence_diagram( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` + :param aspect_equal: if True, force plot to be square shaped. + :type aspect_equal: boolean + :param fontsize: Fontsize to use in axis. + :type fontsize: int + :param title: title for the plot. + :type title: string + :param greyblock: if we want to plot a grey patch on the lower half plane for nicer rendering. Default True. + :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt import matplotlib.patches as mpatches + from matplotlib import rc + plt.rc('text', usetex=True) + plt.rc('font', family='serif') + if persistence_file != "": if path.isfile(persistence_file): @@ -256,18 +283,27 @@ def plot_persistence_diagram( # Replace infinity values with max_death + delta for diagram to be more # readable infinity = max_death + delta + axis_end = max_death + delta / 2 axis_start = min_birth - delta # line display of equation : birth = death x = np.linspace(axis_start, infinity, 1000) # infinity line and text - axes.plot(x, x, color="k", linewidth=1.0) - axes.plot(x, [infinity] * len(x), linewidth=1.0, color="k", alpha=alpha) - axes.text(axis_start, infinity, r"$\infty$", color="k", alpha=alpha) + 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() + yt = np.append(yt, infinity) + ytl = yt.tolist() + ytl[-1] = r'$+\infty$' + axes.set_yticks(yt) + axes.set_yticklabels(ytl) # bootstrap band if band > 0.0: 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')) # Draw points in loop for interval in reversed(persistence): if float(interval[1][1]) != float("inf"): @@ -293,11 +329,13 @@ def plot_persistence_diagram( ] ) - axes.set_xlabel("Birth") - axes.set_ylabel("Death") + axes.set_xlabel("Birth", fontsize=fontsize) + axes.set_ylabel("Death", fontsize=fontsize) # Ends plot on infinity value and starts a little bit before min_birth - axes.axis([axis_start, infinity, axis_start, infinity + delta]) - axes.set_title("Persistence diagram") + axes.axis([axis_start, axis_end, axis_start, infinity + delta]) + axes.set_title(title, fontsize=fontsize) # a different fontsize for the title? + if aspect_equal: + axes.set_aspect("equal") return axes except ImportError: @@ -313,7 +351,11 @@ def plot_persistence_density( dimension=None, cmap=None, legend=False, - axes=None + axes=None, + aspect_equal=False, + fontsize=16, + title="Persistence density", + greyblock=True ): """This function plots the persistence density from persistence values list or from a :doc:`persistence file `. Be @@ -355,11 +397,25 @@ def plot_persistence_density( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` + :param aspect_equal: if True, force plot to be square shaped. + :type aspect_equal: boolean + :param fontsize: Fontsize to use in axis. + :type fontsize: int + :param title: title for the plot. + :type title: string + :param greyblock: if we want to plot a grey patch on the lower half plane + for nicer rendering. Default True. + :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt + import matplotlib.patches as mpatches from scipy.stats import kde + from matplotlib import rc + plt.rc('text', usetex=True) + plt.rc('font', family='serif') + if persistence_file != "": if dimension is None: @@ -418,12 +474,18 @@ def plot_persistence_density( # Make the plot img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap) + if greyblock: + 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) - axes.set_xlabel("Birth") - axes.set_ylabel("Death") - axes.set_title("Persistence density") + axes.set_xlabel("Birth", fontsize=fontsize) + axes.set_ylabel("Death", fontsize=fontsize) + axes.set_title(title, fontsize=fontsize) + if aspect_equal: + axes.set_aspect("equal") + return axes except ImportError: -- cgit v1.2.3 From 80d84e5d8f9a24de745d23f7d721ea3e62217ff4 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Wed, 19 Feb 2020 12:32:00 +0900 Subject: Update timedelay.py --- src/python/gudhi/point_cloud/timedelay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py index d899da67..6ad87cdc 100644 --- a/src/python/gudhi/point_cloud/timedelay.py +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -43,7 +43,7 @@ class TimeDelayEmbedding: A single time-series data. Returns ------- - point clouds : list[list[float, float, float]] + point clouds : list of n x 2 numpy arrays Makes point cloud every a single time-series data. Raises ------- @@ -80,7 +80,7 @@ class TimeDelayEmbedding: the same size. Returns ------- - point clouds : list[list[list[float, float, float]]] + point clouds : list of n x 3 numpy arrays Makes point cloud every a single time-series data. Raises ------- -- cgit v1.2.3 From 88964b4ff10798d6d9c3d0a342c004ee6b8b1496 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Tue, 25 Feb 2020 13:21:55 +0900 Subject: Update timedelay.py --- src/python/gudhi/point_cloud/timedelay.py | 89 +++++++++++++++---------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py index 6ad87cdc..d7a1dab7 100644 --- a/src/python/gudhi/point_cloud/timedelay.py +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -8,10 +8,12 @@ import numpy as np + class TimeDelayEmbedding: """Point cloud transformation class. Embeds time-series data in the R^d according to Takens' Embedding Theorem and obtains the coordinates of each point. + Parameters ---------- dim : int, optional (default=3) @@ -20,16 +22,27 @@ class TimeDelayEmbedding: Time-Delay embedding. skip : int, optional (default=1) How often to skip embedded points. - Given delay=3 and skip=2, an point cloud which is obtained by embedding - a single time-series data into R^3 is as follows. - - .. code-block:: none - - time-series = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - point clouds = [[1, 4, 7], - [3, 6, 9]] - + + Example + ------- + + Given delay=3 and skip=2, a point cloud which is obtained by embedding + a scalar time-series into R^3 is as follows:: + + time-series = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + point cloud = [[1, 4, 7], + [3, 6, 9]] + + Given delay=1 and skip=1, a point cloud which is obtained by embedding + a 2D vector time-series data into R^4 is as follows:: + + time-series = [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] + point cloud = [[0, 1, 2, 3], + [2, 3, 4, 5], + [4, 5, 6, 7], + [6, 7, 8, 9]] """ + def __init__(self, dim=3, delay=1, skip=1): self._dim = dim self._delay = delay @@ -39,56 +52,42 @@ class TimeDelayEmbedding: """Transform method for single time-series data. Parameters ---------- - ts : list[float] - A single time-series data. + ts : Iterable[float] or Iterable[Iterable[float]] + A single time-series data, with scalar or vector values. + Returns ------- - point clouds : list of n x 2 numpy arrays - Makes point cloud every a single time-series data. - Raises - ------- - TypeError - If the parameter's type does not match the desired type. + point cloud : n x dim numpy arrays + Makes point cloud from a single time-series data. """ - ndts = np.array(ts) - if ndts.ndim == 1: - return self._transform(ndts) - else: - raise TypeError("Expects 1-dimensional array.") + return self._transform(np.array(ts)) def fit(self, ts, y=None): return self def _transform(self, ts): """Guts of transform method.""" - return ts[ - np.add.outer( - np.arange(0, len(ts)-self._delay*(self._dim-1), self._skip), - np.arange(0, self._dim*self._delay, self._delay)) - ] + if ts.ndim == 1: + repeat = self._dim + else: + assert self._dim % ts.shape[1] == 0 + repeat = self._dim // ts.shape[1] + end = len(ts) - self._delay * (repeat - 1) + short = np.arange(0, end, self._skip) + vertical = np.arange(0, repeat * self._delay, self._delay) + return ts[np.add.outer(short, vertical)].reshape(len(short), -1) def transform(self, ts): """Transform method for multiple time-series data. + Parameters ---------- - ts : list[list[float]] - Multiple time-series data. - Attributes - ---------- - ndts : - The ndts means that all time series need to have exactly - the same size. + ts : Iterable[Iterable[float]] or Iterable[Iterable[Iterable[float]]] + Multiple time-series data, with scalar or vector values. + Returns ------- - point clouds : list of n x 3 numpy arrays - Makes point cloud every a single time-series data. - Raises - ------- - TypeError - If the parameter's type does not match the desired type. + point clouds : list of n x dim numpy arrays + Makes point cloud from each time-series data. """ - ndts = np.array(ts) - if ndts.ndim == 2: - return np.apply_along_axis(self._transform, 1, ndts) - else: - raise TypeError("Expects 2-dimensional array.") + return [self._transform(np.array(s)) for s in ts] -- cgit v1.2.3 From 66c96498b994fea1fcaa6877121023410f4209f9 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Tue, 25 Feb 2020 13:24:48 +0900 Subject: Update test_time_delay.py --- src/python/test/test_time_delay.py | 51 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/python/test/test_time_delay.py b/src/python/test/test_time_delay.py index 5464a185..1ead9bca 100755 --- a/src/python/test/test_time_delay.py +++ b/src/python/test/test_time_delay.py @@ -1,36 +1,43 @@ from gudhi.point_cloud.timedelay import TimeDelayEmbedding import numpy as np + def test_normal(): # Sample array ts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # Normal case. prep = TimeDelayEmbedding() - attractor = prep(ts) - assert (attractor[0] == np.array([1, 2, 3])).all() - assert (attractor[1] == np.array([2, 3, 4])).all() - assert (attractor[2] == np.array([3, 4, 5])).all() - assert (attractor[3] == np.array([4, 5, 6])).all() - assert (attractor[4] == np.array([5, 6, 7])).all() - assert (attractor[5] == np.array([6, 7, 8])).all() - assert (attractor[6] == np.array([7, 8, 9])).all() - assert (attractor[7] == np.array([8, 9, 10])).all() + pointclouds = prep(ts) + assert (pointclouds[0] == np.array([1, 2, 3])).all() + assert (pointclouds[1] == np.array([2, 3, 4])).all() + assert (pointclouds[2] == np.array([3, 4, 5])).all() + assert (pointclouds[3] == np.array([4, 5, 6])).all() + assert (pointclouds[4] == np.array([5, 6, 7])).all() + assert (pointclouds[5] == np.array([6, 7, 8])).all() + assert (pointclouds[6] == np.array([7, 8, 9])).all() + assert (pointclouds[7] == np.array([8, 9, 10])).all() # Delay = 3 prep = TimeDelayEmbedding(delay=3) - attractor = prep(ts) - assert (attractor[0] == np.array([1, 4, 7])).all() - assert (attractor[1] == np.array([2, 5, 8])).all() - assert (attractor[2] == np.array([3, 6, 9])).all() - assert (attractor[3] == np.array([4, 7, 10])).all() + pointclouds = prep(ts) + assert (pointclouds[0] == np.array([1, 4, 7])).all() + assert (pointclouds[1] == np.array([2, 5, 8])).all() + assert (pointclouds[2] == np.array([3, 6, 9])).all() + assert (pointclouds[3] == np.array([4, 7, 10])).all() # Skip = 3 prep = TimeDelayEmbedding(skip=3) - attractor = prep(ts) - assert (attractor[0] == np.array([1, 2, 3])).all() - assert (attractor[1] == np.array([4, 5, 6])).all() - assert (attractor[2] == np.array([7, 8, 9])).all() + pointclouds = prep(ts) + assert (pointclouds[0] == np.array([1, 2, 3])).all() + assert (pointclouds[1] == np.array([4, 5, 6])).all() + assert (pointclouds[2] == np.array([7, 8, 9])).all() # Delay = 2 / Skip = 2 prep = TimeDelayEmbedding(delay=2, skip=2) - attractor = prep(ts) - assert (attractor[0] == np.array([1, 3, 5])).all() - assert (attractor[1] == np.array([3, 5, 7])).all() - assert (attractor[2] == np.array([5, 7, 9])).all() + pointclouds = prep(ts) + assert (pointclouds[0] == np.array([1, 3, 5])).all() + assert (pointclouds[1] == np.array([3, 5, 7])).all() + assert (pointclouds[2] == np.array([5, 7, 9])).all() + + # Vector series + ts = np.arange(0, 10).reshape(-1, 2) + prep = TimeDelayEmbedding(dim=4) + prep.fit([ts]) + assert (prep.transform([ts])[0] == [[0, 1, 2, 3], [2, 3, 4, 5], [4, 5, 6, 7], [6, 7, 8, 9]]).all() -- cgit v1.2.3 From a74ec878560bbe5fa340b2650ca9c16471b685af Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Tue, 25 Feb 2020 13:27:03 +0900 Subject: Update point_cloud.rst --- src/python/doc/point_cloud.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python/doc/point_cloud.rst b/src/python/doc/point_cloud.rst index 55c74ff3..c0d4b303 100644 --- a/src/python/doc/point_cloud.rst +++ b/src/python/doc/point_cloud.rst @@ -26,4 +26,5 @@ TimeDelayEmbedding .. autoclass:: gudhi.point_cloud.timedelay.TimeDelayEmbedding :members: + :special-members: __call__ -- cgit v1.2.3 From f25d0f86fcd4ac9ab2939b2919d7a66df8b21269 Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Tue, 25 Feb 2020 16:35:41 +0900 Subject: Update timedelay.py --- src/python/gudhi/point_cloud/timedelay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py index d7a1dab7..576f4386 100644 --- a/src/python/gudhi/point_cloud/timedelay.py +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -50,6 +50,7 @@ class TimeDelayEmbedding: def __call__(self, ts): """Transform method for single time-series data. + Parameters ---------- ts : Iterable[float] or Iterable[Iterable[float]] -- cgit v1.2.3 From 2c1edeb7fd241c8718a22618438b482704703b4a Mon Sep 17 00:00:00 2001 From: mtakenouchi Date: Tue, 25 Feb 2020 17:46:28 +0900 Subject: Update timedelay.py --- src/python/gudhi/point_cloud/timedelay.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python/gudhi/point_cloud/timedelay.py b/src/python/gudhi/point_cloud/timedelay.py index 576f4386..f01df442 100644 --- a/src/python/gudhi/point_cloud/timedelay.py +++ b/src/python/gudhi/point_cloud/timedelay.py @@ -11,8 +11,9 @@ import numpy as np class TimeDelayEmbedding: """Point cloud transformation class. - Embeds time-series data in the R^d according to Takens' Embedding Theorem - and obtains the coordinates of each point. + Embeds time-series data in the R^d according to [Takens' Embedding Theorem] + (https://en.wikipedia.org/wiki/Takens%27s_theorem) and obtains the + coordinates of each point. Parameters ---------- -- cgit v1.2.3 From 835a831007196a4d93e57659ab8d3cdb28a4ef92 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 25 Feb 2020 18:14:06 +0100 Subject: Revert "Global improvement of rendering with Python tools" This reverts commit 34e1ae726e27fdd7c41f6d80d8ed7f6504dc3a0d. --- src/python/gudhi/persistence_graphical_tools.py | 92 ++++--------------------- 1 file changed, 15 insertions(+), 77 deletions(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 4a690241..246280de 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -5,7 +5,6 @@ # Copyright (C) 2016 Inria # # Modification(s): -# - 2020/02 Theo Lacombe: Added more options for improved rendering and more flexibility. # - YYYY/MM Author: Description of the modification from os import path @@ -44,7 +43,6 @@ def __min_birth_max_death(persistence, band=0.0): max_death += band return (min_birth, max_death) - def plot_persistence_barcode( persistence=[], persistence_file="", @@ -54,9 +52,7 @@ def plot_persistence_barcode( inf_delta=0.1, legend=False, colormap=None, - axes=None, - fontsize=16, - title="Persistence barcode" + axes=None ): """This function plots the persistence bar code from persistence values list or from a :doc:`persistence file `. @@ -85,18 +81,11 @@ def plot_persistence_barcode( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` - :param fontsize: Fontsize to use in axis. - :type fontsize: int - :param title: title for the plot. - :type title: string :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt import matplotlib.patches as mpatches - from matplotlib import rc - plt.rc('text', usetex=True) - plt.rc('font', family='serif') if persistence_file != "": if path.isfile(persistence_file): @@ -174,7 +163,7 @@ def plot_persistence_barcode( loc="lower right", ) - axes.set_title(title) + axes.set_title("Persistence barcode") # Ends plot on infinity value and starts a little bit before min_birth axes.axis([axis_start, infinity, 0, ind]) @@ -194,11 +183,7 @@ def plot_persistence_diagram( inf_delta=0.1, legend=False, colormap=None, - axes=None, - aspect_equal=False, - fontsize=16, - title="Persistence diagram", - greyblock=True + axes=None ): """This function plots the persistence diagram from persistence values list or from a :doc:`persistence file `. @@ -229,23 +214,11 @@ def plot_persistence_diagram( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` - :param aspect_equal: if True, force plot to be square shaped. - :type aspect_equal: boolean - :param fontsize: Fontsize to use in axis. - :type fontsize: int - :param title: title for the plot. - :type title: string - :param greyblock: if we want to plot a grey patch on the lower half plane for nicer rendering. Default True. - :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt import matplotlib.patches as mpatches - from matplotlib import rc - plt.rc('text', usetex=True) - plt.rc('font', family='serif') - if persistence_file != "": if path.isfile(persistence_file): @@ -283,27 +256,18 @@ def plot_persistence_diagram( # Replace infinity values with max_death + delta for diagram to be more # readable infinity = max_death + delta - axis_end = max_death + delta / 2 axis_start = min_birth - delta # line display of equation : birth = death x = np.linspace(axis_start, infinity, 1000) # 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() - yt = np.append(yt, infinity) - ytl = yt.tolist() - ytl[-1] = r'$+\infty$' - axes.set_yticks(yt) - axes.set_yticklabels(ytl) + axes.plot(x, x, color="k", linewidth=1.0) + axes.plot(x, [infinity] * len(x), linewidth=1.0, color="k", alpha=alpha) + axes.text(axis_start, infinity, r"$\infty$", color="k", alpha=alpha) # bootstrap band if band > 0.0: 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')) + # Draw points in loop for interval in reversed(persistence): if float(interval[1][1]) != float("inf"): @@ -329,13 +293,11 @@ def plot_persistence_diagram( ] ) - axes.set_xlabel("Birth", fontsize=fontsize) - axes.set_ylabel("Death", fontsize=fontsize) + axes.set_xlabel("Birth") + axes.set_ylabel("Death") # Ends plot on infinity value and starts a little bit before min_birth - axes.axis([axis_start, axis_end, axis_start, infinity + delta]) - axes.set_title(title, fontsize=fontsize) # a different fontsize for the title? - if aspect_equal: - axes.set_aspect("equal") + axes.axis([axis_start, infinity, axis_start, infinity + delta]) + axes.set_title("Persistence diagram") return axes except ImportError: @@ -351,11 +313,7 @@ def plot_persistence_density( dimension=None, cmap=None, legend=False, - axes=None, - aspect_equal=False, - fontsize=16, - title="Persistence density", - greyblock=True + axes=None ): """This function plots the persistence density from persistence values list or from a :doc:`persistence file `. Be @@ -397,25 +355,11 @@ def plot_persistence_density( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` - :param aspect_equal: if True, force plot to be square shaped. - :type aspect_equal: boolean - :param fontsize: Fontsize to use in axis. - :type fontsize: int - :param title: title for the plot. - :type title: string - :param greyblock: if we want to plot a grey patch on the lower half plane - for nicer rendering. Default True. - :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt - import matplotlib.patches as mpatches from scipy.stats import kde - from matplotlib import rc - plt.rc('text', usetex=True) - plt.rc('font', family='serif') - if persistence_file != "": if dimension is None: @@ -474,18 +418,12 @@ def plot_persistence_density( # Make the plot img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap) - if greyblock: - 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) - axes.set_xlabel("Birth", fontsize=fontsize) - axes.set_ylabel("Death", fontsize=fontsize) - axes.set_title(title, fontsize=fontsize) - if aspect_equal: - axes.set_aspect("equal") - + axes.set_xlabel("Birth") + axes.set_ylabel("Death") + axes.set_title("Persistence density") return axes except ImportError: -- cgit v1.2.3 From cdcd2904a1c682625670a62608fd781bfd571516 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 25 Feb 2020 18:19:24 +0100 Subject: solved scale issue and removed title/aspect as functions return ax --- src/python/gudhi/persistence_graphical_tools.py | 77 +++++++++++++++++++------ 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 246280de..8ddfdba8 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -5,6 +5,7 @@ # Copyright (C) 2016 Inria # # Modification(s): +# - 2020/02 Theo Lacombe: Added more options for improved rendering and more flexibility. # - YYYY/MM Author: Description of the modification from os import path @@ -43,6 +44,7 @@ def __min_birth_max_death(persistence, band=0.0): max_death += band return (min_birth, max_death) + def plot_persistence_barcode( persistence=[], persistence_file="", @@ -52,7 +54,8 @@ def plot_persistence_barcode( inf_delta=0.1, legend=False, colormap=None, - axes=None + axes=None, + fontsize=16, ): """This function plots the persistence bar code from persistence values list or from a :doc:`persistence file `. @@ -81,11 +84,16 @@ def plot_persistence_barcode( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` + :param fontsize: Fontsize to use in axis. + :type fontsize: int :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt import matplotlib.patches as mpatches + from matplotlib import rc + plt.rc('text', usetex=True) + plt.rc('font', family='serif') if persistence_file != "": if path.isfile(persistence_file): @@ -163,7 +171,7 @@ def plot_persistence_barcode( loc="lower right", ) - axes.set_title("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]) @@ -183,7 +191,9 @@ def plot_persistence_diagram( inf_delta=0.1, legend=False, colormap=None, - axes=None + axes=None, + fontsize=16, + greyblock=True ): """This function plots the persistence diagram from persistence values list or from a :doc:`persistence file `. @@ -214,11 +224,19 @@ def plot_persistence_diagram( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` + :param fontsize: Fontsize to use in axis. + :type fontsize: int + :param greyblock: if we want to plot a grey patch on the lower half plane for nicer rendering. Default True. + :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt import matplotlib.patches as mpatches + from matplotlib import rc + plt.rc('text', usetex=True) + plt.rc('font', family='serif') + if persistence_file != "": if path.isfile(persistence_file): @@ -256,18 +274,27 @@ def plot_persistence_diagram( # Replace infinity values with max_death + delta for diagram to be more # readable infinity = max_death + delta + axis_end = max_death + delta / 2 axis_start = min_birth - delta - # line display of equation : birth = death - x = np.linspace(axis_start, infinity, 1000) # infinity line and text - axes.plot(x, x, color="k", linewidth=1.0) - axes.plot(x, [infinity] * len(x), linewidth=1.0, color="k", alpha=alpha) - axes.text(axis_start, infinity, r"$\infty$", color="k", alpha=alpha) + 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() + 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$' + axes.set_yticks(yt) + axes.set_yticklabels(ytl) # 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')) # Draw points in loop for interval in reversed(persistence): if float(interval[1][1]) != float("inf"): @@ -293,11 +320,11 @@ def plot_persistence_diagram( ] ) - axes.set_xlabel("Birth") - axes.set_ylabel("Death") + 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, infinity, axis_start, infinity + delta]) - axes.set_title("Persistence diagram") + axes.axis([axis_start, axis_end, axis_start, infinity + delta/2]) return axes except ImportError: @@ -313,7 +340,9 @@ def plot_persistence_density( dimension=None, cmap=None, legend=False, - axes=None + axes=None, + fontsize=16, + greyblock=True ): """This function plots the persistence density from persistence values list or from a :doc:`persistence file `. Be @@ -355,11 +384,21 @@ def plot_persistence_density( :param axes: A matplotlib-like subplot axes. If None, the plot is drawn on a new set of axes. :type axes: `matplotlib.axes.Axes` + :param fontsize: Fontsize to use in axis. + :type fontsize: int + :param greyblock: if we want to plot a grey patch on the lower half plane + for nicer rendering. Default True. + :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ try: import matplotlib.pyplot as plt + import matplotlib.patches as mpatches from scipy.stats import kde + from matplotlib import rc + plt.rc('text', usetex=True) + plt.rc('font', family='serif') + if persistence_file != "": if dimension is None: @@ -418,12 +457,16 @@ def plot_persistence_density( # Make the plot img = axes.pcolormesh(xi, yi, zi.reshape(xi.shape), cmap=cmap) + if greyblock: + 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) - axes.set_xlabel("Birth") - axes.set_ylabel("Death") - axes.set_title("Persistence density") + axes.set_xlabel("Birth", fontsize=fontsize) + axes.set_ylabel("Death", fontsize=fontsize) + axes.set_title("Persistence density", fontsize=fontsize) + return axes except ImportError: -- cgit v1.2.3 From 3ecf0caf4efbea0fabf4af0df490900374abda8b Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 25 Feb 2020 18:20:30 +0100 Subject: say in doc that functions return ax --- src/python/doc/persistence_graphical_tools_sum.inc | 6 +++--- src/python/doc/persistence_graphical_tools_user.rst | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/python/doc/persistence_graphical_tools_sum.inc b/src/python/doc/persistence_graphical_tools_sum.inc index 0cdf8072..2ddaccfc 100644 --- a/src/python/doc/persistence_graphical_tools_sum.inc +++ b/src/python/doc/persistence_graphical_tools_sum.inc @@ -3,10 +3,10 @@ +-----------------------------------------------------------------+-----------------------------------------------------------------------+-----------------------------------------------+ | .. figure:: | These graphical tools comes on top of persistence results and allows | :Author: Vincent Rouvreau | - | img/graphical_tools_representation.png | the user to build easily persistence barcode, diagram or density. | | + | img/graphical_tools_representation.png | the user to display easily persistence barcode, diagram or density. | | | | | :Introduced in: GUDHI 2.0.0 | - | | | | - | | | :Copyright: MIT | + | | Note that these functions return the matplotlib axis, allowing | | + | | for further modifications (title, aspect, etc.) | :Copyright: MIT | | | | | | | | :Requires: matplotlib, numpy and scipy | +-----------------------------------------------------------------+-----------------------------------------------------------------------+-----------------------------------------------+ diff --git a/src/python/doc/persistence_graphical_tools_user.rst b/src/python/doc/persistence_graphical_tools_user.rst index 80002db6..ff51604e 100644 --- a/src/python/doc/persistence_graphical_tools_user.rst +++ b/src/python/doc/persistence_graphical_tools_user.rst @@ -20,7 +20,7 @@ This function can display the persistence result as a barcode: .. plot:: :include-source: - import matplotlib.pyplot as plot + import matplotlib.pyplot as plt import gudhi off_file = gudhi.__root_source_dir__ + '/data/points/tore3D_300.off' @@ -31,7 +31,7 @@ This function can display the persistence result as a barcode: diag = simplex_tree.persistence(min_persistence=0.4) gudhi.plot_persistence_barcode(diag) - plot.show() + plt.show() Show persistence as a diagram ----------------------------- @@ -44,15 +44,19 @@ This function can display the persistence result as a diagram: .. plot:: :include-source: - import matplotlib.pyplot as plot + import matplotlib.pyplot as plt import gudhi # rips_on_tore3D_1307.pers obtained from write_persistence_diagram method persistence_file=gudhi.__root_source_dir__ + \ '/data/persistence_diagram/rips_on_tore3D_1307.pers' - gudhi.plot_persistence_diagram(persistence_file=persistence_file, + ax = gudhi.plot_persistence_diagram(persistence_file=persistence_file, legend=True) - plot.show() + # We can modify the title, aspect, etc. + ax.set_title("Persistence diagram of a torus") + ax.set_aspect("equal") # forces to be square shaped + plt.show() + Persistence density ------------------- @@ -65,7 +69,7 @@ If you want more information on a specific dimension, for instance: .. plot:: :include-source: - import matplotlib.pyplot as plot + import matplotlib.pyplot as plt import gudhi # rips_on_tore3D_1307.pers obtained from write_persistence_diagram method persistence_file=gudhi.__root_source_dir__ + \ @@ -75,9 +79,9 @@ If you want more information on a specific dimension, for instance: only_this_dim=1) pers_diag = [(1, elt) for elt in birth_death] # Use subplots to display diagram and density side by side - fig, axes = plot.subplots(nrows=1, ncols=2, figsize=(12, 5)) + fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 5)) gudhi.plot_persistence_diagram(persistence=pers_diag, axes=axes[0]) gudhi.plot_persistence_density(persistence=pers_diag, dimension=1, legend=True, axes=axes[1]) - plot.show() + plt.show() -- cgit v1.2.3 From cbb350d81a8c4acadf31b604aaebde209f462e55 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Wed, 26 Feb 2020 09:32:32 +0100 Subject: Code review: remove import pytest leftovers --- src/python/test/test_euclidean_witness_complex.py | 1 - src/python/test/test_rips_complex.py | 1 - src/python/test/test_simplex_tree.py | 1 - src/python/test/test_tangential_complex.py | 1 - 4 files changed, 4 deletions(-) diff --git a/src/python/test/test_euclidean_witness_complex.py b/src/python/test/test_euclidean_witness_complex.py index 47196a2a..f3664d39 100755 --- a/src/python/test/test_euclidean_witness_complex.py +++ b/src/python/test/test_euclidean_witness_complex.py @@ -9,7 +9,6 @@ """ import gudhi -import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" diff --git a/src/python/test/test_rips_complex.py b/src/python/test/test_rips_complex.py index f5c086cb..b86e7498 100755 --- a/src/python/test/test_rips_complex.py +++ b/src/python/test/test_rips_complex.py @@ -10,7 +10,6 @@ from gudhi import RipsComplex from math import sqrt -import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index eca3807b..04b26e92 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -9,7 +9,6 @@ """ from gudhi import SimplexTree -import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" diff --git a/src/python/test/test_tangential_complex.py b/src/python/test/test_tangential_complex.py index fc500c45..8668a2e0 100755 --- a/src/python/test/test_tangential_complex.py +++ b/src/python/test/test_tangential_complex.py @@ -9,7 +9,6 @@ """ from gudhi import TangentialComplex, SimplexTree -import pytest __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" -- cgit v1.2.3 From f85742957276cbd15a2724c86cbc7a8279d62ef9 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Wed, 26 Feb 2020 11:11:32 +0100 Subject: Code review: add some comments about range.begin() and range.end() --- src/python/include/Simplex_tree_interface.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 55d5af97..66ce5afd 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -124,18 +124,22 @@ class Simplex_tree_interface : public Simplex_tree { // Iterator over the simplex tree typename std::vector::const_iterator get_filtration_iterator_begin() { // Base::initialize_filtration(); already performed in filtration_simplex_range + // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::filtration_simplex_range().begin(); } typename std::vector::const_iterator get_filtration_iterator_end() { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::filtration_simplex_range().end(); } Skeleton_simplex_iterator get_skeleton_iterator_begin(int dimension) { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::skeleton_simplex_range(dimension).begin(); } Skeleton_simplex_iterator get_skeleton_iterator_end(int dimension) { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::skeleton_simplex_range(dimension).end(); } }; -- cgit v1.2.3 From 8e4f3d151818b78a29d11cdc6ca171947bfd6dd9 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 3 Mar 2020 15:33:17 +0100 Subject: update wasserstein distance with pot so that it can return optimal matching now! --- src/python/doc/wasserstein_distance_user.rst | 24 ++++++++++ src/python/gudhi/wasserstein.py | 69 ++++++++++++++++++++++------ src/python/test/test_wasserstein_distance.py | 31 +++++++++---- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index 94b454e2..d3daa318 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -47,3 +47,27 @@ The output is: .. testoutput:: Wasserstein distance value = 1.45 + +We can also have access to the optimal matching by letting `matching=True`. +It is encoded as a list of indices (i,j), meaning that the i-th point in X +is mapped to the j-th point in Y. +An index of -1 represents the diagonal. + +.. testcode:: + + import gudhi.wasserstein + import numpy as np + + diag1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) + diag2 = np.array([[2.8, 4.45], [5, 6], [9.5, 14.1]]) + cost, matching = gudhi.wasserstein.wasserstein_distance(diag1, diag2, matching=True, order=1., internal_p=2.) + + message = "Wasserstein distance value = %.2f, optimal matching: %s" %(cost, matching) + print(message) + +The output is: + +.. testoutput:: + + Wasserstein distance value = 2.15, optimal matching: [(0, 0), (1, 2), (2, -1), (-1, 1)] + diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index 13102094..ba0f7343 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -62,14 +62,39 @@ def _perstot(X, order, internal_p): return (np.sum(np.linalg.norm(X - Xdiag, ord=internal_p, axis=1)**order))**(1./order) -def wasserstein_distance(X, Y, order=2., internal_p=2.): +def _clean_match(match, n, m): ''' - :param X: (n x 2) numpy.array encoding the (finite points of the) first diagram. Must not contain essential points (i.e. with infinite coordinate). + :param match: a list of the form [(i,j) ...] + :param n: int, size of the first dgm + :param m: int, size of the second dgm + :return: a modified version of match where indices greater than n, m are replaced by -1, encoding the diagonal. + and (-1, -1) are removed + ''' + new_match = [] + for i,j in match: + if i >= n: + if j < m: + new_match.append((-1, j)) + elif j >= m: + if i < n: + new_match.append((i,-1)) + else: + new_match.append((i,j)) + return new_match + + +def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): + ''' + :param X: (n x 2) numpy.array encoding the (finite points of the) first diagram. Must not contain essential points + (i.e. with infinite coordinate). :param Y: (m x 2) numpy.array encoding the second diagram. + :param matching: if True, computes and returns the optimal matching between X and Y, encoded as... :param order: exponent for Wasserstein; Default value is 2. - :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2); Default value is 2 (Euclidean norm). - :returns: the Wasserstein distance of order q (1 <= q < infinity) between persistence diagrams with respect to the internal_p-norm as ground metric. - :rtype: float + :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2); + Default value is 2 (Euclidean norm). + :returns: the Wasserstein distance of order q (1 <= q < infinity) between persistence diagrams with + respect to the internal_p-norm as ground metric. + If matching is set to True, also returns the optimal matching between X and Y. ''' n = len(X) m = len(Y) @@ -77,21 +102,39 @@ def wasserstein_distance(X, Y, order=2., internal_p=2.): # handle empty diagrams if X.size == 0: if Y.size == 0: - return 0. + if not matching: + return 0. + else: + return 0., [] else: - return _perstot(Y, order, internal_p) + if not matching: + return _perstot(Y, order, internal_p) + else: + return _perstot(Y, order, internal_p), [(-1, j) for j in range(m)] elif Y.size == 0: - return _perstot(X, order, internal_p) + if not matching: + return _perstot(X, order, internal_p) + else: + return _perstot(X, order, internal_p), [(i, -1) for i in range(n)] M = _build_dist_matrix(X, Y, order=order, internal_p=internal_p) - a = np.full(n+1, 1. / (n + m) ) # weight vector of the input diagram. Uniform here. - a[-1] = a[-1] * m # normalized so that we have a probability measure, required by POT - b = np.full(m+1, 1. / (n + m) ) # weight vector of the input diagram. Uniform here. - b[-1] = b[-1] * n # so that we have a probability measure, required by POT + a = np.ones(n+1) # weight vector of the input diagram. Uniform here. + a[-1] = m + b = np.ones(m+1) # weight vector of the input diagram. Uniform here. + b[-1] = n + + if matching: + P = ot.emd(a=a,b=b,M=M, numItermax=2000000) + ot_cost = np.sum(np.multiply(P,M)) + P[P < 0.5] = 0 # trick to avoid numerical issue, could it be improved? + match = np.argwhere(P) + # Now we turn to -1 points encoding the diagonal + match = _clean_match(match, n, m) + return ot_cost ** (1./order) , match # Comptuation of the otcost using the ot.emd2 library. # Note: it is the Wasserstein distance to the power q. # The default numItermax=100000 is not sufficient for some examples with 5000 points, what is a good value? - ot_cost = (n+m) * ot.emd2(a, b, M, numItermax=2000000) + ot_cost = ot.emd2(a, b, M, numItermax=2000000) return ot_cost ** (1./order) diff --git a/src/python/test/test_wasserstein_distance.py b/src/python/test/test_wasserstein_distance.py index 6a6b217b..02a1d2c9 100755 --- a/src/python/test/test_wasserstein_distance.py +++ b/src/python/test/test_wasserstein_distance.py @@ -51,14 +51,27 @@ def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True): assert wasserstein_distance(diag3, diag4, internal_p=1., order=2.) == approx(np.sqrt(5)) assert wasserstein_distance(diag3, diag4, internal_p=4.5, order=2.) == approx(np.sqrt(5)) - if(not test_infinity): - return + if test_infinity: + diag5 = np.array([[0, 3], [4, np.inf]]) + diag6 = np.array([[7, 8], [4, 6], [3, np.inf]]) - diag5 = np.array([[0, 3], [4, np.inf]]) - diag6 = np.array([[7, 8], [4, 6], [3, np.inf]]) + assert wasserstein_distance(diag4, diag5) == np.inf + assert wasserstein_distance(diag5, diag6, order=1, internal_p=np.inf) == approx(4.) + + + if test_matching: + match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=1., order=2)[1] + assert match == [] + match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] + assert match == [] + match = wasserstein_distance(emptydiag, diag2, matching=True, internal_p=np.inf, order=2.)[1] + assert match == [(-1, 0), (-1, 1)] + match = wasserstein_distance(diag2, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] + assert match == [(0, -1), (1, -1)] + match = wasserstein_distance(diag1, diag2, matching=True, internal_p=2., order=2.)[1] + assert match == [(0, 0), (1, 1), (2, -1)] + - assert wasserstein_distance(diag4, diag5) == np.inf - assert wasserstein_distance(diag5, diag6, order=1, internal_p=np.inf) == approx(4.) def hera_wrap(delta): def fun(*kargs,**kwargs): @@ -66,8 +79,8 @@ def hera_wrap(delta): return fun def test_wasserstein_distance_pot(): - _basic_wasserstein(pot, 1e-15, test_infinity=False) + _basic_wasserstein(pot, 1e-15, test_infinity=False, test_matching=True) def test_wasserstein_distance_hera(): - _basic_wasserstein(hera_wrap(1e-12), 1e-12) - _basic_wasserstein(hera_wrap(.1), .1) + _basic_wasserstein(hera_wrap(1e-12), 1e-12, test_matching=False) + _basic_wasserstein(hera_wrap(.1), .1, test_matching=False) -- cgit v1.2.3 From 2141ef8adfee531f3eaf822cf4076b9b010e6f94 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 3 Mar 2020 16:22:48 +0100 Subject: correction missing arg in test_wasserstein_distance --- src/python/test/test_wasserstein_distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/test/test_wasserstein_distance.py b/src/python/test/test_wasserstein_distance.py index 02a1d2c9..d0f0323c 100755 --- a/src/python/test/test_wasserstein_distance.py +++ b/src/python/test/test_wasserstein_distance.py @@ -17,7 +17,7 @@ __author__ = "Theo Lacombe" __copyright__ = "Copyright (C) 2019 Inria" __license__ = "MIT" -def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True): +def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_matching=True): diag1 = np.array([[2.7, 3.7], [9.6, 14.0], [34.2, 34.974]]) diag2 = np.array([[2.8, 4.45], [9.5, 14.1]]) diag3 = np.array([[0, 2], [4, 6]]) -- cgit v1.2.3 From 73194242e1c8012c1320a7581a382a3b2b59eb09 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 3 Mar 2020 16:30:22 +0100 Subject: Fix #172 and add a proper comment on the modification --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 76608008..5110819f 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1347,7 +1347,9 @@ class Simplex_tree { }); Filtration_value max_filt_border_value = filtration(*max_border); - if (simplex.second.filtration() < max_filt_border_value) { + // Replacing if(f=max)) would mean that if f is NaN, we replace it with the max of the children. + // That seems more useful than keeping NaN. + if (!(simplex.second.filtration() >= max_filt_border_value)) { // Store the filtration modification information modified = true; simplex.second.assign_filtration(max_filt_border_value); -- cgit v1.2.3 From efae8ff48c6b6e4d29afea753b7a1ddee0925ad4 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 3 Mar 2020 16:44:58 +0100 Subject: handle numpy array, should now adapt the doc --- src/python/gudhi/persistence_graphical_tools.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 8ddfdba8..43776fc6 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -45,6 +45,13 @@ def __min_birth_max_death(persistence, band=0.0): return (min_birth, max_death) +def _array_handler(a): + if isinstance(a, np.ndarray): + return [[0, x] for x in a] + else: + return a + + def plot_persistence_barcode( persistence=[], persistence_file="", @@ -95,6 +102,9 @@ def plot_persistence_barcode( plt.rc('text', usetex=True) plt.rc('font', family='serif') + + persistence = _array_handler(persistence) + if persistence_file != "": if path.isfile(persistence_file): # Reset persistence @@ -237,6 +247,7 @@ def plot_persistence_diagram( plt.rc('text', usetex=True) plt.rc('font', family='serif') + persistence = _array_handler(persistence) if persistence_file != "": if path.isfile(persistence_file): @@ -399,6 +410,7 @@ def plot_persistence_density( plt.rc('text', usetex=True) plt.rc('font', family='serif') + persistence = _array_handler(persistence) if persistence_file != "": if dimension is None: -- cgit v1.2.3 From 4f4030e9f9e0215c2d1f2431c02cd9270bba2699 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 3 Mar 2020 16:57:56 +0100 Subject: updated doc and example handling Nx2 numpy arrays --- src/python/doc/persistence_graphical_tools_sum.inc | 2 +- .../doc/persistence_graphical_tools_user.rst | 12 +++++++++++ src/python/gudhi/persistence_graphical_tools.py | 25 ++++++++++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/python/doc/persistence_graphical_tools_sum.inc b/src/python/doc/persistence_graphical_tools_sum.inc index 2ddaccfc..ef376802 100644 --- a/src/python/doc/persistence_graphical_tools_sum.inc +++ b/src/python/doc/persistence_graphical_tools_sum.inc @@ -2,7 +2,7 @@ :widths: 30 50 20 +-----------------------------------------------------------------+-----------------------------------------------------------------------+-----------------------------------------------+ - | .. figure:: | These graphical tools comes on top of persistence results and allows | :Author: Vincent Rouvreau | + | .. figure:: | These graphical tools comes on top of persistence results and allows | :Author: Vincent Rouvreau, Theo Lacombe | | img/graphical_tools_representation.png | the user to display easily persistence barcode, diagram or density. | | | | | :Introduced in: GUDHI 2.0.0 | | | Note that these functions return the matplotlib axis, allowing | | diff --git a/src/python/doc/persistence_graphical_tools_user.rst b/src/python/doc/persistence_graphical_tools_user.rst index ff51604e..91e52703 100644 --- a/src/python/doc/persistence_graphical_tools_user.rst +++ b/src/python/doc/persistence_graphical_tools_user.rst @@ -57,6 +57,18 @@ This function can display the persistence result as a diagram: ax.set_aspect("equal") # forces to be square shaped plt.show() +Note that (as barcode and density) it can also take a simple `np.array` +of shape (N x 2) encoding a persistence diagram (in a given dimension). + +.. plot:: + :include-source: + + import matplotlib.pyplot as plt + import gudhi + import numpy as np + d = np.array([[0, 1], [1, 2], [1, np.inf]]) + gudhi.plot_persistence_diagram(d) + plt.show() Persistence density ------------------- diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 43776fc6..48e26432 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -15,7 +15,7 @@ import numpy as np from gudhi.reader_utils import read_persistence_intervals_in_dimension from gudhi.reader_utils import read_persistence_intervals_grouped_by_dimension -__author__ = "Vincent Rouvreau, Bertrand Michel" +__author__ = "Vincent Rouvreau, Bertrand Michel, Theo Lacombe" __copyright__ = "Copyright (C) 2016 Inria" __license__ = "MIT" @@ -46,6 +46,11 @@ def __min_birth_max_death(persistence, band=0.0): 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, np.ndarray): return [[0, x] for x in a] else: @@ -65,9 +70,12 @@ def plot_persistence_barcode( fontsize=16, ): """This function plots the persistence bar code from persistence values list + , a np.array of shape (N x 2) (representing a diagram + in a single homology dimension), or from a :doc:`persistence file `. - :param persistence: Persistence intervals values list grouped by dimension. + :param persistence: Persistence intervals values list grouped by dimension, + or np.array of shape (N x 2). :type persistence: list of tuples(dimension, tuple(birth, death)). :param persistence_file: A :doc:`persistence file ` style name (reset persistence if both are set). @@ -206,9 +214,11 @@ def plot_persistence_diagram( greyblock=True ): """This function plots the persistence diagram from persistence values - list or from a :doc:`persistence file `. + list, a np.array of shape (N x 2) representing a diagram in a single + homology dimension, or from a :doc:`persistence file `. - :param persistence: Persistence intervals values list grouped by dimension. + :param persistence: Persistence intervals values list grouped by dimension, + or np.array of shape (N x 2). :type persistence: list of tuples(dimension, tuple(birth, death)). :param persistence_file: A :doc:`persistence file ` style name (reset persistence if both are set). @@ -356,12 +366,15 @@ def plot_persistence_density( greyblock=True ): """This function plots the persistence density from persistence - values list or from a :doc:`persistence file `. Be + values list, np.array of shape (N x 2) representing a diagram + in a single homology dimension, + or from a :doc:`persistence file `. Be aware that this function does not distinguish the dimension, it is up to you to select the required one. This function also does not handle degenerate data set (scipy correlation matrix inversion can fail). - :param persistence: Persistence intervals values list grouped by dimension. + :param persistence: Persistence intervals values list grouped by dimension, + or np.array of shape (N x 2). :type persistence: list of tuples(dimension, tuple(birth, death)). :param persistence_file: A :doc:`persistence file ` style name (reset persistence if both are set). -- cgit v1.2.3 From 5b5e9fce6a80151f29f98dde67f5e4150edb9a5b Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 5 Mar 2020 10:03:26 +0100 Subject: Add some tests and documentation for NaN management in make_filtration_non_decreasing Simplex tree method --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 2 + src/Simplex_tree/test/CMakeLists.txt | 6 + ...ee_make_filtration_non_decreasing_unit_test.cpp | 148 +++++++++++++++++++++ src/Simplex_tree/test/simplex_tree_unit_test.cpp | 84 ------------ 4 files changed, 156 insertions(+), 84 deletions(-) create mode 100644 src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 5110819f..7b39a500 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1317,6 +1317,8 @@ class Simplex_tree { * \post Some simplex tree functions require the filtration to be valid. `make_filtration_non_decreasing()` * function is not launching `initialize_filtration()` but returns the filtration modification information. If the * complex has changed , please call `initialize_filtration()` to recompute it. + * + * If a simplex has a `NaN` filtration value, it is considered lower than any other defined filtration value. */ bool make_filtration_non_decreasing() { bool modified = false; diff --git a/src/Simplex_tree/test/CMakeLists.txt b/src/Simplex_tree/test/CMakeLists.txt index 8b9163f5..cf2b0153 100644 --- a/src/Simplex_tree/test/CMakeLists.txt +++ b/src/Simplex_tree/test/CMakeLists.txt @@ -28,3 +28,9 @@ if (TBB_FOUND) target_link_libraries(Simplex_tree_ctor_and_move_test_unit ${TBB_LIBRARIES}) endif() gudhi_add_boost_test(Simplex_tree_ctor_and_move_test_unit) + +add_executable ( Simplex_tree_make_filtration_non_decreasing_test_unit simplex_tree_make_filtration_non_decreasing_unit_test.cpp ) +if (TBB_FOUND) + target_link_libraries(Simplex_tree_make_filtration_non_decreasing_test_unit ${TBB_LIBRARIES}) +endif() +gudhi_add_boost_test(Simplex_tree_make_filtration_non_decreasing_test_unit) diff --git a/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp new file mode 100644 index 00000000..a8130e25 --- /dev/null +++ b/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp @@ -0,0 +1,148 @@ +/* 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) 2020 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#include +#include // for NaN +#include // for isNaN + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "simplex_tree_make_filtration_non_decreasing" +#include +#include + +// ^ +// /!\ Nothing else from Simplex_tree shall be included to test includes are well defined. +#include "gudhi/Simplex_tree.h" + +using namespace Gudhi; + +typedef boost::mpl::list, Simplex_tree> list_of_tested_variants; + +BOOST_AUTO_TEST_CASE_TEMPLATE(make_filtration_non_decreasing, typeST, list_of_tested_variants) { + typeST st; + + st.insert_simplex_and_subfaces({2, 1, 0}, 2.0); + st.insert_simplex_and_subfaces({3, 0}, 2.0); + st.insert_simplex_and_subfaces({3, 4, 5}, 2.0); + + /* Inserted simplex: */ + /* 1 */ + /* o */ + /* /X\ */ + /* o---o---o---o */ + /* 2 0 3\X/4 */ + /* o */ + /* 5 */ + + std::cout << "Check default insertion ensures the filtration values are non decreasing" << std::endl; + BOOST_CHECK(!st.make_filtration_non_decreasing()); + + // Because of non decreasing property of simplex tree, { 0 } , { 1 } and { 0, 1 } are going to be set from value 2.0 + // to 1.0 + st.insert_simplex_and_subfaces({0, 1, 6, 7}, 1.0); + + // Inserted simplex: + // 1 6 + // o---o + // /X\7/ + // o---o---o---o + // 2 0 3\X/4 + // o + // 5 + + std::cout << "Check default second insertion ensures the filtration values are non decreasing" << std::endl; + BOOST_CHECK(!st.make_filtration_non_decreasing()); + + // Copy original simplex tree + typeST st_copy = st; + + // Modify specific values for st to become like st_copy thanks to make_filtration_non_decreasing + st.assign_filtration(st.find({0,1,6,7}), 0.8); + st.assign_filtration(st.find({0,1,6}), 0.9); + st.assign_filtration(st.find({0,6}), 0.6); + st.assign_filtration(st.find({3,4,5}), 1.2); + st.assign_filtration(st.find({3,4}), 1.1); + st.assign_filtration(st.find({4,5}), 1.99); + + std::cout << "Check the simplex_tree is rolled back in case of decreasing filtration values" << std::endl; + BOOST_CHECK(st.make_filtration_non_decreasing()); + BOOST_CHECK(st == st_copy); + + // Other simplex tree + typeST st_other; + st_other.insert_simplex_and_subfaces({2, 1, 0}, 3.0); // This one is different from st + st_other.insert_simplex_and_subfaces({3, 0}, 2.0); + st_other.insert_simplex_and_subfaces({3, 4, 5}, 2.0); + st_other.insert_simplex_and_subfaces({0, 1, 6, 7}, 1.0); + + // Modify specific values for st to become like st_other thanks to make_filtration_non_decreasing + st.assign_filtration(st.find({2}), 3.0); + // By modifying just the simplex {2} + // {0,1,2}, {1,2} and {0,2} will be modified + + std::cout << "Check the simplex_tree is repaired in case of decreasing filtration values" << std::endl; + BOOST_CHECK(st.make_filtration_non_decreasing()); + BOOST_CHECK(st == st_other); + + // Modify specific values for st still to be non-decreasing + st.assign_filtration(st.find({0,1,2}), 10.0); + st.assign_filtration(st.find({0,2}), 9.0); + st.assign_filtration(st.find({0,1,6,7}), 50.0); + st.assign_filtration(st.find({0,1,6}), 49.0); + st.assign_filtration(st.find({0,1,7}), 48.0); + // Other copy simplex tree + typeST st_other_copy = st; + + std::cout << "Check the simplex_tree is not modified in case of non-decreasing filtration values" << std::endl; + BOOST_CHECK(!st.make_filtration_non_decreasing()); + BOOST_CHECK(st == st_other_copy); + +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(make_filtration_non_decreasing_on_nan_values, typeST, list_of_tested_variants) { + typeST st; + + st.insert_simplex_and_subfaces({2, 1, 0}, std::numeric_limits::quiet_NaN()); + st.insert_simplex_and_subfaces({3, 0}, std::numeric_limits::quiet_NaN()); + st.insert_simplex_and_subfaces({3, 4, 5}, std::numeric_limits::quiet_NaN()); + + /* Inserted simplex: */ + /* 1 */ + /* o */ + /* /X\ */ + /* o---o---o---o */ + /* 2 0 3\X/4 */ + /* o */ + /* 5 */ + + std::cout << "SPECIFIC CASE:" << std::endl; + std::cout << "Insertion with NaN values does not ensure the filtration values are non decreasing" << std::endl; + st.make_filtration_non_decreasing(); + + std::cout << "Check all filtration values are NaN" << std::endl; + for (auto f_simplex : st.filtration_simplex_range()) { + BOOST_CHECK(std::isnan(st.filtration(f_simplex))); + } + + st.assign_filtration(st.find({0}), 0.); + st.assign_filtration(st.find({1}), 0.); + st.assign_filtration(st.find({2}), 0.); + st.assign_filtration(st.find({3}), 0.); + st.assign_filtration(st.find({4}), 0.); + st.assign_filtration(st.find({5}), 0.); + + std::cout << "Check make_filtration_non_decreasing is modifying the simplicial complex" << std::endl; + BOOST_CHECK(st.make_filtration_non_decreasing()); + + std::cout << "Check all filtration values are now defined" << std::endl; + for (auto f_simplex : st.filtration_simplex_range()) { + BOOST_CHECK(!std::isnan(st.filtration(f_simplex))); + } +} \ No newline at end of file diff --git a/src/Simplex_tree/test/simplex_tree_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_unit_test.cpp index 58bfa8db..e739ad0a 100644 --- a/src/Simplex_tree/test/simplex_tree_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_unit_test.cpp @@ -791,90 +791,6 @@ BOOST_AUTO_TEST_CASE(non_contiguous) { BOOST_CHECK(++i == std::end(b)); } -BOOST_AUTO_TEST_CASE(make_filtration_non_decreasing) { - std::cout << "********************************************************************" << std::endl; - std::cout << "MAKE FILTRATION NON DECREASING" << std::endl; - typedef Simplex_tree<> typeST; - typeST st; - - st.insert_simplex_and_subfaces({2, 1, 0}, 2.0); - st.insert_simplex_and_subfaces({3, 0}, 2.0); - st.insert_simplex_and_subfaces({3, 4, 5}, 2.0); - - /* Inserted simplex: */ - /* 1 */ - /* o */ - /* /X\ */ - /* o---o---o---o */ - /* 2 0 3\X/4 */ - /* o */ - /* 5 */ - - std::cout << "Check default insertion ensures the filtration values are non decreasing" << std::endl; - BOOST_CHECK(!st.make_filtration_non_decreasing()); - - // Because of non decreasing property of simplex tree, { 0 } , { 1 } and { 0, 1 } are going to be set from value 2.0 - // to 1.0 - st.insert_simplex_and_subfaces({0, 1, 6, 7}, 1.0); - - // Inserted simplex: - // 1 6 - // o---o - // /X\7/ - // o---o---o---o - // 2 0 3\X/4 - // o - // 5 - - std::cout << "Check default second insertion ensures the filtration values are non decreasing" << std::endl; - BOOST_CHECK(!st.make_filtration_non_decreasing()); - - // Copy original simplex tree - typeST st_copy = st; - - // Modify specific values for st to become like st_copy thanks to make_filtration_non_decreasing - st.assign_filtration(st.find({0,1,6,7}), 0.8); - st.assign_filtration(st.find({0,1,6}), 0.9); - st.assign_filtration(st.find({0,6}), 0.6); - st.assign_filtration(st.find({3,4,5}), 1.2); - st.assign_filtration(st.find({3,4}), 1.1); - st.assign_filtration(st.find({4,5}), 1.99); - - std::cout << "Check the simplex_tree is rolled back in case of decreasing filtration values" << std::endl; - BOOST_CHECK(st.make_filtration_non_decreasing()); - BOOST_CHECK(st == st_copy); - - // Other simplex tree - typeST st_other; - st_other.insert_simplex_and_subfaces({2, 1, 0}, 3.0); // This one is different from st - st_other.insert_simplex_and_subfaces({3, 0}, 2.0); - st_other.insert_simplex_and_subfaces({3, 4, 5}, 2.0); - st_other.insert_simplex_and_subfaces({0, 1, 6, 7}, 1.0); - - // Modify specific values for st to become like st_other thanks to make_filtration_non_decreasing - st.assign_filtration(st.find({2}), 3.0); - // By modifying just the simplex {2} - // {0,1,2}, {1,2} and {0,2} will be modified - - std::cout << "Check the simplex_tree is repaired in case of decreasing filtration values" << std::endl; - BOOST_CHECK(st.make_filtration_non_decreasing()); - BOOST_CHECK(st == st_other); - - // Modify specific values for st still to be non-decreasing - st.assign_filtration(st.find({0,1,2}), 10.0); - st.assign_filtration(st.find({0,2}), 9.0); - st.assign_filtration(st.find({0,1,6,7}), 50.0); - st.assign_filtration(st.find({0,1,6}), 49.0); - st.assign_filtration(st.find({0,1,7}), 48.0); - // Other copy simplex tree - typeST st_other_copy = st; - - std::cout << "Check the simplex_tree is not modified in case of non-decreasing filtration values" << std::endl; - BOOST_CHECK(!st.make_filtration_non_decreasing()); - BOOST_CHECK(st == st_other_copy); - -} - typedef boost::mpl::list, -- cgit v1.2.3 From e1dbf6da118615e20d2b642df98b7d3df7cfd8c7 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 5 Mar 2020 10:16:51 +0100 Subject: Remove travis and use appveyor for OSx. Fix parallel test by setting tests dependencies --- .github/build-requirements.txt | 5 ++ .github/test-requirements.txt | 8 +++ .travis.yml | 78 ---------------------- Dockerfile_for_circleci_image | 22 ++---- azure-pipelines.yml | 44 ++++++++++++ src/Alpha_complex/example/CMakeLists.txt | 6 +- src/Alpha_complex/utilities/CMakeLists.txt | 12 +++- .../utilities/CMakeLists.txt | 10 ++- .../utilities/persistence_heat_maps/CMakeLists.txt | 19 ++++-- .../utilities/persistence_intervals/CMakeLists.txt | 7 +- .../persistence_landscapes/CMakeLists.txt | 8 +-- .../persistence_landscapes_on_grid/CMakeLists.txt | 8 +-- .../utilities/persistence_vectors/CMakeLists.txt | 8 +-- src/Rips_complex/example/CMakeLists.txt | 8 +++ src/common/example/CMakeLists.txt | 1 + src/python/CMakeLists.txt | 2 +- 16 files changed, 128 insertions(+), 118 deletions(-) create mode 100644 .github/build-requirements.txt create mode 100644 .github/test-requirements.txt delete mode 100644 .travis.yml create mode 100644 azure-pipelines.yml diff --git a/.github/build-requirements.txt b/.github/build-requirements.txt new file mode 100644 index 00000000..7de60d23 --- /dev/null +++ b/.github/build-requirements.txt @@ -0,0 +1,5 @@ +setuptools +wheel +numpy +Cython +pybind11 \ No newline at end of file diff --git a/.github/test-requirements.txt b/.github/test-requirements.txt new file mode 100644 index 00000000..bd03f98e --- /dev/null +++ b/.github/test-requirements.txt @@ -0,0 +1,8 @@ +pytest +sphinx +sphinxcontrib-bibtex +sphinx-paramlinks +matplotlib +scipy +scikit-learn +POT \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8980be10..00000000 --- a/.travis.yml +++ /dev/null @@ -1,78 +0,0 @@ -language: cpp - -sudo: required - -git: - depth: 3 - -os: osx -osx_image: xcode10.2 -compiler: clang - -matrix: - include: - - env: - # 1. Only examples and associated tests - - CMAKE_EXAMPLE='ON' CMAKE_TEST='OFF' CMAKE_UTILITIES='OFF' CMAKE_PYTHON='OFF' MAKE_TARGET='all' CTEST_COMMAND='ctest --output-on-failure' - - env: - # 2. Only unitary tests - - CMAKE_EXAMPLE='OFF' CMAKE_TEST='ON' CMAKE_UTILITIES='OFF' CMAKE_PYTHON='OFF' MAKE_TARGET='all' CTEST_COMMAND='ctest --output-on-failure' - - env: - # 3. Only utilities and associated tests - - CMAKE_EXAMPLE='OFF' CMAKE_TEST='OFF' CMAKE_UTILITIES='ON' CMAKE_PYTHON='OFF' MAKE_TARGET='all' CTEST_COMMAND='ctest --output-on-failure' - - env: - # 4. Only doxygen documentation - - CMAKE_EXAMPLE='OFF' CMAKE_TEST='OFF' CMAKE_UTILITIES='OFF' CMAKE_PYTHON='OFF' MAKE_TARGET='doxygen' CTEST_COMMAND='echo No tests for doxygen target' - - env: - # 5. Only Python, associated tests and sphinx documentation - # $ which python3 => /usr/local/bin/python3 - # cmake => -- Found PythonInterp: /usr/local/bin/python3 (found version "3.7.5") - # In python3-sphinx-build.py, print(sys.executable) => /usr/local/opt/python/bin/python3.7 ??? - # should be : MAKE_TARGET='all sphinx' CTEST_COMMAND='ctest --output-on-failure' - - CMAKE_EXAMPLE='OFF' CMAKE_TEST='OFF' CMAKE_UTILITIES='OFF' CMAKE_PYTHON='ON' MAKE_TARGET='all' CTEST_COMMAND='ctest --output-on-failure -E sphinx' - -cache: - directories: - - $HOME/.cache/pip - - $HOME/Library/Caches/Homebrew - -before_install: - - brew update && brew unlink python@2 && brew upgrade python - -addons: - homebrew: - packages: - - python3 - - cmake - - graphviz - - doxygen - - boost - - eigen - - gmp - - mpfr - - tbb - - cgal - -before_cache: - - rm -f $HOME/.cache/pip/log/debug.log - - brew cleanup - -# When installing through libcgal-dev apt, CMake Error at CGAL Exports.cmake The imported target "CGAL::CGAL Qt5" references the file -install: - - python3 -m pip install --upgrade pip setuptools wheel - - python3 -m pip install --user pytest Cython sphinx sphinxcontrib-bibtex sphinx-paramlinks matplotlib numpy scipy scikit-learn - - python3 -m pip install --user POT pybind11 - -script: - - rm -rf build - - mkdir -p build - - cd build - - cmake -DCMAKE_BUILD_TYPE=Release -DWITH_GUDHI_EXAMPLE=${CMAKE_EXAMPLE} -DWITH_GUDHI_TEST=${CMAKE_TEST} -DWITH_GUDHI_UTILITIES=${CMAKE_UTILITIES} -DWITH_GUDHI_PYTHON=${CMAKE_PYTHON} -DUSER_VERSION_DIR=version -DPython_ADDITIONAL_VERSIONS=3 .. - - make ${MAKE_TARGET} - - ${CTEST_COMMAND} - - cd .. - -notifications: - email: - on_success: change # default: always - on_failure: always # default: always diff --git a/Dockerfile_for_circleci_image b/Dockerfile_for_circleci_image index ebd2f366..cca93f0c 100644 --- a/Dockerfile_for_circleci_image +++ b/Dockerfile_for_circleci_image @@ -42,24 +42,16 @@ RUN apt-get install -y make \ locales \ python3 \ python3-pip \ - python3-pytest \ python3-tk \ - python3-pybind11 \ libfreetype6-dev \ - pkg-config + pkg-config \ + curl -RUN pip3 install \ - setuptools \ - numpy \ - matplotlib \ - scipy \ - Cython \ - POT \ - scikit-learn \ - sphinx \ - sphinx-paramlinks \ - sphinxcontrib-bibtex \ - tensorflow +ADD .github/build-requirements.txt / +ADD .github/test-requirements.txt / + +RUN pip3 install -r build-requirements.txt +RUN pip3 install -r test-requirements.txt # apt clean up RUN apt autoremove && rm -rf /var/lib/apt/lists/* diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..77e0ac88 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,44 @@ +trigger: + batch: true + branches: + include: + - '*' # All branches + +jobs: + + - job: 'Test' + displayName: "Build and test" + timeoutInMinutes: 0 + cancelTimeoutInMinutes: 60 + + strategy: + matrix: + macOSrelease: + imageName: 'macos-10.14' + CMakeBuildType: Release + customInstallation: 'brew update && brew install graphviz doxygen boost eigen gmp mpfr tbb cgal' + + pool: + vmImage: $(imageName) + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.7' + architecture: 'x64' + + - script: | + $(customInstallation) + git submodule update --init + python -m pip install --upgrade pip + python -m pip install --user -r .github/build-requirements.txt + python -m pip install --user -r .github/test-requirements.txt + displayName: 'Install build dependencies' + - script: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE:STRING=$(CMakeBuildType) -DWITH_GUDHI_TEST=ON -DWITH_GUDHI_UTILITIES=ON -DWITH_GUDHI_PYTHON=ON -DPython_ADDITIONAL_VERSIONS=3 .. + make + make doxygen + ctest -j 8 --output-on-failure -E sphinx # remove sphinx build as it fails + displayName: 'Build, test and documentation generation' diff --git a/src/Alpha_complex/example/CMakeLists.txt b/src/Alpha_complex/example/CMakeLists.txt index b0337934..2eecd50c 100644 --- a/src/Alpha_complex/example/CMakeLists.txt +++ b/src/Alpha_complex/example/CMakeLists.txt @@ -32,14 +32,18 @@ if (DIFF_PATH) add_test(Alpha_complex_example_from_off_60_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/alphaoffreader_result_60.txt ${CMAKE_CURRENT_BINARY_DIR}/alphaoffreader_for_doc_60.txt) + set_tests_properties(Alpha_complex_example_from_off_60_diff_files PROPERTIES DEPENDS Alpha_complex_example_from_off_60) add_test(Alpha_complex_example_from_off_32_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/alphaoffreader_result_32.txt ${CMAKE_CURRENT_BINARY_DIR}/alphaoffreader_for_doc_32.txt) + set_tests_properties(Alpha_complex_example_from_off_32_diff_files PROPERTIES DEPENDS Alpha_complex_example_from_off_32) add_test(Alpha_complex_example_fast_from_off_60_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/fastalphaoffreader_result_60.txt ${CMAKE_CURRENT_BINARY_DIR}/alphaoffreader_for_doc_60.txt) + set_tests_properties(Alpha_complex_example_fast_from_off_60_diff_files PROPERTIES DEPENDS Alpha_complex_example_fast_from_off_60) add_test(Alpha_complex_example_fast_from_off_32_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/fastalphaoffreader_result_32.txt ${CMAKE_CURRENT_BINARY_DIR}/alphaoffreader_for_doc_32.txt) - endif() + set_tests_properties(Alpha_complex_example_fast_from_off_32_diff_files PROPERTIES DEPENDS Alpha_complex_example_fast_from_off_32) +endif() add_executable ( Alpha_complex_example_weighted_3d_from_points Weighted_alpha_complex_3d_from_points.cpp ) target_link_libraries(Alpha_complex_example_weighted_3d_from_points ${CGAL_LIBRARY}) diff --git a/src/Alpha_complex/utilities/CMakeLists.txt b/src/Alpha_complex/utilities/CMakeLists.txt index a3b0cc24..2ffbdde0 100644 --- a/src/Alpha_complex/utilities/CMakeLists.txt +++ b/src/Alpha_complex/utilities/CMakeLists.txt @@ -16,8 +16,14 @@ if (NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) if (DIFF_PATH) add_test(Alpha_complex_utilities_diff_exact_alpha_complex ${DIFF_PATH} "exact.pers" "safe.pers") + set_tests_properties(Alpha_complex_utilities_diff_exact_alpha_complex PROPERTIES DEPENDS + "Alpha_complex_utilities_exact_alpha_complex_persistence;Alpha_complex_utilities_safe_alpha_complex_persistence") + add_test(Alpha_complex_utilities_diff_fast_alpha_complex ${DIFF_PATH} "fast.pers" "safe.pers") + set_tests_properties(Alpha_complex_utilities_diff_fast_alpha_complex PROPERTIES DEPENDS + "Alpha_complex_utilities_fast_alpha_complex_persistence;Alpha_complex_utilities_safe_alpha_complex_persistence") + endif() install(TARGETS alpha_complex_persistence DESTINATION bin) @@ -36,15 +42,19 @@ if (NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) "${CMAKE_SOURCE_DIR}/data/points/tore3D_300.off" "-p" "2" "-m" "0.45" "-o" "exact_3d.pers" "-e") - add_test(NAME Alpha_complex_utilities_safe_alpha_complex_3d COMMAND $ + add_test(NAME Alpha_complex_utilities_fast_alpha_complex_3d COMMAND $ "${CMAKE_SOURCE_DIR}/data/points/tore3D_300.off" "-p" "2" "-m" "0.45" "-o" "fast_3d.pers" "-f") if (DIFF_PATH) add_test(Alpha_complex_utilities_diff_exact_alpha_complex_3d ${DIFF_PATH} "exact_3d.pers" "safe_3d.pers") + set_tests_properties(Alpha_complex_utilities_diff_exact_alpha_complex_3d PROPERTIES DEPENDS + "Alpha_complex_utilities_exact_alpha_complex_3d;Alpha_complex_utilities_alpha_complex_3d") add_test(Alpha_complex_utilities_diff_fast_alpha_complex_3d ${DIFF_PATH} "fast_3d.pers" "safe_3d.pers") + set_tests_properties(Alpha_complex_utilities_diff_fast_alpha_complex_3d PROPERTIES DEPENDS + "Alpha_complex_utilities_fast_alpha_complex_3d;Alpha_complex_utilities_alpha_complex_3d") endif() add_test(NAME Alpha_complex_utilities_periodic_alpha_complex_3d_persistence COMMAND $ diff --git a/src/Persistence_representations/utilities/CMakeLists.txt b/src/Persistence_representations/utilities/CMakeLists.txt index fc51b1d6..85633b7b 100644 --- a/src/Persistence_representations/utilities/CMakeLists.txt +++ b/src/Persistence_representations/utilities/CMakeLists.txt @@ -14,7 +14,7 @@ function(add_persistence_representation_creation_utility creation_utility) install(TARGETS ${creation_utility} DESTINATION bin) endfunction(add_persistence_representation_creation_utility) -function(add_persistence_representation_plot_utility plot_utility tool_extension) +function(add_persistence_representation_plot_utility creation_utility plot_utility tool_extension) add_executable ( ${plot_utility} ${plot_utility}.cpp ) # as the function is called in a subdirectory level, need to '../' to find persistence heat maps files @@ -22,17 +22,21 @@ function(add_persistence_representation_plot_utility plot_utility tool_extension "${CMAKE_CURRENT_BINARY_DIR}/../first.pers${tool_extension}") #add_test(NAME Persistence_representation_utilities_${plot_utility}_second COMMAND $ # "${CMAKE_CURRENT_BINARY_DIR}/../second.pers${tool_extension}") + set_tests_properties(Persistence_representation_utilities_${plot_utility}_first PROPERTIES DEPENDS + Persistence_representation_utilities_${creation_utility}) if(GNUPLOT_PATH) add_test(NAME Persistence_representation_utilities_${plot_utility}_first_gnuplot COMMAND ${GNUPLOT_PATH} "-e" "load '${CMAKE_CURRENT_BINARY_DIR}/../first.pers${tool_extension}_GnuplotScript'") #add_test(NAME Persistence_representation_utilities_${plot_utility}_second_gnuplot COMMAND ${GNUPLOT_PATH} # "-e" "load '${CMAKE_CURRENT_BINARY_DIR}/../second.pers${tool_extension}_GnuplotScript'") + set_tests_properties(Persistence_representation_utilities_${plot_utility}_first_gnuplot PROPERTIES DEPENDS + Persistence_representation_utilities_${plot_utility}_first) endif() install(TARGETS ${plot_utility} DESTINATION bin) endfunction(add_persistence_representation_plot_utility) -function(add_persistence_representation_function_utility function_utility tool_extension) +function(add_persistence_representation_function_utility creation_utility function_utility tool_extension) add_executable ( ${function_utility} ${function_utility}.cpp ) # ARGV2 is an optional argument @@ -48,6 +52,8 @@ function(add_persistence_representation_function_utility function_utility tool_e "${CMAKE_CURRENT_BINARY_DIR}/../first.pers${tool_extension}" "${CMAKE_CURRENT_BINARY_DIR}/../second.pers${tool_extension}") endif() + set_tests_properties(Persistence_representation_utilities_${function_utility} PROPERTIES DEPENDS + Persistence_representation_utilities_${creation_utility}) install(TARGETS ${function_utility} DESTINATION bin) endfunction(add_persistence_representation_function_utility) diff --git a/src/Persistence_representations/utilities/persistence_heat_maps/CMakeLists.txt b/src/Persistence_representations/utilities/persistence_heat_maps/CMakeLists.txt index 89ef232f..e4c471c2 100644 --- a/src/Persistence_representations/utilities/persistence_heat_maps/CMakeLists.txt +++ b/src/Persistence_representations/utilities/persistence_heat_maps/CMakeLists.txt @@ -2,13 +2,24 @@ project(Persistence_representations_heat_maps_utilities) add_persistence_representation_creation_utility(create_pssk "10" "-1" "-1" "4" "-1") add_persistence_representation_creation_utility(create_p_h_m_weighted_by_arctan_of_their_persistence "10" "-1" "-1" "4" "-1") + add_persistence_representation_creation_utility(create_p_h_m_weighted_by_distance_from_diagonal "10" "-1" "-1" "4" "-1") +# Tests output the same file +set_tests_properties(Persistence_representation_utilities_create_p_h_m_weighted_by_distance_from_diagonal PROPERTIES DEPENDS + Persistence_representation_utilities_create_p_h_m_weighted_by_arctan_of_their_persistence) + add_persistence_representation_creation_utility(create_p_h_m_weighted_by_squared_diag_distance "10" "-1" "-1" "4" "-1") +# Tests output the same file +set_tests_properties(Persistence_representation_utilities_create_p_h_m_weighted_by_squared_diag_distance PROPERTIES DEPENDS + Persistence_representation_utilities_create_p_h_m_weighted_by_distance_from_diagonal) + # Need to set grid min and max for further average, distance and scalar_product add_persistence_representation_creation_utility(create_persistence_heat_maps "10" "0" "35" "10" "-1") +set_tests_properties(Persistence_representation_utilities_create_persistence_heat_maps PROPERTIES DEPENDS + Persistence_representation_utilities_create_p_h_m_weighted_by_squared_diag_distance) -add_persistence_representation_plot_utility(plot_persistence_heat_map ".mps") +add_persistence_representation_plot_utility(create_persistence_heat_maps plot_persistence_heat_map ".mps") -add_persistence_representation_function_utility(average_persistence_heat_maps ".mps") -add_persistence_representation_function_utility(compute_distance_of_persistence_heat_maps ".mps" "1") -add_persistence_representation_function_utility(compute_scalar_product_of_persistence_heat_maps ".mps") +add_persistence_representation_function_utility(create_persistence_heat_maps average_persistence_heat_maps ".mps") +add_persistence_representation_function_utility(create_persistence_heat_maps compute_distance_of_persistence_heat_maps ".mps" "1") +add_persistence_representation_function_utility(create_persistence_heat_maps compute_scalar_product_of_persistence_heat_maps ".mps") diff --git a/src/Persistence_representations/utilities/persistence_intervals/CMakeLists.txt b/src/Persistence_representations/utilities/persistence_intervals/CMakeLists.txt index a025183e..118c1e9b 100644 --- a/src/Persistence_representations/utilities/persistence_intervals/CMakeLists.txt +++ b/src/Persistence_representations/utilities/persistence_intervals/CMakeLists.txt @@ -3,17 +3,16 @@ project(Persistence_representations_intervals_utilities) add_executable ( plot_histogram_of_intervals_lengths plot_histogram_of_intervals_lengths.cpp ) -add_test(NAME plot_histogram_of_intervals_lengths COMMAND $ +add_test(NAME Persistence_representation_utilities_plot_histogram_of_intervals_lengths COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../first.pers" "-1") install(TARGETS plot_histogram_of_intervals_lengths DESTINATION bin) -add_persistence_representation_plot_utility(plot_persistence_intervals "") -add_persistence_representation_plot_utility(plot_persistence_Betti_numbers "") +add_persistence_representation_plot_utility(plot_histogram_of_intervals_lengths plot_persistence_intervals "") +add_persistence_representation_plot_utility(plot_histogram_of_intervals_lengths plot_persistence_Betti_numbers "") add_persistence_representation_creation_utility(compute_birth_death_range_in_persistence_diagram "-1") - add_executable ( compute_number_of_dominant_intervals compute_number_of_dominant_intervals.cpp ) add_test(NAME Persistence_representation_utilities_compute_number_of_dominant_intervals COMMAND $ diff --git a/src/Persistence_representations/utilities/persistence_landscapes/CMakeLists.txt b/src/Persistence_representations/utilities/persistence_landscapes/CMakeLists.txt index 6b24d032..4df84d36 100644 --- a/src/Persistence_representations/utilities/persistence_landscapes/CMakeLists.txt +++ b/src/Persistence_representations/utilities/persistence_landscapes/CMakeLists.txt @@ -2,8 +2,8 @@ project(Persistence_representations_landscapes_utilities) add_persistence_representation_creation_utility(create_landscapes "-1") -add_persistence_representation_plot_utility(plot_landscapes ".land") +add_persistence_representation_plot_utility(create_landscapes plot_landscapes ".land") -add_persistence_representation_function_utility(average_landscapes ".land") -add_persistence_representation_function_utility(compute_distance_of_landscapes ".land" "1") -add_persistence_representation_function_utility(compute_scalar_product_of_landscapes ".land") +add_persistence_representation_function_utility(create_landscapes average_landscapes ".land") +add_persistence_representation_function_utility(create_landscapes compute_distance_of_landscapes ".land" "1") +add_persistence_representation_function_utility(create_landscapes compute_scalar_product_of_landscapes ".land") diff --git a/src/Persistence_representations/utilities/persistence_landscapes_on_grid/CMakeLists.txt b/src/Persistence_representations/utilities/persistence_landscapes_on_grid/CMakeLists.txt index 36f3196b..8cd965f1 100644 --- a/src/Persistence_representations/utilities/persistence_landscapes_on_grid/CMakeLists.txt +++ b/src/Persistence_representations/utilities/persistence_landscapes_on_grid/CMakeLists.txt @@ -3,8 +3,8 @@ project(Persistence_representations_lanscapes_on_grid_utilities) # Need to set grid min and max for further average, distance and scalar_product add_persistence_representation_creation_utility(create_landscapes_on_grid "100" "0" "35" "-1") -add_persistence_representation_plot_utility(plot_landscapes_on_grid ".g_land") +add_persistence_representation_plot_utility(create_landscapes_on_grid plot_landscapes_on_grid ".g_land") -add_persistence_representation_function_utility(average_landscapes_on_grid ".g_land") -add_persistence_representation_function_utility(compute_distance_of_landscapes_on_grid ".g_land" "1") -add_persistence_representation_function_utility(compute_scalar_product_of_landscapes_on_grid ".g_land") +add_persistence_representation_function_utility(create_landscapes_on_grid average_landscapes_on_grid ".g_land") +add_persistence_representation_function_utility(create_landscapes_on_grid compute_distance_of_landscapes_on_grid ".g_land" "1") +add_persistence_representation_function_utility(create_landscapes_on_grid compute_scalar_product_of_landscapes_on_grid ".g_land") diff --git a/src/Persistence_representations/utilities/persistence_vectors/CMakeLists.txt b/src/Persistence_representations/utilities/persistence_vectors/CMakeLists.txt index bc982094..5b22ca84 100644 --- a/src/Persistence_representations/utilities/persistence_vectors/CMakeLists.txt +++ b/src/Persistence_representations/utilities/persistence_vectors/CMakeLists.txt @@ -2,8 +2,8 @@ project(Persistence_vectors_utilities) add_persistence_representation_creation_utility(create_persistence_vectors "-1") -add_persistence_representation_plot_utility(plot_persistence_vectors ".vect") +add_persistence_representation_plot_utility(create_persistence_vectors plot_persistence_vectors ".vect") -add_persistence_representation_function_utility(average_persistence_vectors ".vect") -add_persistence_representation_function_utility(compute_distance_of_persistence_vectors ".vect" "1") -add_persistence_representation_function_utility(compute_scalar_product_of_persistence_vectors ".vect") +add_persistence_representation_function_utility(create_persistence_vectors average_persistence_vectors ".vect") +add_persistence_representation_function_utility(create_persistence_vectors compute_distance_of_persistence_vectors ".vect" "1") +add_persistence_representation_function_utility(create_persistence_vectors compute_scalar_product_of_persistence_vectors ".vect") diff --git a/src/Rips_complex/example/CMakeLists.txt b/src/Rips_complex/example/CMakeLists.txt index e7772bdb..244a93ec 100644 --- a/src/Rips_complex/example/CMakeLists.txt +++ b/src/Rips_complex/example/CMakeLists.txt @@ -53,15 +53,23 @@ if (DIFF_PATH) add_test(Rips_complex_example_from_off_doc_12_1_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_1.txt ${CMAKE_CURRENT_BINARY_DIR}/one_skeleton_rips_for_doc.txt) + set_tests_properties(Rips_complex_example_from_off_doc_12_1_diff_files PROPERTIES DEPENDS Rips_complex_example_from_off_doc_12_1) + add_test(Rips_complex_example_from_off_doc_12_3_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_3.txt ${CMAKE_CURRENT_BINARY_DIR}/full_skeleton_rips_for_doc.txt) + set_tests_properties(Rips_complex_example_from_off_doc_12_3_diff_files PROPERTIES DEPENDS Rips_complex_example_from_off_doc_12_3) + add_test(Rips_complex_example_from_csv_distance_matrix_doc_12_1_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/ripscsvreader_result_12_1.txt ${CMAKE_CURRENT_BINARY_DIR}/one_skeleton_rips_for_doc.txt) + set_tests_properties(Rips_complex_example_from_csv_distance_matrix_doc_12_1_diff_files PROPERTIES DEPENDS Rips_complex_example_from_csv_distance_matrix_doc_12_1) + add_test(Rips_complex_example_from_csv_distance_matrix_doc_12_3_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/ripscsvreader_result_12_3.txt ${CMAKE_CURRENT_BINARY_DIR}/full_skeleton_rips_for_doc.txt) + set_tests_properties(Rips_complex_example_from_csv_distance_matrix_doc_12_3_diff_files PROPERTIES DEPENDS Rips_complex_example_from_csv_distance_matrix_doc_12_3) + endif() install(TARGETS Rips_complex_example_from_off DESTINATION bin) diff --git a/src/common/example/CMakeLists.txt b/src/common/example/CMakeLists.txt index 583a0027..fa8eb98c 100644 --- a/src/common/example/CMakeLists.txt +++ b/src/common/example/CMakeLists.txt @@ -12,6 +12,7 @@ if (DIFF_PATH) add_test(Common_example_vector_double_off_reader_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/vectordoubleoffreader_result.txt ${CMAKE_CURRENT_BINARY_DIR}/alphacomplexdoc.off.txt) + set_tests_properties(Common_example_vector_double_off_reader_diff_files PROPERTIES DEPENDS Common_example_vector_double_off_reader) endif() if(NOT CGAL_VERSION VERSION_LESS 4.11.0) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 090a7446..20e72a5f 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -181,7 +181,7 @@ if(PYTHONINTERP_FOUND) set(GUDHI_PYTHON_LIBRARY_DIRS "${GUDHI_PYTHON_LIBRARY_DIRS}'${MPFR_LIBRARIES_DIR}', ") message("** Add mpfr ${MPFR_LIBRARIES}") endif(MPFR_FOUND) -endif(CGAL_FOUND) + endif(CGAL_FOUND) # Specific for Mac if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") -- cgit v1.2.3 From a9d2eae3139d291a3d66e71174b22a851bd797ba Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 5 Mar 2020 10:36:19 +0100 Subject: remove ext (submodule directory) from doxygen parsing --- src/Doxyfile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Doxyfile.in b/src/Doxyfile.in index ec551882..49e781bd 100644 --- a/src/Doxyfile.in +++ b/src/Doxyfile.in @@ -785,6 +785,7 @@ EXCLUDE = data/ \ GudhUI/ \ cmake/ \ python/ \ + ext/ \ README.md # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -- cgit v1.2.3 From 05985743182a5e7d462a6a8b872b92c9b8a90404 Mon Sep 17 00:00:00 2001 From: Théo Lacombe Date: Thu, 5 Mar 2020 11:31:47 +0100 Subject: Update src/python/gudhi/persistence_graphical_tools.py Co-Authored-By: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> --- src/python/gudhi/persistence_graphical_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 48e26432..c9af88f5 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -51,7 +51,7 @@ def _array_handler(a): persistence-compatible list (padding with 0), so that the plot can be performed seamlessly. ''' - if isinstance(a, np.ndarray): + if isinstance(a[0][1], np.float64) or isinstance(a[0][1], float): return [[0, x] for x in a] else: return a -- cgit v1.2.3 From 19ea0c10f283188282a78ebebf4c1a51f2f40040 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 5 Mar 2020 14:14:27 +0100 Subject: CR: use complex_simplex_range instead of filtration_simplex_range --- .../test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp index a8130e25..4697ec05 100644 --- a/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_make_filtration_non_decreasing_unit_test.cpp @@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(make_filtration_non_decreasing_on_nan_values, type st.make_filtration_non_decreasing(); std::cout << "Check all filtration values are NaN" << std::endl; - for (auto f_simplex : st.filtration_simplex_range()) { + for (auto f_simplex : st.complex_simplex_range()) { BOOST_CHECK(std::isnan(st.filtration(f_simplex))); } @@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(make_filtration_non_decreasing_on_nan_values, type BOOST_CHECK(st.make_filtration_non_decreasing()); std::cout << "Check all filtration values are now defined" << std::endl; - for (auto f_simplex : st.filtration_simplex_range()) { + for (auto f_simplex : st.complex_simplex_range()) { BOOST_CHECK(!std::isnan(st.filtration(f_simplex))); } } \ No newline at end of file -- cgit v1.2.3 From c1ce28b8e8021097825a893564aed97757f2ac8e Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Thu, 5 Mar 2020 14:27:43 +0100 Subject: Fix bad link --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 13d6cad7..a18ff8bd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,7 +19,7 @@ There is a label **enhancement** in the [new issue](https://github.com/GUDHI/gud ## You are not familiar with GitHub ? -Please take some time to read our [how to use GitHub to contribute to GUDHI](/home/vincent/workspace/gudhi/gudhi-devel/for_dev/how_to_use_github_to_contribute_to_gudhi.md). +Please take some time to read our [how to use GitHub to contribute to GUDHI](how_to_use_github_to_contribute_to_gudhi.md). ## Something you want to improve in the documentation -- cgit v1.2.3 From eccfa153efe660662be945134115f7634c27335d Mon Sep 17 00:00:00 2001 From: tlacombe Date: Thu, 5 Mar 2020 15:32:09 +0100 Subject: updated doc --- src/python/gudhi/persistence_graphical_tools.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index c9af88f5..6fd854ff 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -74,9 +74,8 @@ def plot_persistence_barcode( in a single homology dimension), or from a :doc:`persistence file `. - :param persistence: Persistence intervals values list grouped by dimension, - or np.array of shape (N x 2). - :type persistence: list of tuples(dimension, tuple(birth, death)). + :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 persistence_file: A :doc:`persistence file ` style name (reset persistence if both are set). :type persistence_file: string @@ -217,9 +216,8 @@ def plot_persistence_diagram( list, a np.array of shape (N x 2) representing a diagram in a single homology dimension, or from a :doc:`persistence file `. - :param persistence: Persistence intervals values list grouped by dimension, - or np.array of shape (N x 2). - :type persistence: list of tuples(dimension, tuple(birth, death)). + :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 persistence_file: A :doc:`persistence file ` style name (reset persistence if both are set). :type persistence_file: string @@ -373,9 +371,10 @@ def plot_persistence_density( up to you to select the required one. This function also does not handle degenerate data set (scipy correlation matrix inversion can fail). - :param persistence: Persistence intervals values list grouped by dimension, - or np.array of shape (N x 2). - :type persistence: list of tuples(dimension, tuple(birth, death)). + :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 persistence_file: A :doc:`persistence file ` style name (reset persistence if both are set). :type persistence_file: string -- cgit v1.2.3 From 570a9b83eb3f714bc52735dae289a5195874bf41 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Thu, 5 Mar 2020 15:40:45 +0100 Subject: completed as... --- src/python/gudhi/wasserstein.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index ba0f7343..aab0cb3c 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -30,7 +30,9 @@ def _build_dist_matrix(X, Y, order=2., internal_p=2.): :param order: exponent for the Wasserstein metric. :param internal_p: Ground metric (i.e. norm L^p). :returns: (n+1) x (m+1) np.array encoding the cost matrix C. - For 1 <= i <= n, 1 <= j <= m, C[i,j] encodes the distance between X[i] and Y[j], while C[i, m+1] (resp. C[n+1, j]) encodes the distance (to the p) between X[i] (resp Y[j]) and its orthogonal proj onto the diagonal. + For 1 <= i <= n, 1 <= j <= m, C[i,j] encodes the distance between X[i] and Y[j], + while C[i, m+1] (resp. C[n+1, j]) encodes the distance (to the p) between X[i] (resp Y[j]) + and its orthogonal proj onto the diagonal. note also that C[n+1, m+1] = 0 (it costs nothing to move from the diagonal to the diagonal). ''' Xdiag = _proj_on_diag(X) @@ -88,7 +90,9 @@ def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): :param X: (n x 2) numpy.array encoding the (finite points of the) first diagram. Must not contain essential points (i.e. with infinite coordinate). :param Y: (m x 2) numpy.array encoding the second diagram. - :param matching: if True, computes and returns the optimal matching between X and Y, encoded as... + :param matching: if True, computes and returns the optimal matching between X and Y, encoded as + a list of tuple [...(i,j)...], meaning the i-th point in X is matched to + the j-th point in Y, with the convention (-1) represents the diagonal. :param order: exponent for Wasserstein; Default value is 2. :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2); Default value is 2 (Euclidean norm). -- cgit v1.2.3 From 27d5a1dfdaeb2bc1840e9c39be4c58570d948d56 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Thu, 5 Mar 2020 16:27:30 +0100 Subject: [skip ci] Replace travis badge with azure one --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f7e3d70c..279953e1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -[![Build Status](https://travis-ci.org/GUDHI/gudhi-devel.svg?branch=master)](https://travis-ci.org/GUDHI/gudhi-devel) -[![CircleCI](https://circleci.com/gh/GUDHI/gudhi-devel/tree/master.svg?style=svg)](https://circleci.com/gh/GUDHI/gudhi-devel/tree/master) -[![Build status](https://ci.appveyor.com/api/projects/status/976j2uut8xgalvx2/branch/master?svg=true)](https://ci.appveyor.com/project/GUDHI/gudhi-devel/branch/master) +[![OSx on Azure](https://dev.azure.com/GUDHI/gudhi-devel/_apis/build/status/GUDHI.gudhi-devel?branchName=master)](https://dev.azure.com/GUDHI/gudhi-devel/_build/latest?definitionId=1&branchName=master) +[![Linux on CircleCI](https://circleci.com/gh/GUDHI/gudhi-devel/tree/master.svg?style=svg)](https://circleci.com/gh/GUDHI/gudhi-devel/tree/master) +[![Win on Appveyor](https://ci.appveyor.com/api/projects/status/976j2uut8xgalvx2/branch/master?svg=true)](https://ci.appveyor.com/project/GUDHI/gudhi-devel/branch/master) [![Anaconda Cloud](https://anaconda.org/conda-forge/gudhi/badges/version.svg)](https://anaconda.org/conda-forge/gudhi) [![Anaconda downloads](https://anaconda.org/conda-forge/gudhi/badges/downloads.svg)](https://anaconda.org/conda-forge/gudhi) -- cgit v1.2.3 From ae8c34ad96f421d480cbe6bff9c04ec6f0eff920 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Thu, 5 Mar 2020 16:55:16 +0100 Subject: [skip ci] Install full LaTex matplotlib seems to fail to produce LaTeX-like plots --- Dockerfile_for_circleci_image | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile_for_circleci_image b/Dockerfile_for_circleci_image index cca93f0c..1eededb5 100644 --- a/Dockerfile_for_circleci_image +++ b/Dockerfile_for_circleci_image @@ -30,7 +30,7 @@ RUN apt-get install -y make \ cmake \ graphviz \ perl \ - texlive-bibtex-extra \ + texlive-full \ biber \ doxygen \ libboost-all-dev \ -- cgit v1.2.3 From 7789744d5d5972ef2d6218296a7b8a0a05337679 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Fri, 6 Mar 2020 13:31:05 +0100 Subject: set greyblock to False by default in density --- src/python/gudhi/persistence_graphical_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 6fd854ff..8c38b684 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -361,7 +361,7 @@ def plot_persistence_density( legend=False, axes=None, fontsize=16, - greyblock=True + greyblock=False ): """This function plots the persistence density from persistence values list, np.array of shape (N x 2) representing a diagram @@ -410,7 +410,7 @@ def plot_persistence_density( :param fontsize: Fontsize to use in axis. :type fontsize: int :param greyblock: if we want to plot a grey patch on the lower half plane - for nicer rendering. Default True. + for nicer rendering. Default False. :type greyblock: boolean :returns: (`matplotlib.axes.Axes`): The axes on which the plot was drawn. """ -- cgit v1.2.3 From 78ccc10eb0034a4648df303f2913b6b4680b085e Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 6 Mar 2020 17:35:38 +0100 Subject: Generators for simplex tree --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 86 +++++++++++++++++++++- .../gudhi/Simplex_tree/Simplex_tree_iterators.h | 12 +-- src/Simplex_tree/test/simplex_tree_unit_test.cpp | 36 +++++++++ 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 76608008..7315bf45 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef GUDHI_USE_TBB #include @@ -246,8 +247,8 @@ class Simplex_tree { * which is consequenlty * equal to \f$(-1)^{\text{dim} \sigma}\f$ the canonical orientation on the simplex. */ - Simplex_vertex_range simplex_vertex_range(Simplex_handle sh) { - assert(sh != null_simplex()); // Empty simplex + Simplex_vertex_range simplex_vertex_range(Simplex_handle sh) const { + GUDHI_CHECK(sh != null_simplex(), "empty simplex"); return Simplex_vertex_range(Simplex_vertex_iterator(this, sh), Simplex_vertex_iterator(this)); } @@ -450,6 +451,15 @@ class Simplex_tree { return true; } + /** \brief Returns the filtration value of a simplex. + * + * Same as `filtration()`, but does not handle `null_simplex()`. + */ + static Filtration_value filtration_(Simplex_handle sh) { + GUDHI_CHECK (sh != null_simplex(), "null simplex"); + return sh->second.filtration(); + } + public: /** \brief Returns the key associated to a simplex. * @@ -827,7 +837,7 @@ class Simplex_tree { /** Returns the Siblings containing a simplex.*/ template - Siblings* self_siblings(SimplexHandle sh) { + static Siblings* self_siblings(SimplexHandle sh) { if (sh->second.children()->parent() == sh->first) return sh->second.children()->oncles(); else @@ -1465,6 +1475,76 @@ class Simplex_tree { } } + /** \brief Returns a vertex of `sh` that has the same filtration value as `sh` if it exists, and `null_vertex()` otherwise. + * + * For a lower-star filtration built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out which vertex had its filtration value propagated to `sh`. + * If several vertices have the same filtration value, the one it returns is arbitrary. */ + Vertex_handle vertex_with_same_filtration(Simplex_handle sh) { + auto filt = filtration_(sh); + for(auto v : simplex_vertex_range(sh)) + if(filtration_(find_vertex(v)) == filt) + return v; + return null_vertex(); + } + + /** \brief Returns an edge of `sh` that has the same filtration value as `sh` if it exists, and `null_simplex()` otherwise. + * + * For a flag-complex built with `expansion()`, this is a way to invert the process and find out which edge had its filtration value propagated to `sh`. + * If several edges have the same filtration value, the one it returns is arbitrary. + * + * \pre `sh` must have dimension at least 1. */ + Simplex_handle edge_with_same_filtration(Simplex_handle sh) { +#if 0 + // FIXME: Only do this if dim >= 2, since we don't want to return a vertex... + // Test if we are lucky and the parent has the same filtration value. + Siblings* sib = self_siblings(sh); + Vertex_handle v_par = sib->parent(); + sib = sib->oncles(); + Simplex_handle par = sib->find(v_par); + if(filtration_(par) == filt) return edge_with_same_filtration(par); +#endif + auto&& vertices = simplex_vertex_range(sh); + auto end = std::end(vertices); + auto vi = std::begin(vertices); + GUDHI_CHECK(vi != end, "empty simplex"); + auto v0 = *vi; + ++vi; + GUDHI_CHECK(vi != end, "simplex of dimension 0"); + if(std::next(vi) == end) return sh; // shortcut for dimension 1 + boost::container::static_vector suffix; + suffix.push_back(v0); + auto filt = filtration_(sh); + do + { + Vertex_handle v = *vi; + auto&& children1 = find_vertex(v)->second.children()->members_; + for(auto w : suffix){ + // Can we take advantage of the fact that suffix is ordered? + Simplex_handle s = children1.find(w); + if(filtration_(s) == filt) + return s; + } + suffix.push_back(v); + } + while(++vi != end); + return null_simplex(); + } + + /** \brief Returns an edge of `sh` that has the same filtration value as `sh` if it exists, and `null_simplex()` otherwise. + * + * For a flag-complex built with `expansion()`, this is a way to invert the process and find out which edge had its filtration value propagated to `sh`. + * If several edges have the same filtration value, the one it returns is arbitrary. */ + Simplex_handle minimal_simplex_with_same_filtration(Simplex_handle sh) { + if(dimension(sh) == 0) // vertices are minimal + return sh; + auto filt = filtration_(sh); + // Naive implementation, it can be sped up. + for(auto b : boundary_simplex_range(sh)) + if(filtration_(b) == filt) + return minimal_simplex_with_same_filtration(b); + return sh; // None of its faces has the same filtration. + } + private: Vertex_handle null_vertex_; /** \brief Total number of simplices in the complex, without the empty simplex.*/ 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 efccf2f2..9007b6bd 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 @@ -15,9 +15,7 @@ #include #include -#if BOOST_VERSION >= 105600 -# include -#endif +#include #include @@ -42,13 +40,13 @@ class Simplex_tree_simplex_vertex_iterator : public boost::iterator_facade< typedef typename SimplexTree::Siblings Siblings; typedef typename SimplexTree::Vertex_handle Vertex_handle; - explicit Simplex_tree_simplex_vertex_iterator(SimplexTree * st) + explicit Simplex_tree_simplex_vertex_iterator(SimplexTree const* st) : // any end() iterator sib_(nullptr), v_(st->null_vertex()) { } - Simplex_tree_simplex_vertex_iterator(SimplexTree * st, Simplex_handle sh) + Simplex_tree_simplex_vertex_iterator(SimplexTree const* st, Simplex_handle sh) : sib_(st->self_siblings(sh)), v_(sh->first) { } @@ -166,15 +164,11 @@ class Simplex_tree_boundary_simplex_iterator : public boost::iterator_facade< // Most of the storage should be moved to the range, iterators should be light. Vertex_handle last_; // last vertex of the simplex Vertex_handle next_; // next vertex to push in suffix_ -#if BOOST_VERSION >= 105600 // 40 seems a conservative bound on the dimension of a Simplex_tree for now, // as it would not fit on the biggest hard-drive. boost::container::static_vector suffix_; // static_vector still has some overhead compared to a trivial hand-made // version using std::aligned_storage, or compared to making suffix_ static. -#else - std::vector suffix_; -#endif Siblings * sib_; // where the next search will start from Simplex_handle sh_; // current Simplex_handle in the boundary SimplexTree * st_; // simplex containing the simplicial complex diff --git a/src/Simplex_tree/test/simplex_tree_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_unit_test.cpp index 58bfa8db..2a2e2b25 100644 --- a/src/Simplex_tree/test/simplex_tree_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_unit_test.cpp @@ -986,5 +986,41 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(insert_duplicated_vertices, typeST, list_of_tested << " - num_simplices = " << st.num_simplices() << std::endl; BOOST_CHECK(st.dimension() == 1); BOOST_CHECK(st.num_simplices() == st.num_vertices() + 1); +} +BOOST_AUTO_TEST_CASE_TEMPLATE(generators, typeST, list_of_tested_variants) { + std::cout << "********************************************************************" << std::endl; + std::cout << "TEST FIND GENERATORS" << std::endl; + { + typeST st; + st.insert_simplex_and_subfaces({0,1,2,3,4,5,6},0); + st.assign_filtration(st.find({0,2,4}), 10); + st.assign_filtration(st.find({1,5}), 20); + st.assign_filtration(st.find({1,2,4}), 30); + st.assign_filtration(st.find({3}), 5); + st.make_filtration_non_decreasing(); + BOOST_CHECK(st.filtration(st.find({1,2}))==0); + BOOST_CHECK(st.filtration(st.find({0,1,2,3,4}))==30); + BOOST_CHECK(st.minimal_simplex_with_same_filtration(st.find({0,1,2,3,4,5}))==st.find({1,2,4})); + BOOST_CHECK(st.minimal_simplex_with_same_filtration(st.find({0,2,3}))==st.find({3})); + auto s=st.minimal_simplex_with_same_filtration(st.find({0,2,6})); + BOOST_CHECK(s==st.find({0})||s==st.find({2})||s==st.find({6})); + BOOST_CHECK(st.vertex_with_same_filtration(st.find({2}))==2); + BOOST_CHECK(st.vertex_with_same_filtration(st.find({1,5}))==st.null_vertex()); + BOOST_CHECK(st.vertex_with_same_filtration(st.find({5,6}))>=5); + } + { + typeST st; + st.insert_simplex_and_subfaces({0,1}, 8); + st.insert_simplex_and_subfaces({0,2}, 10); + st.insert_simplex_and_subfaces({3,4}, 6); + st.insert_simplex_and_subfaces({1,2}, 5); + st.insert_simplex_and_subfaces({1,5}, 4); + st.insert_simplex_and_subfaces({0,5}, 3); + st.insert_simplex_and_subfaces({2,5}, 2); + st.insert_simplex_and_subfaces({1,3}, 9); + st.expansion(50); + BOOST_CHECK(st.edge_with_same_filtration(st.find({0,1,2,5}))==st.find({0,2})); + BOOST_CHECK(st.edge_with_same_filtration(st.find({1,5}))==st.find({1,5})); + } } -- cgit v1.2.3 From 5748647e9e89bb91e361b06c7c6cb053081618bf Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 6 Mar 2020 18:10:05 +0100 Subject: Fix doc of minimal_simplex_with_same_filtration --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 7315bf45..b6973756 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1530,13 +1530,11 @@ class Simplex_tree { return null_simplex(); } - /** \brief Returns an edge of `sh` that has the same filtration value as `sh` if it exists, and `null_simplex()` otherwise. + /** \brief Returns a minimal face of `sh` that has the same filtration value as `sh`. * - * For a flag-complex built with `expansion()`, this is a way to invert the process and find out which edge had its filtration value propagated to `sh`. - * If several edges have the same filtration value, the one it returns is arbitrary. */ + * For a complex built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out which simplex had its filtration value propagated to `sh`. + * If several minimal (for inclusion) simplices have the same filtration value, the one it returns is arbitrary, and it is not guaranteed to be the one with smallest dimension. */ Simplex_handle minimal_simplex_with_same_filtration(Simplex_handle sh) { - if(dimension(sh) == 0) // vertices are minimal - return sh; auto filt = filtration_(sh); // Naive implementation, it can be sped up. for(auto b : boundary_simplex_range(sh)) -- cgit v1.2.3 From 8bd39f74f69e8fcb662873e0e045c953b814f28f Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 6 Mar 2020 18:21:40 +0100 Subject: Tweak doc. --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index b6973756..ad592a92 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1532,7 +1532,7 @@ class Simplex_tree { /** \brief Returns a minimal face of `sh` that has the same filtration value as `sh`. * - * For a complex built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out which simplex had its filtration value propagated to `sh`. + * For a filtration built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out which simplex had its filtration value propagated to `sh`. * If several minimal (for inclusion) simplices have the same filtration value, the one it returns is arbitrary, and it is not guaranteed to be the one with smallest dimension. */ Simplex_handle minimal_simplex_with_same_filtration(Simplex_handle sh) { auto filt = filtration_(sh); -- cgit v1.2.3 From 5bc96fdf837e4acb80b1333b9db63ddf5802edc8 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Mon, 9 Mar 2020 12:12:13 +0100 Subject: removed infty line plot in plot_diagram if no pts at infty --- src/python/gudhi/persistence_graphical_tools.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/python/gudhi/persistence_graphical_tools.py b/src/python/gudhi/persistence_graphical_tools.py index 8c38b684..cc3db467 100644 --- a/src/python/gudhi/persistence_graphical_tools.py +++ b/src/python/gudhi/persistence_graphical_tools.py @@ -296,17 +296,6 @@ def plot_persistence_diagram( axis_end = max_death + delta / 2 axis_start = min_birth - delta - # 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() - 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$' - axes.set_yticks(yt) - axes.set_yticklabels(ytl) # bootstrap band if band > 0.0: x = np.linspace(axis_start, infinity, 1000) @@ -315,6 +304,7 @@ def plot_persistence_diagram( if greyblock: axes.add_patch(mpatches.Polygon([[axis_start, axis_start], [axis_end, axis_start], [axis_end, axis_end]], fill=True, color='lightgrey')) # 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 @@ -325,10 +315,23 @@ def plot_persistence_diagram( 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]] ) + 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() + 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$' + axes.set_yticks(yt) + axes.set_yticklabels(ytl) if legend: dimensions = list(set(item[0] for item in persistence)) -- cgit v1.2.3 From d1d25b4ae8d0f778f0e2b3f98449d7d13e466013 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 10 Mar 2020 09:04:45 +0100 Subject: Fix example - only fails on OSx --- src/python/example/alpha_complex_from_points_example.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/python/example/alpha_complex_from_points_example.py b/src/python/example/alpha_complex_from_points_example.py index 465632eb..73faf17c 100755 --- a/src/python/example/alpha_complex_from_points_example.py +++ b/src/python/example/alpha_complex_from_points_example.py @@ -46,6 +46,9 @@ if simplex_tree.find([4]): else: print("[4] Not found...") +# Some insertions, simplex_tree needs to initialize filtrations +simplex_tree.initialize_filtration() + print("dimension=", simplex_tree.dimension()) print("filtrations=") for simplex_with_filtration in simplex_tree.get_filtration(): -- cgit v1.2.3 From 2eca5c75b1fbd7157e2656b875e730dc5f00f373 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 10 Mar 2020 15:45:45 +0100 Subject: removed P[P < 0.5] thresholding ; as it shouldn't happen anymore. --- src/python/gudhi/wasserstein.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index aab0cb3c..e28c63e6 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -130,7 +130,6 @@ def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): if matching: P = ot.emd(a=a,b=b,M=M, numItermax=2000000) ot_cost = np.sum(np.multiply(P,M)) - P[P < 0.5] = 0 # trick to avoid numerical issue, could it be improved? match = np.argwhere(P) # Now we turn to -1 points encoding the diagonal match = _clean_match(match, n, m) -- cgit v1.2.3 From 7a5b614aa3bd06897e0135f0cda4e61f16951b20 Mon Sep 17 00:00:00 2001 From: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> Date: Tue, 10 Mar 2020 16:46:11 +0100 Subject: Try without trigger --- azure-pipelines.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 77e0ac88..95b15db2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,9 +1,3 @@ -trigger: - batch: true - branches: - include: - - '*' # All branches - jobs: - job: 'Test' -- cgit v1.2.3 From 967ceab26b09ad74e0cff0d84429a766af267f6b Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 10 Mar 2020 16:47:09 +0100 Subject: removed _clean_match and changed matching format, it is now a (n x 2) numpy array --- src/python/gudhi/wasserstein.py | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index e28c63e6..9efa946e 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -64,34 +64,13 @@ def _perstot(X, order, internal_p): return (np.sum(np.linalg.norm(X - Xdiag, ord=internal_p, axis=1)**order))**(1./order) -def _clean_match(match, n, m): - ''' - :param match: a list of the form [(i,j) ...] - :param n: int, size of the first dgm - :param m: int, size of the second dgm - :return: a modified version of match where indices greater than n, m are replaced by -1, encoding the diagonal. - and (-1, -1) are removed - ''' - new_match = [] - for i,j in match: - if i >= n: - if j < m: - new_match.append((-1, j)) - elif j >= m: - if i < n: - new_match.append((i,-1)) - else: - new_match.append((i,j)) - return new_match - - def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): ''' :param X: (n x 2) numpy.array encoding the (finite points of the) first diagram. Must not contain essential points (i.e. with infinite coordinate). :param Y: (m x 2) numpy.array encoding the second diagram. :param matching: if True, computes and returns the optimal matching between X and Y, encoded as - a list of tuple [...(i,j)...], meaning the i-th point in X is matched to + a (n x 2) np.array [...[i,j]...], meaning the i-th point in X is matched to the j-th point in Y, with the convention (-1) represents the diagonal. :param order: exponent for Wasserstein; Default value is 2. :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2); @@ -114,12 +93,12 @@ def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): if not matching: return _perstot(Y, order, internal_p) else: - return _perstot(Y, order, internal_p), [(-1, j) for j in range(m)] + return _perstot(Y, order, internal_p), np.array([[-1, j] for j in range(m)]) elif Y.size == 0: if not matching: return _perstot(X, order, internal_p) else: - return _perstot(X, order, internal_p), [(i, -1) for i in range(n)] + return _perstot(X, order, internal_p), np.array([[i, -1] for i in range(n)]) M = _build_dist_matrix(X, Y, order=order, internal_p=internal_p) a = np.ones(n+1) # weight vector of the input diagram. Uniform here. @@ -130,9 +109,11 @@ def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): if matching: P = ot.emd(a=a,b=b,M=M, numItermax=2000000) ot_cost = np.sum(np.multiply(P,M)) + P[-1, -1] = 0 # Remove matching corresponding to the diagonal match = np.argwhere(P) # Now we turn to -1 points encoding the diagonal - match = _clean_match(match, n, m) + match[:,0][match[:,0] >= n] = -1 + match[:,1][match[:,1] >= m] = -1 return ot_cost ** (1./order) , match # Comptuation of the otcost using the ot.emd2 library. -- cgit v1.2.3 From 4aea5deab6ce4cbb491f4c9c2b7e9f023efbbe01 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 10 Mar 2020 17:41:38 +0100 Subject: changed output of matching as a (n x 2) array, adapted tests and doc --- src/python/doc/wasserstein_distance_user.rst | 2 +- src/python/gudhi/wasserstein.py | 2 +- src/python/test/test_wasserstein_distance.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index d3daa318..9519caa6 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -69,5 +69,5 @@ The output is: .. testoutput:: - Wasserstein distance value = 2.15, optimal matching: [(0, 0), (1, 2), (2, -1), (-1, 1)] + Wasserstein distance value = 2.15, optimal matching: [[0, 0], [1, 2], [2, -1], [-1, 1]] diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index 9efa946e..9e4dc7d5 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -88,7 +88,7 @@ def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): if not matching: return 0. else: - return 0., [] + return 0., np.array([]) else: if not matching: return _perstot(Y, order, internal_p) diff --git a/src/python/test/test_wasserstein_distance.py b/src/python/test/test_wasserstein_distance.py index d0f0323c..ca9a4a61 100755 --- a/src/python/test/test_wasserstein_distance.py +++ b/src/python/test/test_wasserstein_distance.py @@ -61,15 +61,15 @@ def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_mat if test_matching: match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=1., order=2)[1] - assert match == [] + assert np.array_equal(match, np.array([])) match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] - assert match == [] + assert np.array_equal(match, np.array([])) match = wasserstein_distance(emptydiag, diag2, matching=True, internal_p=np.inf, order=2.)[1] - assert match == [(-1, 0), (-1, 1)] + assert np.array_equal(match , np.array([[-1, 0], [-1, 1]])) match = wasserstein_distance(diag2, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] - assert match == [(0, -1), (1, -1)] + assert np.array_equal(match , np.array([[0, -1], [1, -1]])) match = wasserstein_distance(diag1, diag2, matching=True, internal_p=2., order=2.)[1] - assert match == [(0, 0), (1, 1), (2, -1)] + assert np.array_equal(match, np.array_equal([[0, 0], [1, 1], [2, -1]])) -- cgit v1.2.3 From fc4e10863d103ee6bc22863f48548fe246a3ddd6 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 10 Mar 2020 18:03:21 +0100 Subject: correction of typo in the doc --- src/python/gudhi/wasserstein.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index 9e4dc7d5..12337780 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -30,10 +30,10 @@ def _build_dist_matrix(X, Y, order=2., internal_p=2.): :param order: exponent for the Wasserstein metric. :param internal_p: Ground metric (i.e. norm L^p). :returns: (n+1) x (m+1) np.array encoding the cost matrix C. - For 1 <= i <= n, 1 <= j <= m, C[i,j] encodes the distance between X[i] and Y[j], - while C[i, m+1] (resp. C[n+1, j]) encodes the distance (to the p) between X[i] (resp Y[j]) + For 0 <= i < n, 0 <= j < m, C[i,j] encodes the distance between X[i] and Y[j], + while C[i, m] (resp. C[n, j]) encodes the distance (to the p) between X[i] (resp Y[j]) and its orthogonal proj onto the diagonal. - note also that C[n+1, m+1] = 0 (it costs nothing to move from the diagonal to the diagonal). + note also that C[n, m] = 0 (it costs nothing to move from the diagonal to the diagonal). ''' Xdiag = _proj_on_diag(X) Ydiag = _proj_on_diag(Y) -- cgit v1.2.3 From 6c369a6aa566dfcb8cdb501d0c39eafb32219669 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 10 Mar 2020 18:08:15 +0100 Subject: fix typo in test_wasserstein_distance --- src/python/test/test_wasserstein_distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/test/test_wasserstein_distance.py b/src/python/test/test_wasserstein_distance.py index ca9a4a61..f92208c0 100755 --- a/src/python/test/test_wasserstein_distance.py +++ b/src/python/test/test_wasserstein_distance.py @@ -69,7 +69,7 @@ def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_mat match = wasserstein_distance(diag2, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] assert np.array_equal(match , np.array([[0, -1], [1, -1]])) match = wasserstein_distance(diag1, diag2, matching=True, internal_p=2., order=2.)[1] - assert np.array_equal(match, np.array_equal([[0, 0], [1, 1], [2, -1]])) + assert np.array_equal(match, np.array([[0, 0], [1, 1], [2, -1]])) -- cgit v1.2.3 From 753290475ab6e95c2de1baad97ee6f755a0ce19a Mon Sep 17 00:00:00 2001 From: Théo Lacombe Date: Tue, 10 Mar 2020 18:25:10 +0100 Subject: Update src/python/gudhi/wasserstein.py Co-Authored-By: Marc Glisse --- src/python/gudhi/wasserstein.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index 12337780..83a682df 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -32,7 +32,7 @@ def _build_dist_matrix(X, Y, order=2., internal_p=2.): :returns: (n+1) x (m+1) np.array encoding the cost matrix C. For 0 <= i < n, 0 <= j < m, C[i,j] encodes the distance between X[i] and Y[j], while C[i, m] (resp. C[n, j]) encodes the distance (to the p) between X[i] (resp Y[j]) - and its orthogonal proj onto the diagonal. + and its orthogonal projection onto the diagonal. note also that C[n, m] = 0 (it costs nothing to move from the diagonal to the diagonal). ''' Xdiag = _proj_on_diag(X) -- cgit v1.2.3 From c9d6e27495c8927d736d593afb0450360b46ccc9 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Tue, 10 Mar 2020 18:55:19 +0100 Subject: fix indentation in wasserstein --- src/python/gudhi/wasserstein.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python/gudhi/wasserstein.py b/src/python/gudhi/wasserstein.py index 83a682df..3dd993f9 100644 --- a/src/python/gudhi/wasserstein.py +++ b/src/python/gudhi/wasserstein.py @@ -70,13 +70,13 @@ def wasserstein_distance(X, Y, matching=False, order=2., internal_p=2.): (i.e. with infinite coordinate). :param Y: (m x 2) numpy.array encoding the second diagram. :param matching: if True, computes and returns the optimal matching between X and Y, encoded as - a (n x 2) np.array [...[i,j]...], meaning the i-th point in X is matched to - the j-th point in Y, with the convention (-1) represents the diagonal. + a (n x 2) np.array [...[i,j]...], meaning the i-th point in X is matched to + the j-th point in Y, with the convention (-1) represents the diagonal. :param order: exponent for Wasserstein; Default value is 2. :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2); - Default value is 2 (Euclidean norm). + Default value is 2 (Euclidean norm). :returns: the Wasserstein distance of order q (1 <= q < infinity) between persistence diagrams with - respect to the internal_p-norm as ground metric. + respect to the internal_p-norm as ground metric. If matching is set to True, also returns the optimal matching between X and Y. ''' n = len(X) -- cgit v1.2.3 From a17a09a2c58bba79e897d0ba00aada05da556967 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Wed, 11 Mar 2020 10:41:53 +0100 Subject: clean test_wasserstein from useless np.array --- src/python/test/test_wasserstein_distance.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/python/test/test_wasserstein_distance.py b/src/python/test/test_wasserstein_distance.py index f92208c0..0d70e11a 100755 --- a/src/python/test/test_wasserstein_distance.py +++ b/src/python/test/test_wasserstein_distance.py @@ -61,15 +61,15 @@ def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_mat if test_matching: match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=1., order=2)[1] - assert np.array_equal(match, np.array([])) + assert np.array_equal(match, []) match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] - assert np.array_equal(match, np.array([])) + assert np.array_equal(match, []) match = wasserstein_distance(emptydiag, diag2, matching=True, internal_p=np.inf, order=2.)[1] - assert np.array_equal(match , np.array([[-1, 0], [-1, 1]])) + assert np.array_equal(match , [[-1, 0], [-1, 1]]) match = wasserstein_distance(diag2, emptydiag, matching=True, internal_p=np.inf, order=2.24)[1] - assert np.array_equal(match , np.array([[0, -1], [1, -1]])) + assert np.array_equal(match , [[0, -1], [1, -1]]) match = wasserstein_distance(diag1, diag2, matching=True, internal_p=2., order=2.)[1] - assert np.array_equal(match, np.array([[0, 0], [1, 1], [2, -1]])) + assert np.array_equal(match, [[0, 0], [1, 1], [2, -1]]) -- cgit v1.2.3 From 62eb0a311da737a58ec704c3c5ad93da871e57a0 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Thu, 12 Mar 2020 22:37:43 +0100 Subject: Point the hera submodule to github instead of bitbucket --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 6e8b3ab1..f70c570d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "ext/hera"] path = ext/hera - url = https://bitbucket.org/grey_narn/hera.git + url = https://github.com/grey-narn/hera.git -- cgit v1.2.3 From b0bd6dfc34a3c93073e9f326292047f8debb7fb3 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Thu, 12 Mar 2020 23:46:03 +0100 Subject: Update hera --- ext/hera | 2 +- src/cmake/modules/GUDHI_third_party_libraries.cmake | 2 +- src/cmake/modules/GUDHI_user_version_target.cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/hera b/ext/hera index cb1838e6..0019cae9 160000 --- a/ext/hera +++ b/ext/hera @@ -1 +1 @@ -Subproject commit cb1838e682ec07f80720241cf9098400caeb83c7 +Subproject commit 0019cae9dc1e9d11aa03bc59681435ba7f21eea8 diff --git a/src/cmake/modules/GUDHI_third_party_libraries.cmake b/src/cmake/modules/GUDHI_third_party_libraries.cmake index 6db2c76b..2d010483 100644 --- a/src/cmake/modules/GUDHI_third_party_libraries.cmake +++ b/src/cmake/modules/GUDHI_third_party_libraries.cmake @@ -68,7 +68,7 @@ if(CGAL_FOUND) endif() # For those who dislike bundled dependencies, this indicates where to find a preinstalled Hera. -set(HERA_WASSERSTEIN_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/ext/hera/geom_matching/wasserstein/include CACHE PATH "Directory where one can find Hera's wasserstein.h") +set(HERA_WASSERSTEIN_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/ext/hera/wasserstein/include CACHE PATH "Directory where one can find Hera's wasserstein.h") option(WITH_GUDHI_USE_TBB "Build with Intel TBB parallelization" ON) diff --git a/src/cmake/modules/GUDHI_user_version_target.cmake b/src/cmake/modules/GUDHI_user_version_target.cmake index 5047252f..257d1939 100644 --- a/src/cmake/modules/GUDHI_user_version_target.cmake +++ b/src/cmake/modules/GUDHI_user_version_target.cmake @@ -55,7 +55,7 @@ add_custom_command(TARGET user_version PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/GudhUI ${GUDHI_USER_VERSION_DIR}/GudhUI) add_custom_command(TARGET user_version PRE_BUILD COMMAND ${CMAKE_COMMAND} -E - copy_directory ${CMAKE_SOURCE_DIR}/ext/hera/geom_matching/wasserstein/include ${GUDHI_USER_VERSION_DIR}/ext/hera/geom_matching/wasserstein/include) + copy_directory ${CMAKE_SOURCE_DIR}/ext/hera/wasserstein/include ${GUDHI_USER_VERSION_DIR}/ext/hera/wasserstein/include) set(GUDHI_DIRECTORIES "doc;example;concept;utilities") -- cgit v1.2.3 From ba24c58487f9a62e024138127c1b8375449334f9 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 13 Mar 2020 09:17:25 +0100 Subject: Mention git submodule sync --- .github/how_to_use_github_to_contribute_to_gudhi.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/how_to_use_github_to_contribute_to_gudhi.md b/.github/how_to_use_github_to_contribute_to_gudhi.md index 6ab05e36..747ca39b 100644 --- a/.github/how_to_use_github_to_contribute_to_gudhi.md +++ b/.github/how_to_use_github_to_contribute_to_gudhi.md @@ -25,10 +25,10 @@ This creates a directory gudhi-devel, which you are free to move around or renam cd gudhi-devel ``` -Everytime you clone the repository, you will have to download the *submodules*. +When you clone the repository, you also need to download the *submodules*. ## Submodules -An interface to Hera for Wasserstein distance is available on an external git repository. To download it: +Hera, used for Wasserstein distance, is available on an external git repository. To download it: ```bash git submodule update --init ``` @@ -60,8 +60,9 @@ This is a command you can run quite regularly. It tells git to check all that happened on github. It is safe, it will not mess with your files. -**Reminder:** Everytime you checkout master or merge from master, afterwards, if the version of one the submodule has changed, or if a submodule was added, you will have to: +**Reminder:** If the version of a submodule has changed, or if a submodule was added, you may need to: ```bash +git submodule sync git submodule update --init ``` -- cgit v1.2.3 From 6ed2a97421a223b4ebe31b91f48d779c2209f470 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Mon, 16 Mar 2020 13:38:18 +0100 Subject: Add get_simplices method - contrary to get_filtration method, sort is not performed --- src/Simplex_tree/example/simple_simplex_tree.cpp | 13 +++++++++++-- src/python/example/simplex_tree_example.py | 4 ++++ src/python/gudhi/simplex_tree.pxd | 8 ++++++++ src/python/gudhi/simplex_tree.pyx | 17 ++++++++++++++++- src/python/include/Simplex_tree_interface.h | 11 +++++++++++ src/python/test/test_simplex_tree.py | 12 ++++++++++++ 6 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/Simplex_tree/example/simple_simplex_tree.cpp b/src/Simplex_tree/example/simple_simplex_tree.cpp index 4353939f..47ea7e36 100644 --- a/src/Simplex_tree/example/simple_simplex_tree.cpp +++ b/src/Simplex_tree/example/simple_simplex_tree.cpp @@ -166,10 +166,19 @@ int main(int argc, char* const argv[]) { // ++ GENERAL VARIABLE SET std::cout << "********************************************************************\n"; - // Display the Simplex_tree - Can not be done in the middle of 2 inserts std::cout << "* The complex contains " << simplexTree.num_simplices() << " simplices\n"; std::cout << " - dimension " << simplexTree.dimension() << "\n"; - std::cout << "* Iterator on Simplices in the filtration, with [filtration value]:\n"; + std::cout << "* Iterator on simplices, with [filtration value]:\n"; + for (Simplex_tree::Simplex_handle f_simplex : simplexTree.complex_simplex_range()) { + std::cout << " " + << "[" << simplexTree.filtration(f_simplex) << "] "; + for (auto vertex : simplexTree.simplex_vertex_range(f_simplex)) std::cout << "(" << vertex << ")"; + std::cout << std::endl; + } + + std::cout << "********************************************************************\n"; + // Can not be done in the middle of 2 inserts + std::cout << "* Iterator on simplices sorted by filtration values, with [filtration value]:\n"; for (auto f_simplex : simplexTree.filtration_simplex_range()) { std::cout << " " << "[" << simplexTree.filtration(f_simplex) << "] "; diff --git a/src/python/example/simplex_tree_example.py b/src/python/example/simplex_tree_example.py index 7f20c389..34833899 100755 --- a/src/python/example/simplex_tree_example.py +++ b/src/python/example/simplex_tree_example.py @@ -38,6 +38,10 @@ else: print("dimension=", st.dimension()) +print("simplices=") +for simplex_with_filtration in st.get_simplices(): + print("(%s, %.2f)" % tuple(simplex_with_filtration)) + st.initialize_filtration() print("filtration=") for simplex_with_filtration in st.get_filtration(): diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 66c173a6..82f155de 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -24,6 +24,12 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_simplex_handle "Gudhi::Simplex_tree_interface::Simplex_handle": pass + cdef cppclass Simplex_tree_simplices_iterator "Gudhi::Simplex_tree_interface::Complex_simplex_iterator": + Simplex_tree_simplices_iterator() + Simplex_tree_simplex_handle& operator*() + Simplex_tree_simplices_iterator operator++() + bint operator!=(Simplex_tree_simplices_iterator) + cdef cppclass Simplex_tree_skeleton_iterator "Gudhi::Simplex_tree_interface::Skeleton_simplex_iterator": Simplex_tree_skeleton_iterator() Simplex_tree_simplex_handle& operator*() @@ -53,6 +59,8 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": bool make_filtration_non_decreasing() # Iterators over Simplex tree pair[vector[int], double] get_simplex_and_filtration(Simplex_tree_simplex_handle f_simplex) + Simplex_tree_simplices_iterator get_simplices_iterator_begin() + Simplex_tree_simplices_iterator get_simplices_iterator_end() vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_begin() vector[Simplex_tree_simplex_handle].const_iterator get_filtration_iterator_end() Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index efac2d80..c01cc905 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -208,10 +208,25 @@ cdef class SimplexTree: return self.get_ptr().insert_simplex_and_subfaces(csimplex, filtration) - def get_filtration(self): + def get_simplices(self): """This function returns a generator with simplices and their given filtration values. + :returns: The simplices. + :rtype: generator with tuples(simplex, filtration) + """ + cdef Simplex_tree_simplices_iterator it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_simplices_iterator end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_simplex_handle sh = dereference(it) + + while it != end: + yield self.get_ptr().get_simplex_and_filtration(dereference(it)) + preincrement(it) + + def get_filtration(self): + """This function returns a generator with simplices and their given + filtration values sorted by increasing filtration values. + :returns: The simplices sorted by increasing filtration values. :rtype: generator with tuples(simplex, filtration) """ diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 66ce5afd..4a7062d6 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -36,6 +36,7 @@ class Simplex_tree_interface : public Simplex_tree { using Simplex_and_filtration = std::pair; using Filtered_simplices = std::vector; using Skeleton_simplex_iterator = typename Base::Skeleton_simplex_iterator; + using Complex_simplex_iterator = typename Base::Complex_simplex_iterator; public: bool find_simplex(const Simplex& vh) { @@ -122,6 +123,16 @@ class Simplex_tree_interface : public Simplex_tree { } // Iterator over the simplex tree + Complex_simplex_iterator get_simplices_iterator_begin() { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::complex_simplex_range().begin(); + } + + Complex_simplex_iterator get_simplices_iterator_end() { + // this specific case works because the range is just a pair of iterators - won't work if range was a vector + return Base::complex_simplex_range().end(); + } + typename std::vector::const_iterator get_filtration_iterator_begin() { // Base::initialize_filtration(); already performed in filtration_simplex_range // this specific case works because the range is just a pair of iterators - won't work if range was a vector diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 04b26e92..f7848379 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -249,3 +249,15 @@ def test_make_filtration_non_decreasing(): assert st.filtration([3, 4, 5]) == 2.0 assert st.filtration([3, 4]) == 2.0 assert st.filtration([4, 5]) == 2.0 + +def test_simplices_iterator(): + st = SimplexTree() + + assert st.insert([0, 1, 2], filtration=4.0) == True + assert st.insert([2, 3, 4], filtration=2.0) == True + + for simplex in st.get_simplices(): + print("simplex is: ", simplex[0]) + assert st.find(simplex[0]) == True + print("filtration is: ", simplex[1]) + assert st.filtration(simplex[0]) == simplex[1] -- cgit v1.2.3 From 5c55e976606b4dd020bd4e21c93ae22143ef5348 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Mon, 16 Mar 2020 18:01:16 +0100 Subject: changed doc of matchings for a more explicit (and hopefully sphinx-valid) version --- src/python/doc/wasserstein_distance_user.rst | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index 9519caa6..4c3b53dd 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -58,16 +58,29 @@ An index of -1 represents the diagonal. import gudhi.wasserstein import numpy as np - diag1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) - diag2 = np.array([[2.8, 4.45], [5, 6], [9.5, 14.1]]) - cost, matching = gudhi.wasserstein.wasserstein_distance(diag1, diag2, matching=True, order=1., internal_p=2.) - - message = "Wasserstein distance value = %.2f, optimal matching: %s" %(cost, matching) - print(message) + dgm1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) + dgm2 = np.array([[2.8, 4.45], [5, 6], [9.5, 14.1]]) + cost, matchings = gudhi.wasserstein.wasserstein_distance(diag1, diag2, matching=True, order=1., internal_p=2.) + + message_cost = "Wasserstein distance value = %.2f" %cost + print(message_cost) + dgm1_to_diagonal = matchings[np.where(matchings[:,0] == -1)][:,1] + dgm2_to_diagonal = matchings[np.where(matchings[:,1] == -1)][:,0] + off_diagonal_match = np.delete(matchings, np.where(matchings == -1)[0], axis=0) + + for i,j in off_diagonal_match: + print("point %s in dgm1 is matched to point %s in dgm2" %(i,j)) + for i in dgm1_to_diagonal: + print("point %s in dgm1 is matched to the diagonal" %i) + for j in dgm2_to_diagonal: + print("point %s in dgm2 is matched to the diagonal" %j) The output is: .. testoutput:: - Wasserstein distance value = 2.15, optimal matching: [[0, 0], [1, 2], [2, -1], [-1, 1]] - + Wasserstein distance value = 2.15 + point 0 in dgm1 is matched to point 0 in dgm2 + point 1 in dgm1 is matched to point 2 in dgm2 + point 2 in dgm1 is matched to the diagonal + point 1 in dgm2 is matched to the diagonal -- cgit v1.2.3 From 66f0b08a8f8d5006f8d29352c169525cc53a22e6 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Mon, 16 Mar 2020 19:11:30 +0100 Subject: changed typo in doc (diag --> dgm), used integer for order and internal p, simplify th ecode --- src/python/doc/wasserstein_distance_user.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index 4c3b53dd..f43b2217 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -36,10 +36,10 @@ Note that persistence diagrams must be submitted as (n x 2) numpy arrays and mus import gudhi.wasserstein import numpy as np - diag1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) - diag2 = np.array([[2.8, 4.45],[9.5, 14.1]]) + dgm1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) + dgm2 = np.array([[2.8, 4.45],[9.5, 14.1]]) - message = "Wasserstein distance value = " + '%.2f' % gudhi.wasserstein.wasserstein_distance(diag1, diag2, order=1., internal_p=2.) + message = "Wasserstein distance value = " + '%.2f' % gudhi.wasserstein.wasserstein_distance(dgm1, dgm2, order=1., internal_p=2.) print(message) The output is: @@ -60,12 +60,12 @@ An index of -1 represents the diagonal. dgm1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) dgm2 = np.array([[2.8, 4.45], [5, 6], [9.5, 14.1]]) - cost, matchings = gudhi.wasserstein.wasserstein_distance(diag1, diag2, matching=True, order=1., internal_p=2.) + cost, matchings = gudhi.wasserstein.wasserstein_distance(dgm1, dgm2, matching=True, order=1, internal_p=2) message_cost = "Wasserstein distance value = %.2f" %cost print(message_cost) - dgm1_to_diagonal = matchings[np.where(matchings[:,0] == -1)][:,1] - dgm2_to_diagonal = matchings[np.where(matchings[:,1] == -1)][:,0] + dgm1_to_diagonal = matching[matching[:,0] == -1, 1] + dgm2_to_diagonal = matching[matching[:,1] == -1, 0] off_diagonal_match = np.delete(matchings, np.where(matchings == -1)[0], axis=0) for i,j in off_diagonal_match: -- cgit v1.2.3 From a253c0c4f54a9a148740ed9c20457df0ea43c842 Mon Sep 17 00:00:00 2001 From: tlacombe Date: Mon, 16 Mar 2020 19:36:07 +0100 Subject: correction typo in usr.rst --- src/python/doc/wasserstein_distance_user.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index f43b2217..25e51d68 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -64,8 +64,8 @@ An index of -1 represents the diagonal. message_cost = "Wasserstein distance value = %.2f" %cost print(message_cost) - dgm1_to_diagonal = matching[matching[:,0] == -1, 1] - dgm2_to_diagonal = matching[matching[:,1] == -1, 0] + dgm1_to_diagonal = matchings[matchings[:,0] == -1, 1] + dgm2_to_diagonal = matchings[matchings[:,1] == -1, 0] off_diagonal_match = np.delete(matchings, np.where(matchings == -1)[0], axis=0) for i,j in off_diagonal_match: -- cgit v1.2.3 From 60d11e3f06e08b66e49997f389c4dc01b00b793f Mon Sep 17 00:00:00 2001 From: tlacombe Date: Mon, 16 Mar 2020 21:17:38 +0100 Subject: correction of typo in usr.rst --- src/python/doc/wasserstein_distance_user.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index 25e51d68..a9b21fa5 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -64,8 +64,8 @@ An index of -1 represents the diagonal. message_cost = "Wasserstein distance value = %.2f" %cost print(message_cost) - dgm1_to_diagonal = matchings[matchings[:,0] == -1, 1] - dgm2_to_diagonal = matchings[matchings[:,1] == -1, 0] + dgm1_to_diagonal = matchings[matchings[:,1] == -1, 0] + dgm2_to_diagonal = matchings[matchings[:,0] == -1, 1] off_diagonal_match = np.delete(matchings, np.where(matchings == -1)[0], axis=0) for i,j in off_diagonal_match: -- cgit v1.2.3 From 47e72c7250bff568735df829a40bcbea0a48f7c2 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 17 Mar 2020 13:28:35 +0100 Subject: Remove code that was commented out. --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index ad592a92..af711075 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -1494,16 +1494,8 @@ class Simplex_tree { * * \pre `sh` must have dimension at least 1. */ Simplex_handle edge_with_same_filtration(Simplex_handle sh) { -#if 0 - // FIXME: Only do this if dim >= 2, since we don't want to return a vertex... - // Test if we are lucky and the parent has the same filtration value. - Siblings* sib = self_siblings(sh); - Vertex_handle v_par = sib->parent(); - sib = sib->oncles(); - Simplex_handle par = sib->find(v_par); - if(filtration_(par) == filt) return edge_with_same_filtration(par); -#endif - auto&& vertices = simplex_vertex_range(sh); + // See issue #251 for potential speed improvements. + auto&& vertices = simplex_vertex_range(sh); // vertices in decreasing order auto end = std::end(vertices); auto vi = std::begin(vertices); GUDHI_CHECK(vi != end, "empty simplex"); -- cgit v1.2.3 From e16ca3a14de72e304d87a4c11b6115e18df899fa Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 17 Mar 2020 16:27:58 +0100 Subject: Fix #249 --- .github/test-requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/test-requirements.txt b/.github/test-requirements.txt index bd03f98e..18882792 100644 --- a/.github/test-requirements.txt +++ b/.github/test-requirements.txt @@ -5,4 +5,5 @@ sphinx-paramlinks matplotlib scipy scikit-learn -POT \ No newline at end of file +POT +tensorflow \ No newline at end of file -- cgit v1.2.3 From 513f15705668c4da0b44506052d78a9721ef1b64 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 17 Mar 2020 17:55:43 +0100 Subject: Fix #224 --- .../include/gudhi/Persistent_cohomology.h | 24 ++++++---------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h index 0f1876d0..b1ded5ae 100644 --- a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h +++ b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h @@ -566,15 +566,9 @@ class Persistent_cohomology { std::sort(std::begin(persistent_pairs_), std::end(persistent_pairs_), cmp); bool has_infinity = std::numeric_limits::has_infinity; for (auto pair : persistent_pairs_) { - // Special case on windows, inf is "1.#INF" (cf. unitary tests and R package TDA) - if (has_infinity && cpx_->filtration(get<1>(pair)) == std::numeric_limits::infinity()) { - ostream << get<2>(pair) << " " << cpx_->dimension(get<0>(pair)) << " " - << cpx_->filtration(get<0>(pair)) << " inf " << std::endl; - } else { - ostream << get<2>(pair) << " " << cpx_->dimension(get<0>(pair)) << " " - << cpx_->filtration(get<0>(pair)) << " " - << cpx_->filtration(get<1>(pair)) << " " << std::endl; - } + ostream << get<2>(pair) << " " << cpx_->dimension(get<0>(pair)) << " " + << cpx_->filtration(get<0>(pair)) << " " + << cpx_->filtration(get<1>(pair)) << " " << std::endl; } } @@ -584,15 +578,9 @@ class Persistent_cohomology { std::sort(std::begin(persistent_pairs_), std::end(persistent_pairs_), cmp); bool has_infinity = std::numeric_limits::has_infinity; for (auto pair : persistent_pairs_) { - // Special case on windows, inf is "1.#INF" - if (has_infinity && cpx_->filtration(get<1>(pair)) == std::numeric_limits::infinity()) { - diagram_out << cpx_->dimension(get<0>(pair)) << " " - << cpx_->filtration(get<0>(pair)) << " inf" << std::endl; - } else { - diagram_out << cpx_->dimension(get<0>(pair)) << " " - << cpx_->filtration(get<0>(pair)) << " " - << cpx_->filtration(get<1>(pair)) << std::endl; - } + diagram_out << cpx_->dimension(get<0>(pair)) << " " + << cpx_->filtration(get<0>(pair)) << " " + << cpx_->filtration(get<1>(pair)) << std::endl; } } -- cgit v1.2.3 From b262406b0a75e39276c11f70ef1174981aa31b51 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 17 Mar 2020 17:57:17 +0100 Subject: Remove thread_local workaround --- src/Alpha_complex/include/gudhi/Alpha_complex_3d.h | 5 +-- src/Nerve_GIC/include/gudhi/GIC.h | 14 +------- .../include/gudhi/Persistent_cohomology.h | 5 +-- src/Simplex_tree/include/gudhi/Simplex_tree.h | 12 ++----- src/cmake/modules/GUDHI_compilation_flags.cmake | 37 ---------------------- src/python/CMakeLists.txt | 10 ------ 6 files changed, 5 insertions(+), 78 deletions(-) diff --git a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h index 7f96c94c..1486cefd 100644 --- a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h +++ b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h @@ -61,10 +61,7 @@ namespace Gudhi { namespace alpha_complex { -#ifdef GUDHI_CAN_USE_CXX11_THREAD_LOCAL -thread_local -#endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL - double RELATIVE_PRECISION_OF_TO_DOUBLE = 0.00001; +thread_local double RELATIVE_PRECISION_OF_TO_DOUBLE = 0.00001; // Value_from_iterator returns the filtration value from an iterator on alpha shapes values // diff --git a/src/Nerve_GIC/include/gudhi/GIC.h b/src/Nerve_GIC/include/gudhi/GIC.h index 2a6d4788..9a4c813d 100644 --- a/src/Nerve_GIC/include/gudhi/GIC.h +++ b/src/Nerve_GIC/include/gudhi/GIC.h @@ -139,19 +139,9 @@ class Cover_complex { for (boost::tie(ei, ei_end) = boost::edges(G); ei != ei_end; ++ei) boost::remove_edge(*ei, G); } - // Thread local is not available on XCode version < V.8 - // If not available, random engine is a class member. -#ifndef GUDHI_CAN_USE_CXX11_THREAD_LOCAL - std::default_random_engine re; -#endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL - // Find random number in [0,1]. double GetUniform() { - // Thread local is not available on XCode version < V.8 - // If available, random engine is defined for each thread. -#ifdef GUDHI_CAN_USE_CXX11_THREAD_LOCAL thread_local std::default_random_engine re; -#endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL std::uniform_real_distribution Dist(0, 1); return Dist(re); } @@ -456,9 +446,7 @@ class Cover_complex { if (distances.size() == 0) compute_pairwise_distances(distance); - // This cannot be parallelized if thread_local is not defined - // thread_local is not defined for XCode < v.8 - #if defined(GUDHI_USE_TBB) && defined(GUDHI_CAN_USE_CXX11_THREAD_LOCAL) + #ifdef GUDHI_USE_TBB std::mutex deltamutex; tbb::parallel_for(0, N, [&](int i){ std::vector samples(m); diff --git a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h index b1ded5ae..ca4bc10d 100644 --- a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h +++ b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h @@ -288,10 +288,7 @@ class Persistent_cohomology { // with multiplicity. We used to sum the coefficients directly in // annotations_in_boundary by using a map, we now do it later. typedef std::pair annotation_t; -#ifdef GUDHI_CAN_USE_CXX11_THREAD_LOCAL - thread_local -#endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL - std::vector annotations_in_boundary; + thread_local std::vector annotations_in_boundary; annotations_in_boundary.clear(); int sign = 1 - 2 * (dim_sigma % 2); // \in {-1,1} provides the sign in the // alternate sum in the boundary. diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index b7fb9002..2adc8354 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -765,12 +765,7 @@ class Simplex_tree { if (first == last) return { null_simplex(), true }; // FIXME: false would make more sense to me. - // Copy before sorting - // Thread local is not available on XCode version < V.8 - It will slow down computation -#ifdef GUDHI_CAN_USE_CXX11_THREAD_LOCAL - thread_local -#endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL - std::vector copy; + thread_local std::vector copy; copy.clear(); copy.insert(copy.end(), first, last); std::sort(copy.begin(), copy.end()); @@ -1133,10 +1128,7 @@ class Simplex_tree { Dictionary_it next = siblings->members().begin(); ++next; -#ifdef GUDHI_CAN_USE_CXX11_THREAD_LOCAL - thread_local -#endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL - std::vector > inter; + thread_local std::vector > inter; for (Dictionary_it s_h = siblings->members().begin(); s_h != siblings->members().end(); ++s_h, ++next) { Simplex_handle root_sh = find_vertex(s_h->first); diff --git a/src/cmake/modules/GUDHI_compilation_flags.cmake b/src/cmake/modules/GUDHI_compilation_flags.cmake index 34c2e065..567fbc40 100644 --- a/src/cmake/modules/GUDHI_compilation_flags.cmake +++ b/src/cmake/modules/GUDHI_compilation_flags.cmake @@ -1,7 +1,6 @@ # This files manage compilation flags required by GUDHI include(TestCXXAcceptsFlag) -include(CheckCXXSourceCompiles) # add a compiler flag only if it is accepted macro(add_cxx_compiler_flag _flag) @@ -12,32 +11,6 @@ macro(add_cxx_compiler_flag _flag) endif() endmacro() -function(can_cgal_use_cxx11_thread_local) - # This is because of https://github.com/CGAL/cgal/blob/master/Installation/include/CGAL/tss.h - # CGAL is using boost thread if thread_local is not ready (requires XCode 8 for Mac). - # The test in https://github.com/CGAL/cgal/blob/master/Installation/include/CGAL/config.h - # #if __has_feature(cxx_thread_local) || \ - # ( (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L ) || \ - # ( _MSC_VER >= 1900 ) - # #define CGAL_CAN_USE_CXX11_THREAD_LOCAL - # #endif - set(CGAL_CAN_USE_CXX11_THREAD_LOCAL " - int main() { - #ifndef __has_feature - #define __has_feature(x) 0 // Compatibility with non-clang compilers. - #endif - #if __has_feature(cxx_thread_local) || \ - ( (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L ) || \ - ( _MSC_VER >= 1900 ) - bool has_feature_thread_local = true; - #else - // Explicit error of compilation for CMake test purpose - has_feature_thread_local is not defined - #endif - bool result = has_feature_thread_local; - } ") - check_cxx_source_compiles("${CGAL_CAN_USE_CXX11_THREAD_LOCAL}" CGAL_CAN_USE_CXX11_THREAD_LOCAL_RESULT) -endfunction() - set (CMAKE_CXX_STANDARD 14) enable_testing() @@ -58,16 +31,6 @@ if (DEBUG_TRACES) add_definitions(-DDEBUG_TRACES) endif() -set(GUDHI_CAN_USE_CXX11_THREAD_LOCAL " - int main() { - thread_local int result = 0; - return result; - } ") -check_cxx_source_compiles("${GUDHI_CAN_USE_CXX11_THREAD_LOCAL}" GUDHI_CAN_USE_CXX11_THREAD_LOCAL_RESULT) -if (GUDHI_CAN_USE_CXX11_THREAD_LOCAL_RESULT) - add_definitions(-DGUDHI_CAN_USE_CXX11_THREAD_LOCAL) -endif() - if(CMAKE_BUILD_TYPE MATCHES Debug) message("++ Debug compilation flags are: ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_DEBUG}") else() diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 22af3ec9..f00966a5 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -128,16 +128,6 @@ if(PYTHONINTERP_FOUND) endif () if(CGAL_FOUND) - can_cgal_use_cxx11_thread_local() - if (NOT CGAL_CAN_USE_CXX11_THREAD_LOCAL_RESULT) - if(CMAKE_BUILD_TYPE MATCHES Debug) - add_GUDHI_PYTHON_lib("${Boost_THREAD_LIBRARY_DEBUG}") - else() - add_GUDHI_PYTHON_lib("${Boost_THREAD_LIBRARY_RELEASE}") - endif() - message("** Add Boost ${Boost_LIBRARY_DIRS}") - set(GUDHI_PYTHON_LIBRARY_DIRS "${GUDHI_PYTHON_LIBRARY_DIRS}'${Boost_LIBRARY_DIRS}', ") - endif() # Add CGAL compilation args if(CGAL_HEADER_ONLY) add_gudhi_debug_info("CGAL header only version ${CGAL_VERSION}") -- cgit v1.2.3