From 64199fd8037556f135f90102ba8270cccf9d3e60 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sat, 7 Mar 2020 01:08:10 +0100 Subject: persistence generators for lower-star and flag filtrations --- src/python/gudhi/simplex_tree.pxd | 2 + src/python/gudhi/simplex_tree.pyx | 55 ++++++++ .../include/Persistent_cohomology_interface.h | 138 ++++++++++++++++----- 3 files changed, 167 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 96d14079..4e435c67 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -53,3 +53,5 @@ cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": vector[pair[double,double]] intervals_in_dimension(int dimension) void write_output_diagram(string diagram_file_name) vector[pair[vector[int], vector[int]]] persistence_pairs() + pair[vector[vector[int]], vector[vector[int]]] lower_star_generators() + pair[vector[vector[int]], vector[vector[int]]] flag_generators() diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index b18627c4..1c9b9cf1 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -514,3 +514,58 @@ cdef class SimplexTree: else: print("intervals_in_dim function requires persistence function" " to be launched first.") + + def lower_star_persistence_generators(self): + """Assuming this is a lower-star filtration, this function returns the persistence pairs, + where each simplex is replaced with the vertex that gave it its filtration value. + + :returns: first the regular persistence pairs, grouped by dimension, with one vertex per extremity, + and second the essential features, grouped by dimension, with one vertex each + :rtype: Tuple[List[numpy.array[int] of shape (n,2)], List[numpy.array[int] of shape (m,)]] + + :note: intervals_in_dim function requires + :func:`persistence()` + function to be launched first. + """ + if self.pcohptr != NULL: + gen = self.pcohptr.lower_star_generators() + normal = [np_array(d).reshape(-1,2) for d in gen.first] + infinite = [np_array(d) for d in gen.second] + return (normal, infinite) + else: + print("lower_star_persistence_generators() requires that persistence() be called first.") + + def flag_persistence_generators(self): + """Assuming this is a flag complex, this function returns the persistence pairs, + where each simplex is replaced with the vertices of the edges that gave it its filtration value. + + :returns: first the regular persistence pairs of dimension 0, with one vertex for birth and two for death; + then the other regular persistence pairs, grouped by dimension, with 2 vertices per extremity; + then the connected components, with one vertex each; + finally the other essential features, grouped by dimension, with 2 vertices for birth. + :rtype: Tuple[List[numpy.array[int] of shape (n,3)], List[numpy.array[int] of shape (m,4)], List[numpy.array[int] of shape (l,)], List[numpy.array[int] of shape (k,2)]] + + :note: intervals_in_dim function requires + :func:`persistence()` + function to be launched first. + """ + if self.pcohptr != NULL: + gen = self.pcohptr.flag_generators() + if len(gen.first) == 0: + normal0 = np_array([]) + normals = np_array([]) + else: + l = iter(gen.first) + normal0 = np_array(next(l)).reshape(-1,3) + normals = [np_array(d).reshape(-1,4) for d in l] + if len(gen.second) == 0: + infinite0 = np_array([]) + infinites = np_array([]) + else: + l = iter(gen.second) + infinite0 = np_array(next(l)) + infinites = [np_array(d).reshape(-1,3) for d in l] + + return (normal0, normals, infinite0, infinites) + else: + print("lower_star_persistence_generators() requires that persistence() be called first.") diff --git a/src/python/include/Persistent_cohomology_interface.h b/src/python/include/Persistent_cohomology_interface.h index 8c79e6f3..6e9aac52 100644 --- a/src/python/include/Persistent_cohomology_interface.h +++ b/src/python/include/Persistent_cohomology_interface.h @@ -23,61 +23,55 @@ template class Persistent_cohomology_interface : public persistent_cohomology::Persistent_cohomology { private: + typedef persistent_cohomology::Persistent_cohomology Base; /* * Compare two intervals by dimension, then by length. */ struct cmp_intervals_by_dim_then_length { - explicit cmp_intervals_by_dim_then_length(FilteredComplex * sc) - : sc_(sc) { } - template bool operator()(const Persistent_interval & p1, const Persistent_interval & p2) { - if (sc_->dimension(get < 0 > (p1)) == sc_->dimension(get < 0 > (p2))) - return (sc_->filtration(get < 1 > (p1)) - sc_->filtration(get < 0 > (p1)) - > sc_->filtration(get < 1 > (p2)) - sc_->filtration(get < 0 > (p2))); + if (std::get<0>(p1) == std::get<0>(p2)) { + auto& i1 = std::get<1>(p1); + auto& i2 = std::get<1>(p2); + return std::get<1>(i1) - std::get<0>(i1) > std::get<1>(i2) - std::get<0>(i2); + } else - return (sc_->dimension(get < 0 > (p1)) > sc_->dimension(get < 0 > (p2))); + return (std::get<0>(p1) > std::get<0>(p2)); + // Why does this sort by decreasing dimension? } - FilteredComplex* sc_; }; public: Persistent_cohomology_interface(FilteredComplex* stptr) - : persistent_cohomology::Persistent_cohomology(*stptr), + : Base(*stptr), stptr_(stptr) { } Persistent_cohomology_interface(FilteredComplex* stptr, bool persistence_dim_max) - : persistent_cohomology::Persistent_cohomology(*stptr, persistence_dim_max), + : Base(*stptr, persistence_dim_max), stptr_(stptr) { } std::vector>> get_persistence(int homology_coeff_field, double min_persistence) { - persistent_cohomology::Persistent_cohomology::init_coefficients(homology_coeff_field); - persistent_cohomology::Persistent_cohomology::compute_persistent_cohomology(min_persistence); - - // Custom sort and output persistence - cmp_intervals_by_dim_then_length cmp(stptr_); - auto persistent_pairs = persistent_cohomology::Persistent_cohomology::get_persistent_pairs(); - std::sort(std::begin(persistent_pairs), std::end(persistent_pairs), cmp); + Base::init_coefficients(homology_coeff_field); + Base::compute_persistent_cohomology(min_persistence); + auto const& persistent_pairs = Base::get_persistent_pairs(); std::vector>> persistence; + persistence.reserve(persistent_pairs.size()); for (auto pair : persistent_pairs) { - persistence.push_back(std::make_pair(stptr_->dimension(get<0>(pair)), - std::make_pair(stptr_->filtration(get<0>(pair)), - stptr_->filtration(get<1>(pair))))); + persistence.emplace_back(stptr_->dimension(get<0>(pair)), + std::make_pair(stptr_->filtration(get<0>(pair)), + stptr_->filtration(get<1>(pair)))); } + // Custom sort and output persistence + cmp_intervals_by_dim_then_length cmp; + std::sort(std::begin(persistence), std::end(persistence), cmp); return persistence; } std::vector, std::vector>> persistence_pairs() { - auto pairs = persistent_cohomology::Persistent_cohomology::get_persistent_pairs(); - std::vector, std::vector>> persistence_pairs; + auto const& pairs = Base::get_persistent_pairs(); persistence_pairs.reserve(pairs.size()); for (auto pair : pairs) { std::vector birth; @@ -89,16 +83,104 @@ persistent_cohomology::Persistent_cohomology death; if (get<1>(pair) != stptr_->null_simplex()) { + death.reserve(birth.size()+1); for (auto vertex : stptr_->simplex_vertex_range(get<1>(pair))) { death.push_back(vertex); } } - persistence_pairs.push_back(std::make_pair(birth, death)); + persistence_pairs.emplace_back(std::move(birth), std::move(death)); } return persistence_pairs; } + // TODO: (possibly at the python level) + // - an option to ignore intervals of length 0? + // - an option to return only some of those vectors? + typedef std::pair>, std::vector>> Generators; + + Generators lower_star_generators() { + Generators out; + // diags[i] should be interpreted as vector> + auto& diags = out.first; + // diagsinf[i] should be interpreted as vector + auto& diagsinf = out.second; + for (auto pair : Base::get_persistent_pairs()) { + auto s = std::get<0>(pair); + auto t = std::get<1>(pair); + int dim = stptr_->dimension(s); + auto v = stptr_->vertex_with_same_filtration(s); + if(t == stptr_->null_simplex()) { + while(diagsinf.size() < dim+1) diagsinf.emplace_back(); + diagsinf[dim].push_back(v); + } else { + while(diags.size() < dim+1) diags.emplace_back(); + auto w = stptr_->vertex_with_same_filtration(t); + diags[dim].push_back(v); + diags[dim].push_back(w); + } + } + return out; + } + + Generators flag_generators() { + Generators out; + // diags[0] should be interpreted as vector> and other diags[i] as vector> + auto& diags = out.first; + // diagsinf[0] should be interpreted as vector and other diagsinf[i] as vector> + auto& diagsinf = out.second; + for (auto pair : Base::get_persistent_pairs()) { + auto s = std::get<0>(pair); + auto t = std::get<1>(pair); + int dim = stptr_->dimension(s); + bool infinite = t == stptr_->null_simplex(); + if(infinite) { + if(dim == 0) { + auto v = *std::begin(stptr_->simplex_vertex_range(s)); + if(diagsinf.size()==0)diagsinf.emplace_back(); + diagsinf[0].push_back(v); + } else { + auto e = stptr_->edge_with_same_filtration(s); + auto&& e_vertices = stptr_->simplex_vertex_range(e); + auto i = std::begin(e_vertices); + auto v1 = *i; + auto v2 = *++i; + GUDHI_CHECK(++i==std::end(e_vertices), "must be an edge"); + while(diagsinf.size() < dim+1) diagsinf.emplace_back(); + diagsinf[dim].push_back(v1); + diagsinf[dim].push_back(v2); + } + } else { + auto et = stptr_->edge_with_same_filtration(t); + auto&& et_vertices = stptr_->simplex_vertex_range(et); + auto it = std::begin(et_vertices); + auto w1 = *it; + auto w2 = *++it; + GUDHI_CHECK(++it==std::end(et_vertices), "must be an edge"); + if(dim == 0) { + auto v = *std::begin(stptr_->simplex_vertex_range(s)); + if(diags.size()==0)diags.emplace_back(); + diags[0].push_back(v); + diags[0].push_back(w1); + diags[0].push_back(w2); + } else { + auto es = stptr_->edge_with_same_filtration(s); + auto&& es_vertices = stptr_->simplex_vertex_range(es); + auto is = std::begin(es_vertices); + auto v1 = *is; + auto v2 = *++is; + GUDHI_CHECK(++is==std::end(es_vertices), "must be an edge"); + while(diags.size() < dim+1) diags.emplace_back(); + diags[dim].push_back(v1); + diags[dim].push_back(v2); + diags[dim].push_back(w1); + diags[dim].push_back(w2); + } + } + } + return out; + } + private: // A copy FilteredComplex* stptr_; -- cgit v1.2.3 From 35e08b30836fb0c419c0377eaf51d2a3b16e7670 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sat, 7 Mar 2020 14:05:05 +0100 Subject: min_persistence for generators --- src/python/gudhi/simplex_tree.pxd | 4 +-- src/python/gudhi/simplex_tree.pyx | 36 +++++++++++++--------- .../include/Persistent_cohomology_interface.h | 10 ++++-- 3 files changed, 30 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 4e435c67..53e2bbc9 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -53,5 +53,5 @@ cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": vector[pair[double,double]] intervals_in_dimension(int dimension) void write_output_diagram(string diagram_file_name) vector[pair[vector[int], vector[int]]] persistence_pairs() - pair[vector[vector[int]], vector[vector[int]]] lower_star_generators() - pair[vector[vector[int]], vector[vector[int]]] flag_generators() + pair[vector[vector[int]], vector[vector[int]]] lower_star_generators(double) + pair[vector[vector[int]], vector[vector[int]]] flag_generators(double) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 1c9b9cf1..3f582ac9 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -395,7 +395,7 @@ cdef class SimplexTree: :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is 0.0. - Sets min_persistence to -1.0 to see all values. + Set min_persistence to -1.0 to see all values. :type min_persistence: float. :param persistence_dim_max: If true, the persistent homology for the maximal dimension in the complex is computed. If false, it is @@ -515,42 +515,48 @@ cdef class SimplexTree: print("intervals_in_dim function requires persistence function" " to be launched first.") - def lower_star_persistence_generators(self): + def lower_star_persistence_generators(self, min_persistence=0.): """Assuming this is a lower-star filtration, this function returns the persistence pairs, where each simplex is replaced with the vertex that gave it its filtration value. - :returns: first the regular persistence pairs, grouped by dimension, with one vertex per extremity, + :param min_persistence: The minimum persistence value to take into + account (strictly greater than min_persistence). Default value is + 0.0. + Set min_persistence to -1.0 to see all values. + :type min_persistence: float. + :returns: First the regular persistence pairs, grouped by dimension, with one vertex per extremity, and second the essential features, grouped by dimension, with one vertex each :rtype: Tuple[List[numpy.array[int] of shape (n,2)], List[numpy.array[int] of shape (m,)]] - :note: intervals_in_dim function requires - :func:`persistence()` - function to be launched first. + :note: lower_star_persistence_generators requires that `persistence()` be called first. """ if self.pcohptr != NULL: - gen = self.pcohptr.lower_star_generators() + gen = self.pcohptr.lower_star_generators(min_persistence) normal = [np_array(d).reshape(-1,2) for d in gen.first] infinite = [np_array(d) for d in gen.second] return (normal, infinite) else: print("lower_star_persistence_generators() requires that persistence() be called first.") - def flag_persistence_generators(self): + def flag_persistence_generators(self, min_persistence=0.): """Assuming this is a flag complex, this function returns the persistence pairs, where each simplex is replaced with the vertices of the edges that gave it its filtration value. - :returns: first the regular persistence pairs of dimension 0, with one vertex for birth and two for death; + :param min_persistence: The minimum persistence value to take into + account (strictly greater than min_persistence). Default value is + 0.0. + Set min_persistence to -1.0 to see all values. + :type min_persistence: float. + :returns: First the regular persistence pairs of dimension 0, with one vertex for birth and two for death; then the other regular persistence pairs, grouped by dimension, with 2 vertices per extremity; then the connected components, with one vertex each; finally the other essential features, grouped by dimension, with 2 vertices for birth. - :rtype: Tuple[List[numpy.array[int] of shape (n,3)], List[numpy.array[int] of shape (m,4)], List[numpy.array[int] of shape (l,)], List[numpy.array[int] of shape (k,2)]] + :rtype: Tuple[numpy.array[int] of shape (n,3), List[numpy.array[int] of shape (m,4)], numpy.array[int] of shape (l,), List[numpy.array[int] of shape (k,2)]] - :note: intervals_in_dim function requires - :func:`persistence()` - function to be launched first. + :note: flag_persistence_generators requires that `persistence()` be called first. """ if self.pcohptr != NULL: - gen = self.pcohptr.flag_generators() + gen = self.pcohptr.flag_generators(min_persistence) if len(gen.first) == 0: normal0 = np_array([]) normals = np_array([]) @@ -568,4 +574,4 @@ cdef class SimplexTree: return (normal0, normals, infinite0, infinites) else: - print("lower_star_persistence_generators() requires that persistence() be called first.") + print("flag_persistence_generators() requires that persistence() be called first.") diff --git a/src/python/include/Persistent_cohomology_interface.h b/src/python/include/Persistent_cohomology_interface.h index 6e9aac52..8e721fc0 100644 --- a/src/python/include/Persistent_cohomology_interface.h +++ b/src/python/include/Persistent_cohomology_interface.h @@ -95,11 +95,10 @@ persistent_cohomology::Persistent_cohomology>, std::vector>> Generators; - Generators lower_star_generators() { + Generators lower_star_generators(double min_persistence) { Generators out; // diags[i] should be interpreted as vector> auto& diags = out.first; @@ -108,6 +107,8 @@ persistent_cohomology::Persistent_cohomology(pair); auto t = std::get<1>(pair); + if(stptr_->filtration(t) - stptr_->filtration(s) <= min_persistence) + continue; int dim = stptr_->dimension(s); auto v = stptr_->vertex_with_same_filtration(s); if(t == stptr_->null_simplex()) { @@ -123,7 +124,8 @@ persistent_cohomology::Persistent_cohomology> and other diags[i] as vector> auto& diags = out.first; @@ -132,6 +134,8 @@ persistent_cohomology::Persistent_cohomology(pair); auto t = std::get<1>(pair); + if(stptr_->filtration(t) - stptr_->filtration(s) <= min_persistence) + continue; int dim = stptr_->dimension(s); bool infinite = t == stptr_->null_simplex(); if(infinite) { -- cgit v1.2.3 From 08be68c1fb3c05a35d738eab53712ec6cb4d1ad5 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sat, 7 Mar 2020 14:14:45 +0100 Subject: [ci skip] Comment --- src/python/include/Persistent_cohomology_interface.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/python/include/Persistent_cohomology_interface.h b/src/python/include/Persistent_cohomology_interface.h index 8e721fc0..22d6f654 100644 --- a/src/python/include/Persistent_cohomology_interface.h +++ b/src/python/include/Persistent_cohomology_interface.h @@ -125,6 +125,7 @@ persistent_cohomology::Persistent_cohomology> and other diags[i] as vector> -- cgit v1.2.3 From 55c1385419edd4e152df219dfff596d2631367f1 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sun, 8 Mar 2020 11:15:04 +0100 Subject: Typo in shape of array --- src/python/gudhi/simplex_tree.pyx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 3f582ac9..d5f642d1 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -8,6 +8,7 @@ # - YYYY/MM Author: Description of the modification from libc.stdint cimport intptr_t +import numpy from numpy import array as np_array cimport simplex_tree @@ -558,19 +559,19 @@ cdef class SimplexTree: if self.pcohptr != NULL: gen = self.pcohptr.flag_generators(min_persistence) if len(gen.first) == 0: - normal0 = np_array([]) - normals = np_array([]) + normal0 = numpy.empty((0,3)) + normals = [] else: l = iter(gen.first) normal0 = np_array(next(l)).reshape(-1,3) normals = [np_array(d).reshape(-1,4) for d in l] if len(gen.second) == 0: - infinite0 = np_array([]) - infinites = np_array([]) + infinite0 = numpy.empty(0) + infinites = [] else: l = iter(gen.second) infinite0 = np_array(next(l)) - infinites = [np_array(d).reshape(-1,3) for d in l] + infinites = [np_array(d).reshape(-1,2) for d in l] return (normal0, normals, infinite0, infinites) else: -- cgit v1.2.3 From 0b4eddeb0d53d465016d5eb913b382123bc5b891 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Mon, 23 Mar 2020 18:35:07 +0100 Subject: Avoid consecutive push_back --- src/python/include/Persistent_cohomology_interface.h | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/python/include/Persistent_cohomology_interface.h b/src/python/include/Persistent_cohomology_interface.h index 22d6f654..89ff5137 100644 --- a/src/python/include/Persistent_cohomology_interface.h +++ b/src/python/include/Persistent_cohomology_interface.h @@ -117,8 +117,8 @@ persistent_cohomology::Persistent_cohomologyvertex_with_same_filtration(t); - diags[dim].push_back(v); - diags[dim].push_back(w); + auto& d = diags[dim]; + d.insert(d.end(), { v, w }); } } return out; @@ -152,8 +152,8 @@ persistent_cohomology::Persistent_cohomologyedge_with_same_filtration(t); @@ -165,9 +165,8 @@ persistent_cohomology::Persistent_cohomologysimplex_vertex_range(s)); if(diags.size()==0)diags.emplace_back(); - diags[0].push_back(v); - diags[0].push_back(w1); - diags[0].push_back(w2); + auto& d = diags[0]; + d.insert(d.end(), { v, w1, w2 }); } else { auto es = stptr_->edge_with_same_filtration(s); auto&& es_vertices = stptr_->simplex_vertex_range(es); @@ -176,10 +175,8 @@ persistent_cohomology::Persistent_cohomology Date: Mon, 23 Mar 2020 18:52:49 +0100 Subject: Reuse vector Reuse + copy should be slightly faster than regrowing each time (and moving) --- src/python/include/Persistent_cohomology_interface.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/python/include/Persistent_cohomology_interface.h b/src/python/include/Persistent_cohomology_interface.h index 89ff5137..3ce40af5 100644 --- a/src/python/include/Persistent_cohomology_interface.h +++ b/src/python/include/Persistent_cohomology_interface.h @@ -73,15 +73,17 @@ persistent_cohomology::Persistent_cohomology, std::vector>> persistence_pairs; auto const& pairs = Base::get_persistent_pairs(); persistence_pairs.reserve(pairs.size()); + std::vector birth; + std::vector death; for (auto pair : pairs) { - std::vector birth; + birth.clear(); if (get<0>(pair) != stptr_->null_simplex()) { for (auto vertex : stptr_->simplex_vertex_range(get<0>(pair))) { birth.push_back(vertex); } } - std::vector death; + death.clear(); if (get<1>(pair) != stptr_->null_simplex()) { death.reserve(birth.size()+1); for (auto vertex : stptr_->simplex_vertex_range(get<1>(pair))) { @@ -89,7 +91,7 @@ persistent_cohomology::Persistent_cohomology Date: Mon, 23 Mar 2020 21:54:56 +0100 Subject: Add test --- src/python/test/test_simplex_generators.py | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100755 src/python/test/test_simplex_generators.py (limited to 'src') diff --git a/src/python/test/test_simplex_generators.py b/src/python/test/test_simplex_generators.py new file mode 100755 index 00000000..efb5f8e3 --- /dev/null +++ b/src/python/test/test_simplex_generators.py @@ -0,0 +1,57 @@ +""" 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): Marc Glisse + + Copyright (C) 2020 Inria + + Modification(s): + - YYYY/MM Author: Description of the modification +""" + +import gudhi +import numpy as np + + +def test_flag_generators(): + pts = np.array([[0, 0], [0, 1.01], [1, 0], [1.02, 1.03], [100, 0], [100, 3.01], [103, 0], [103.02, 3.03]]) + r = gudhi.RipsComplex(pts, max_edge_length=4) + st = r.create_simplex_tree(max_dimension=50) + st.persistence() + g = st.flag_persistence_generators() + assert np.array_equal(g[0], [[2, 2, 0], [1, 1, 0], [3, 3, 1], [6, 6, 4], [5, 5, 4], [7, 7, 5]]) + assert len(g[1]) == 1 + assert np.array_equal(g[1][0], [[3, 2, 2, 1]]) + assert np.array_equal(g[2], [0, 4]) + assert len(g[3]) == 1 + assert np.array_equal(g[3][0], [[7, 6]]) + + +def test_lower_star_generators(): + st = gudhi.SimplexTree() + st.insert([0, 1, 2], -10) + st.insert([0, 3], -10) + st.insert([1, 3], -10) + st.assign_filtration([2], -1) + st.assign_filtration([3], 0) + st.assign_filtration([0], 1) + st.assign_filtration([1], 2) + st.make_filtration_non_decreasing() + st.persistence(min_persistence=-1) + g = st.lower_star_persistence_generators(min_persistence=-1) + assert len(g[0]) == 2 + assert np.array_equal(g[0][0], [[0, 0], [3, 0], [1, 1]]) + assert np.array_equal(g[0][1], [[1, 1]]) + assert len(g[1]) == 2 + assert np.array_equal(g[1][0], [2]) + assert np.array_equal(g[1][1], [1]) + + +def test_empty(): + st = gudhi.SimplexTree() + st.persistence() + assert st.lower_star_persistence_generators() == ([], []) + g = st.flag_persistence_generators() + assert np.array_equal(g[0], np.empty((0, 3))) + assert g[1] == [] + assert np.array_equal(g[2], []) + assert g[3] == [] -- cgit v1.2.3 From ec4a9583adaa73c01b05a4b30425581ed7256379 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 24 Mar 2020 14:50:53 +0100 Subject: Remove min_persistence from generators It is supposed to be handled in persistence() already. --- src/python/CMakeLists.txt | 1 + src/python/gudhi/simplex_tree.pxd | 4 ++-- src/python/gudhi/simplex_tree.pyx | 18 ++++-------------- src/python/include/Persistent_cohomology_interface.h | 8 ++------ src/python/test/test_simplex_generators.py | 2 +- 5 files changed, 10 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index f00966a5..fb219884 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -374,6 +374,7 @@ if(PYTHONINTERP_FOUND) ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example/simplex_tree_example.py) add_gudhi_py_test(test_simplex_tree) + add_gudhi_py_test(test_simplex_generators) # Witness add_test(NAME witness_complex_from_nearest_landmark_table_py_test diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 44789365..4038b41d 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -75,5 +75,5 @@ cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": vector[pair[double,double]] intervals_in_dimension(int dimension) void write_output_diagram(string diagram_file_name) vector[pair[vector[int], vector[int]]] persistence_pairs() - pair[vector[vector[int]], vector[vector[int]]] lower_star_generators(double) - pair[vector[vector[int]], vector[vector[int]]] flag_generators(double) + pair[vector[vector[int]], vector[vector[int]]] lower_star_generators() + pair[vector[vector[int]], vector[vector[int]]] flag_generators() diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index faa9f9d8..beb40bc4 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -526,15 +526,10 @@ cdef class SimplexTree: print("intervals_in_dim function requires persistence function" " to be launched first.") - def lower_star_persistence_generators(self, min_persistence=0.): + def lower_star_persistence_generators(self): """Assuming this is a lower-star filtration, this function returns the persistence pairs, where each simplex is replaced with the vertex that gave it its filtration value. - :param min_persistence: The minimum persistence value to take into - account (strictly greater than min_persistence). Default value is - 0.0. - Set min_persistence to -1.0 to see all values. - :type min_persistence: float. :returns: First the regular persistence pairs, grouped by dimension, with one vertex per extremity, and second the essential features, grouped by dimension, with one vertex each :rtype: Tuple[List[numpy.array[int] of shape (n,2)], List[numpy.array[int] of shape (m,)]] @@ -542,22 +537,17 @@ cdef class SimplexTree: :note: lower_star_persistence_generators requires that `persistence()` be called first. """ if self.pcohptr != NULL: - gen = self.pcohptr.lower_star_generators(min_persistence) + gen = self.pcohptr.lower_star_generators() normal = [np_array(d).reshape(-1,2) for d in gen.first] infinite = [np_array(d) for d in gen.second] return (normal, infinite) else: print("lower_star_persistence_generators() requires that persistence() be called first.") - def flag_persistence_generators(self, min_persistence=0.): + def flag_persistence_generators(self): """Assuming this is a flag complex, this function returns the persistence pairs, where each simplex is replaced with the vertices of the edges that gave it its filtration value. - :param min_persistence: The minimum persistence value to take into - account (strictly greater than min_persistence). Default value is - 0.0. - Set min_persistence to -1.0 to see all values. - :type min_persistence: float. :returns: First the regular persistence pairs of dimension 0, with one vertex for birth and two for death; then the other regular persistence pairs, grouped by dimension, with 2 vertices per extremity; then the connected components, with one vertex each; @@ -567,7 +557,7 @@ cdef class SimplexTree: :note: flag_persistence_generators requires that `persistence()` be called first. """ if self.pcohptr != NULL: - gen = self.pcohptr.flag_generators(min_persistence) + gen = self.pcohptr.flag_generators() if len(gen.first) == 0: normal0 = numpy.empty((0,3)) normals = [] diff --git a/src/python/include/Persistent_cohomology_interface.h b/src/python/include/Persistent_cohomology_interface.h index 3ce40af5..3074389c 100644 --- a/src/python/include/Persistent_cohomology_interface.h +++ b/src/python/include/Persistent_cohomology_interface.h @@ -100,7 +100,7 @@ persistent_cohomology::Persistent_cohomology>, std::vector>> Generators; - Generators lower_star_generators(double min_persistence) { + Generators lower_star_generators() { Generators out; // diags[i] should be interpreted as vector> auto& diags = out.first; @@ -109,8 +109,6 @@ persistent_cohomology::Persistent_cohomology(pair); auto t = std::get<1>(pair); - if(stptr_->filtration(t) - stptr_->filtration(s) <= min_persistence) - continue; int dim = stptr_->dimension(s); auto v = stptr_->vertex_with_same_filtration(s); if(t == stptr_->null_simplex()) { @@ -128,7 +126,7 @@ persistent_cohomology::Persistent_cohomology> and other diags[i] as vector> auto& diags = out.first; @@ -137,8 +135,6 @@ persistent_cohomology::Persistent_cohomology(pair); auto t = std::get<1>(pair); - if(stptr_->filtration(t) - stptr_->filtration(s) <= min_persistence) - continue; int dim = stptr_->dimension(s); bool infinite = t == stptr_->null_simplex(); if(infinite) { diff --git a/src/python/test/test_simplex_generators.py b/src/python/test/test_simplex_generators.py index efb5f8e3..e3bdc094 100755 --- a/src/python/test/test_simplex_generators.py +++ b/src/python/test/test_simplex_generators.py @@ -37,7 +37,7 @@ def test_lower_star_generators(): st.assign_filtration([1], 2) st.make_filtration_non_decreasing() st.persistence(min_persistence=-1) - g = st.lower_star_persistence_generators(min_persistence=-1) + g = st.lower_star_persistence_generators() assert len(g[0]) == 2 assert np.array_equal(g[0][0], [[0, 0], [3, 0], [1, 1]]) assert np.array_equal(g[0][1], [[1, 1]]) -- cgit v1.2.3 From d5c8dc1ba4d00ead5875b97e164d07f6180526b0 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 24 Mar 2020 20:31:05 +0100 Subject: print -> assert --- src/python/gudhi/simplex_tree.pyx | 47 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index beb40bc4..dcf1b46e 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -536,13 +536,11 @@ cdef class SimplexTree: :note: lower_star_persistence_generators requires that `persistence()` be called first. """ - if self.pcohptr != NULL: - gen = self.pcohptr.lower_star_generators() - normal = [np_array(d).reshape(-1,2) for d in gen.first] - infinite = [np_array(d) for d in gen.second] - return (normal, infinite) - else: - print("lower_star_persistence_generators() requires that persistence() be called first.") + assert self.pcohptr != NULL, "lower_star_persistence_generators() requires that persistence() be called first." + gen = self.pcohptr.lower_star_generators() + normal = [np_array(d).reshape(-1,2) for d in gen.first] + infinite = [np_array(d) for d in gen.second] + return (normal, infinite) def flag_persistence_generators(self): """Assuming this is a flag complex, this function returns the persistence pairs, @@ -556,23 +554,20 @@ cdef class SimplexTree: :note: flag_persistence_generators requires that `persistence()` be called first. """ - if self.pcohptr != NULL: - gen = self.pcohptr.flag_generators() - if len(gen.first) == 0: - normal0 = numpy.empty((0,3)) - normals = [] - else: - l = iter(gen.first) - normal0 = np_array(next(l)).reshape(-1,3) - normals = [np_array(d).reshape(-1,4) for d in l] - if len(gen.second) == 0: - infinite0 = numpy.empty(0) - infinites = [] - else: - l = iter(gen.second) - infinite0 = np_array(next(l)) - infinites = [np_array(d).reshape(-1,2) for d in l] - - return (normal0, normals, infinite0, infinites) + assert self.pcohptr != NULL, "flag_persistence_generators() requires that persistence() be called first." + gen = self.pcohptr.flag_generators() + if len(gen.first) == 0: + normal0 = numpy.empty((0,3)) + normals = [] + else: + l = iter(gen.first) + normal0 = np_array(next(l)).reshape(-1,3) + normals = [np_array(d).reshape(-1,4) for d in l] + if len(gen.second) == 0: + infinite0 = numpy.empty(0) + infinites = [] else: - print("flag_persistence_generators() requires that persistence() be called first.") + l = iter(gen.second) + infinite0 = np_array(next(l)) + infinites = [np_array(d).reshape(-1,2) for d in l] + return (normal0, normals, infinite0, infinites) -- cgit v1.2.3 From 1fc55e54ed2f24969a691914edee642f97142fa9 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sun, 19 Apr 2020 13:43:23 +0200 Subject: Test comparison with persistence_pairs() --- src/python/test/test_simplex_generators.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/python/test/test_simplex_generators.py b/src/python/test/test_simplex_generators.py index e3bdc094..8a9b4844 100755 --- a/src/python/test/test_simplex_generators.py +++ b/src/python/test/test_simplex_generators.py @@ -24,6 +24,13 @@ def test_flag_generators(): assert np.array_equal(g[2], [0, 4]) assert len(g[3]) == 1 assert np.array_equal(g[3][0], [[7, 6]]) + # Compare trivial cases (where the simplex is the generator) with persistence_pairs. + # This still makes assumptions on the order of vertices in a simplex and could be more robust. + pairs = st.persistence_pairs() + assert {tuple(i) for i in g[0]} == {(i[0][0],) + tuple(i[1]) for i in pairs if len(i[0]) == 1 and len(i[1]) != 0} + assert {(i[0], i[1]) for i in g[1][0]} == {tuple(i[0]) for i in pairs if len(i[0]) == 2 and len(i[1]) != 0} + assert set(g[2]) == {i[0][0] for i in pairs if len(i[0]) == 1 and len(i[1]) == 0} + assert {(i[0], i[1]) for i in g[3][0]} == {tuple(i[0]) for i in pairs if len(i[0]) == 2 and len(i[1]) == 0} def test_lower_star_generators(): -- cgit v1.2.3