From 34207229b3ab2936aecd953997286a0daab88a83 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 29 May 2020 17:56:45 +0200 Subject: convert matrix to SimplexTree --- src/python/gudhi/simplex_tree.pxd | 1 + src/python/gudhi/simplex_tree.pyx | 25 +++++++++++++++++++++++++ src/python/include/Simplex_tree_interface.h | 26 ++++++++++++++++++++++++++ src/python/test/test_simplex_tree.py | 7 +++++++ 4 files changed, 59 insertions(+) diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index e748ac40..3bd9b080 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -49,6 +49,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": int upper_bound_dimension() nogil bool find_simplex(vector[int] simplex) nogil bool insert(vector[int] simplex, double filtration) nogil + void insert_matrix(double* filtrations, int n, int stride0, int stride1, double max_filtration) nogil vector[pair[vector[int], double]] get_star(vector[int] simplex) nogil vector[pair[vector[int], double]] get_cofaces(vector[int] simplex, int dimension) nogil void expansion(int max_dim) nogil except + diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 9cb24221..261f7e1b 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -195,6 +195,31 @@ cdef class SimplexTree: """ return self.get_ptr().insert(simplex, filtration) + def insert_array(self, filtrations, double max_filtration=numpy.inf): + """Inserts edges in an empty complex. The vertices are numbered from 0 to n, and the filtration values are + encoded in the array, with the diagonal representing the vertices. It is the caller's responsibility to + ensure that this defines a filtration, which can be achieved with either:: + + filtrations[np.diag_indices_from(filtrations)] = filtrations.min(1) + + or:: + + diag = filtrations.diagonal() + filtrations = np.fmax(np.fmax(filtrations, diag[:, None]), diag[None, :]) + + :param filtrations: the filtration values of the vertices and edges to insert. The matrix is assumed to be symmetric. + :type filtrations: numpy.ndarray of shape (n,n) + :param max_filtration: only insert vertices and edges with filtration values no larger than max_filtration + :type max_filtration: float + """ + filtrations = numpy.asanyarray(filtrations, dtype=float) + cdef double[:,:] F = filtrations + assert self.num_vertices() == 0, "insert_array requires an empty SimplexTree" + cdef int n = F.shape[0] + assert n == F.shape[1] + with nogil: + self.get_ptr().insert_matrix(&F[0,0], n, F.strides[0], F.strides[1], max_filtration) + def get_simplices(self): """This function returns a generator with simplices and their given filtration values. diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 56d7c41d..295b1b7f 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -36,6 +36,8 @@ class Simplex_tree_interface : public Simplex_tree { using Skeleton_simplex_iterator = typename Base::Skeleton_simplex_iterator; using Complex_simplex_iterator = typename Base::Complex_simplex_iterator; using Extended_filtration_data = typename Base::Extended_filtration_data; + using Siblings = typename Base::Siblings; + using Node = typename Base::Node; public: @@ -57,6 +59,30 @@ class Simplex_tree_interface : public Simplex_tree { return (result.second); } + void insert_matrix(double* filtrations, int n, int stride0, int stride1, double max_filtration) { + // We could delegate to insert_graph, but wrapping the matrix in a graph interface is too much work + assert(this->num_simplices() == 0); + auto& rm = this->root()->members_; + for(int i=0; i max_filtration) continue; + auto sh = rm.emplace_hint(rm.end(), i, Node(this->root(), fv)); + Siblings* children = nullptr; + // Should we make a first pass to count the number of edges so we can reserve the right space? + for(int j=i+1; j max_filtration) continue; + if(!children) { + children = new Siblings(this->root(), i); + sh->second.assign_children(children); + } + children->members().emplace_hint(children->members().end(), j, Node(children, fe)); + } + } + + } + // Do not interface this function, only used in alpha complex interface for complex creation bool insert_simplex(const Simplex& simplex, Filtration_value filtration = 0) { Insertion_result result = Base::insert_simplex(simplex, filtration); diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 2137d822..f75be58d 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -10,6 +10,7 @@ from gudhi import SimplexTree import pytest +import numpy as np __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -340,3 +341,9 @@ def test_simplices_iterator(): assert st.find(simplex[0]) == True print("filtration is: ", simplex[1]) assert st.filtration(simplex[0]) == simplex[1] + +def test_insert_array(): + st = SimplexTree() + a = np.array([[1, 4, 13, 6], [4, 3, 11, 5], [13, 11, 10, 12], [6, 5, 12, 2]]) + st.insert_array(a, max_filtration=5) + assert list(st.get_filtration()) == [([0], 1.0), ([3], 2.0), ([1], 3.0), ([0, 1], 4.0), ([1, 3], 5.0)] -- cgit v1.2.3 From 723c5cc3d48d887e6855f0d8b562c178bf396652 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 29 May 2020 18:23:29 +0200 Subject: comment --- src/python/include/Simplex_tree_interface.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index 295b1b7f..d94755d7 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -60,7 +60,8 @@ class Simplex_tree_interface : public Simplex_tree { } void insert_matrix(double* filtrations, int n, int stride0, int stride1, double max_filtration) { - // We could delegate to insert_graph, but wrapping the matrix in a graph interface is too much work + // We could delegate to insert_graph, but wrapping the matrix in a graph interface is too much work, + // and this is a bit more efficient. assert(this->num_simplices() == 0); auto& rm = this->root()->members_; for(int i=0; i Date: Sat, 30 May 2020 10:55:34 +0200 Subject: generalize insert_vertex_vector and make it available to derived classes --- src/Simplex_tree/include/gudhi/Simplex_tree.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index 889dbd00..9a4f2c34 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 #include #ifdef GUDHI_USE_TBB @@ -676,10 +677,10 @@ class Simplex_tree { return true; } - private: - /** \brief Inserts a simplex represented by a vector of vertex. - * @param[in] simplex vector of Vertex_handles, representing the vertices of the new simplex. The vector must be - * sorted by increasing vertex handle order. + protected: + /** \brief Inserts a simplex represented by a range of vertex. + * @param[in] simplex range of Vertex_handles, representing the vertices of the new simplex. The range must be + * sorted by increasing vertex handle order, and not empty. * @param[in] filtration the filtration value assigned to the new simplex. * @return If the new simplex is inserted successfully (i.e. it was not in the * simplicial complex yet) the bool is set to true and the Simplex_handle is the handle assigned @@ -691,12 +692,13 @@ class Simplex_tree { * null_simplex. * */ - std::pair insert_vertex_vector(const std::vector& simplex, + template > + std::pair insert_simplex_raw(const RandomVertexHandleRange& simplex, Filtration_value filtration) { Siblings * curr_sib = &root_; std::pair res_insert; auto vi = simplex.begin(); - for (; vi != simplex.end() - 1; ++vi) { + for (; vi != std::prev(simplex.end()); ++vi) { GUDHI_CHECK(*vi != null_vertex(), "cannot use the dummy null_vertex() as a real vertex"); res_insert = curr_sib->members_.emplace(*vi, Node(curr_sib, filtration)); if (!(has_children(res_insert.first))) { @@ -717,9 +719,10 @@ class Simplex_tree { return std::pair(null_simplex(), false); } // otherwise the insertion has succeeded - size is a size_type - if (static_cast(simplex.size()) - 1 > dimension_) { + int dim = static_cast(boost::size(simplex)) - 1; + if (dim > dimension_) { // Update dimension if needed - dimension_ = static_cast(simplex.size()) - 1; + dimension_ = dim; } return res_insert; } @@ -760,7 +763,7 @@ class Simplex_tree { // Copy before sorting std::vector copy(first, last); std::sort(std::begin(copy), std::end(copy)); - return insert_vertex_vector(copy, filtration); + return insert_simplex_raw(copy, filtration); } /** \brief Insert a N-simplex and all his subfaces, from a N-simplex represented by a range of @@ -1569,7 +1572,7 @@ class Simplex_tree { Simplex_tree st_copy = *this; // Add point for coning the simplicial complex - this->insert_simplex({maxvert}, -3); + this->insert_simplex_raw({maxvert}, -3); // For each simplex std::vector vr; -- cgit v1.2.3 From 598a6bb607edd079c3c6c49dc631f987621b12cb Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 8 Jul 2020 21:31:10 +0200 Subject: Minor doc tweak --- src/python/gudhi/simplex_tree.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 5e032e2f..557a80a9 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -196,7 +196,7 @@ cdef class SimplexTree: return self.get_ptr().insert(simplex, filtration) def insert_array(self, filtrations, double max_filtration=numpy.inf): - """Inserts edges in an empty complex. The vertices are numbered from 0 to n, and the filtration values are + """Inserts edges in an empty complex. The vertices are numbered from 0 to n-1, and the filtration values are encoded in the array, with the diagonal representing the vertices. It is the caller's responsibility to ensure that this defines a filtration, which can be achieved with either:: @@ -208,7 +208,7 @@ cdef class SimplexTree: filtrations = np.fmax(np.fmax(filtrations, diag[:, None]), diag[None, :]) :param filtrations: the filtration values of the vertices and edges to insert. The matrix is assumed to be symmetric. - :type filtrations: numpy.ndarray of shape (n,n) + :type filtrations: symmetric numpy.ndarray of shape (n,n) :param max_filtration: only insert vertices and edges with filtration values no larger than max_filtration :type max_filtration: float """ -- cgit v1.2.3 From b03844ff3276c54cff49cef898fadc2b33930184 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 8 Jul 2020 21:36:10 +0200 Subject: Remove redundant check already done in the caller --- src/python/include/Simplex_tree_interface.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index d94755d7..f570c5fc 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -62,7 +62,6 @@ class Simplex_tree_interface : public Simplex_tree { void insert_matrix(double* filtrations, int n, int stride0, int stride1, double max_filtration) { // We could delegate to insert_graph, but wrapping the matrix in a graph interface is too much work, // and this is a bit more efficient. - assert(this->num_simplices() == 0); auto& rm = this->root()->members_; for(int i=0; i Date: Wed, 8 Jul 2020 21:55:45 +0200 Subject: Rename insert_array -> insert_edges_from_array --- src/python/gudhi/simplex_tree.pyx | 4 ++-- src/python/test/test_simplex_tree.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 557a80a9..d2d3f459 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -195,7 +195,7 @@ cdef class SimplexTree: """ return self.get_ptr().insert(simplex, filtration) - def insert_array(self, filtrations, double max_filtration=numpy.inf): + def insert_edges_from_array(self, filtrations, double max_filtration=numpy.inf): """Inserts edges in an empty complex. The vertices are numbered from 0 to n-1, and the filtration values are encoded in the array, with the diagonal representing the vertices. It is the caller's responsibility to ensure that this defines a filtration, which can be achieved with either:: @@ -214,7 +214,7 @@ cdef class SimplexTree: """ filtrations = numpy.asanyarray(filtrations, dtype=float) cdef double[:,:] F = filtrations - assert self.num_vertices() == 0, "insert_array requires an empty SimplexTree" + assert self.num_vertices() == 0, "insert_edges_from_array requires an empty SimplexTree" cdef int n = F.shape[0] assert n == F.shape[1] with nogil: diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index f75be58d..02ad63cc 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -342,8 +342,8 @@ def test_simplices_iterator(): print("filtration is: ", simplex[1]) assert st.filtration(simplex[0]) == simplex[1] -def test_insert_array(): +def test_insert_edges_from_array(): st = SimplexTree() a = np.array([[1, 4, 13, 6], [4, 3, 11, 5], [13, 11, 10, 12], [6, 5, 12, 2]]) - st.insert_array(a, max_filtration=5) + st.insert_edges_from_array(a, max_filtration=5) assert list(st.get_filtration()) == [([0], 1.0), ([3], 2.0), ([1], 3.0), ([0, 1], 4.0), ([1, 3], 5.0)] -- cgit v1.2.3 From 83acc2e74cd8a34f34d0082c85ea85b3260d2458 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 8 Jul 2020 23:35:42 +0200 Subject: insert edges from sparse matrix --- src/python/gudhi/simplex_tree.pyx | 20 +++- src/python/test/test_simplex_tree.py | 176 ++++++++++++++++++++++------------- 2 files changed, 128 insertions(+), 68 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index d2d3f459..9d2c30a9 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -208,10 +208,11 @@ cdef class SimplexTree: filtrations = np.fmax(np.fmax(filtrations, diag[:, None]), diag[None, :]) :param filtrations: the filtration values of the vertices and edges to insert. The matrix is assumed to be symmetric. - :type filtrations: symmetric numpy.ndarray of shape (n,n) + :type filtrations: numpy.ndarray of shape (n,n) :param max_filtration: only insert vertices and edges with filtration values no larger than max_filtration :type max_filtration: float """ + # TODO: document which half of the matrix is actually read? filtrations = numpy.asanyarray(filtrations, dtype=float) cdef double[:,:] F = filtrations assert self.num_vertices() == 0, "insert_edges_from_array requires an empty SimplexTree" @@ -220,6 +221,23 @@ cdef class SimplexTree: with nogil: self.get_ptr().insert_matrix(&F[0,0], n, F.strides[0], F.strides[1], max_filtration) + def insert_edges_from_coo_matrix(self, edges): + """Inserts edges given by a sparse matrix in `COOrdinate format + `_. + Duplicate entries are not allowed. Missing entries are not inserted. Diagonal entries are interpreted as + vertices, although this is only useful if you want to insert vertices with a smaller filtration value than + the smallest edge containing it, since vertices are implicitly inserted together with the edges. + + :param edges: the edges to insert and their filtration values. + :type edges: scipy.sparse.coo_matrix of shape (n,n) + """ + # TODO: optimize this + for edge in zip(edges.row, edges.col, edges.data): + if edge[0] == edge[1]: + self.get_ptr().insert((edge[0],), edge[2]) + else: + self.get_ptr().insert((edge[0], edge[1]), edge[2]) + def get_simplices(self): """This function returns a generator with simplices and their given filtration values. diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 02ad63cc..c6c5dc0e 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -249,6 +249,7 @@ def test_make_filtration_non_decreasing(): assert st.filtration([3, 4]) == 2.0 assert st.filtration([4, 5]) == 2.0 + def test_extend_filtration(): # Inserted simplex: @@ -257,82 +258,83 @@ def test_extend_filtration(): # / \ / # o o # /2\ /3 - # o o - # 1 0 - - st = SimplexTree() - st.insert([0,2]) - st.insert([1,2]) - st.insert([0,3]) - st.insert([2,5]) - st.insert([3,4]) - st.insert([3,5]) - st.assign_filtration([0], 1.) - st.assign_filtration([1], 2.) - st.assign_filtration([2], 3.) - st.assign_filtration([3], 4.) - st.assign_filtration([4], 5.) - st.assign_filtration([5], 6.) - - assert list(st.get_filtration()) == [ - ([0, 2], 0.0), - ([1, 2], 0.0), - ([0, 3], 0.0), - ([3, 4], 0.0), - ([2, 5], 0.0), - ([3, 5], 0.0), - ([0], 1.0), - ([1], 2.0), - ([2], 3.0), - ([3], 4.0), - ([4], 5.0), - ([5], 6.0) + # o o + # 1 0 + + st = SimplexTree() + st.insert([0, 2]) + st.insert([1, 2]) + st.insert([0, 3]) + st.insert([2, 5]) + st.insert([3, 4]) + st.insert([3, 5]) + st.assign_filtration([0], 1.0) + st.assign_filtration([1], 2.0) + st.assign_filtration([2], 3.0) + st.assign_filtration([3], 4.0) + st.assign_filtration([4], 5.0) + st.assign_filtration([5], 6.0) + + assert list(st.get_filtration()) == [ + ([0, 2], 0.0), + ([1, 2], 0.0), + ([0, 3], 0.0), + ([3, 4], 0.0), + ([2, 5], 0.0), + ([3, 5], 0.0), + ([0], 1.0), + ([1], 2.0), + ([2], 3.0), + ([3], 4.0), + ([4], 5.0), + ([5], 6.0), ] - + st.extend_filtration() - - assert list(st.get_filtration()) == [ - ([6], -3.0), - ([0], -2.0), - ([1], -1.8), - ([2], -1.6), - ([0, 2], -1.6), - ([1, 2], -1.6), - ([3], -1.4), - ([0, 3], -1.4), - ([4], -1.2), - ([3, 4], -1.2), - ([5], -1.0), - ([2, 5], -1.0), - ([3, 5], -1.0), - ([5, 6], 1.0), - ([4, 6], 1.2), - ([3, 6], 1.4), + + assert list(st.get_filtration()) == [ + ([6], -3.0), + ([0], -2.0), + ([1], -1.8), + ([2], -1.6), + ([0, 2], -1.6), + ([1, 2], -1.6), + ([3], -1.4), + ([0, 3], -1.4), + ([4], -1.2), + ([3, 4], -1.2), + ([5], -1.0), + ([2, 5], -1.0), + ([3, 5], -1.0), + ([5, 6], 1.0), + ([4, 6], 1.2), + ([3, 6], 1.4), ([3, 4, 6], 1.4), - ([3, 5, 6], 1.4), - ([2, 6], 1.6), - ([2, 5, 6], 1.6), - ([1, 6], 1.8), - ([1, 2, 6], 1.8), - ([0, 6], 2.0), - ([0, 2, 6], 2.0), - ([0, 3, 6], 2.0) + ([3, 5, 6], 1.4), + ([2, 6], 1.6), + ([2, 5, 6], 1.6), + ([1, 6], 1.8), + ([1, 2, 6], 1.8), + ([0, 6], 2.0), + ([0, 2, 6], 2.0), + ([0, 3, 6], 2.0), ] - dgms = st.extended_persistence(min_persistence=-1.) + dgms = st.extended_persistence(min_persistence=-1.0) + + assert dgms[0][0][1][0] == pytest.approx(2.0) + assert dgms[0][0][1][1] == pytest.approx(3.0) + assert dgms[1][0][1][0] == pytest.approx(5.0) + assert dgms[1][0][1][1] == pytest.approx(4.0) + assert dgms[2][0][1][0] == pytest.approx(1.0) + assert dgms[2][0][1][1] == pytest.approx(6.0) + assert dgms[3][0][1][0] == pytest.approx(6.0) + assert dgms[3][0][1][1] == pytest.approx(1.0) - assert dgms[0][0][1][0] == pytest.approx(2.) - assert dgms[0][0][1][1] == pytest.approx(3.) - assert dgms[1][0][1][0] == pytest.approx(5.) - assert dgms[1][0][1][1] == pytest.approx(4.) - assert dgms[2][0][1][0] == pytest.approx(1.) - assert dgms[2][0][1][1] == pytest.approx(6.) - assert dgms[3][0][1][0] == pytest.approx(6.) - assert dgms[3][0][1][1] == pytest.approx(1.) 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 @@ -342,8 +344,48 @@ def test_simplices_iterator(): print("filtration is: ", simplex[1]) assert st.filtration(simplex[0]) == simplex[1] + def test_insert_edges_from_array(): st = SimplexTree() a = np.array([[1, 4, 13, 6], [4, 3, 11, 5], [13, 11, 10, 12], [6, 5, 12, 2]]) st.insert_edges_from_array(a, max_filtration=5) assert list(st.get_filtration()) == [([0], 1.0), ([3], 2.0), ([1], 3.0), ([0, 1], 4.0), ([1, 3], 5.0)] + + +def test_insert_edges_from_coo_matrix(): + try: + from scipy.sparse import coo_matrix + from scipy.spatial import cKDTree + except ImportError: + print("Skipping, no SciPy") + return + + st = SimplexTree() + st.insert([1, 2, 7], 7) + row = np.array([2, 5, 3]) + col = np.array([1, 4, 6]) + dat = np.array([1, 2, 3]) + edges = coo_matrix((dat, (row, col))) + st.insert_edges_from_coo_matrix(edges) + assert list(st.get_filtration()) == [ + ([1], 1.0), + ([2], 1.0), + ([1, 2], 1.0), + ([4], 2.0), + ([5], 2.0), + ([4, 5], 2.0), + ([3], 3.0), + ([6], 3.0), + ([3, 6], 3.0), + ([7], 7.0), + ([1, 7], 7.0), + ([2, 7], 7.0), + ([1, 2, 7], 7.0), + ] + + pts = np.random.rand(100, 2) + tree = cKDTree(pts) + edges = tree.sparse_distance_matrix(tree, max_distance=0.15, output_type="coo_matrix") + st = SimplexTree() + st.insert_edges_from_coo_matrix(edges) + assert 100 < st.num_simplices() < 1000 -- cgit v1.2.3 From 31acee0a124afc628b906349e468a9aa9fac4a2a Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Thu, 30 Jul 2020 19:45:36 +0200 Subject: Review comments --- src/python/gudhi/simplex_tree.pyx | 5 +---- src/python/include/Simplex_tree_interface.h | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 9d2c30a9..1df2420e 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -233,10 +233,7 @@ cdef class SimplexTree: """ # TODO: optimize this for edge in zip(edges.row, edges.col, edges.data): - if edge[0] == edge[1]: - self.get_ptr().insert((edge[0],), edge[2]) - else: - self.get_ptr().insert((edge[0], edge[1]), edge[2]) + self.get_ptr().insert((edge[0], edge[1]), edge[2]) def get_simplices(self): """This function returns a generator with simplices and their given diff --git a/src/python/include/Simplex_tree_interface.h b/src/python/include/Simplex_tree_interface.h index f570c5fc..3061884f 100644 --- a/src/python/include/Simplex_tree_interface.h +++ b/src/python/include/Simplex_tree_interface.h @@ -64,14 +64,14 @@ class Simplex_tree_interface : public Simplex_tree { // and this is a bit more efficient. auto& rm = this->root()->members_; for(int i=0; i(filtrations) + i * stride0; + double fv = *reinterpret_cast(p + i * stride1); if(fv > max_filtration) continue; auto sh = rm.emplace_hint(rm.end(), i, Node(this->root(), fv)); Siblings* children = nullptr; // Should we make a first pass to count the number of edges so we can reserve the right space? for(int j=i+1; j(p + j * stride1); if(fe > max_filtration) continue; if(!children) { children = new Siblings(this->root(), i); -- cgit v1.2.3 From 2830010c74cc74d29691faeeb7bb3a31cc53d87d Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 31 Jul 2020 11:50:11 +0200 Subject: static construction from array --- src/python/gudhi/simplex_tree.pyx | 16 ++++++++++------ src/python/test/test_simplex_tree.py | 5 ++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 1df2420e..42f1e635 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -195,10 +195,11 @@ cdef class SimplexTree: """ return self.get_ptr().insert(simplex, filtration) - def insert_edges_from_array(self, filtrations, double max_filtration=numpy.inf): - """Inserts edges in an empty complex. The vertices are numbered from 0 to n-1, and the filtration values are - encoded in the array, with the diagonal representing the vertices. It is the caller's responsibility to - ensure that this defines a filtration, which can be achieved with either:: + @staticmethod + def create_from_array(filtrations, double max_filtration=numpy.inf): + """Creates a new, empty complex and inserts vertices and edges. The vertices are numbered from 0 to n-1, and + the filtration values are encoded in the array, with the diagonal representing the vertices. It is the + caller's responsibility to ensure that this defines a filtration, which can be achieved with either:: filtrations[np.diag_indices_from(filtrations)] = filtrations.min(1) @@ -211,15 +212,18 @@ cdef class SimplexTree: :type filtrations: numpy.ndarray of shape (n,n) :param max_filtration: only insert vertices and edges with filtration values no larger than max_filtration :type max_filtration: float + :returns: the new complex + :rtype: SimplexTree """ # TODO: document which half of the matrix is actually read? filtrations = numpy.asanyarray(filtrations, dtype=float) cdef double[:,:] F = filtrations - assert self.num_vertices() == 0, "insert_edges_from_array requires an empty SimplexTree" + ret = SimplexTree() cdef int n = F.shape[0] assert n == F.shape[1] with nogil: - self.get_ptr().insert_matrix(&F[0,0], n, F.strides[0], F.strides[1], max_filtration) + ret.get_ptr().insert_matrix(&F[0,0], n, F.strides[0], F.strides[1], max_filtration) + return ret def insert_edges_from_coo_matrix(self, edges): """Inserts edges given by a sparse matrix in `COOrdinate format diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index c6c5dc0e..34173e78 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -345,10 +345,9 @@ def test_simplices_iterator(): assert st.filtration(simplex[0]) == simplex[1] -def test_insert_edges_from_array(): - st = SimplexTree() +def test_create_from_array(): a = np.array([[1, 4, 13, 6], [4, 3, 11, 5], [13, 11, 10, 12], [6, 5, 12, 2]]) - st.insert_edges_from_array(a, max_filtration=5) + st = SimplexTree.create_from_array(a, max_filtration=5) assert list(st.get_filtration()) == [([0], 1.0), ([3], 2.0), ([1], 3.0), ([0, 1], 4.0), ([1, 3], 5.0)] -- cgit v1.2.3 From 74948a7debebdce1ddb7afca169e2c9dc6456fa1 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 1 Apr 2022 22:31:43 +0200 Subject: SimplexTree.insert_batch for multiple simplices of the same dimension --- src/python/gudhi/simplex_tree.pyx | 42 +++++++++++- src/python/test/test_simplex_tree.py | 127 +++++++++++++++++++++++------------ 2 files changed, 123 insertions(+), 46 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 3eebfff7..3646d659 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -8,14 +8,23 @@ # - YYYY/MM Author: Description of the modification from cython.operator import dereference, preincrement -from libc.stdint cimport intptr_t +from libc.stdint cimport intptr_t, int32_t, int64_t import numpy as np cimport gudhi.simplex_tree +cimport cython __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" __license__ = "MIT" +ctypedef fused some_int: + int32_t + int64_t + +ctypedef fused some_float: + float + double + # SimplexTree python interface cdef class SimplexTree: """The simplex tree is an efficient and flexible data structure for @@ -226,6 +235,7 @@ cdef class SimplexTree: return self.get_ptr().insert(simplex, filtration) @staticmethod + @cython.boundscheck(False) def create_from_array(filtrations, double max_filtration=np.inf): """Creates a new, empty complex and inserts vertices and edges. The vertices are numbered from 0 to n-1, and the filtration values are encoded in the array, with the diagonal representing the vertices. It is the @@ -265,10 +275,38 @@ cdef class SimplexTree: :param edges: the edges to insert and their filtration values. :type edges: scipy.sparse.coo_matrix of shape (n,n) """ - # TODO: optimize this + # TODO: optimize this? for edge in zip(edges.row, edges.col, edges.data): self.get_ptr().insert((edge[0], edge[1]), edge[2]) + @cython.boundscheck(False) + @cython.wraparound(False) + def insert_batch(self, some_int[:,:] vertex_array, some_float[:] filtrations): + """Inserts k-simplices given by a sparse array in a format similar + to `torch.sparse `_. + Duplicate entries are not allowed. Missing entries are not inserted. + Simplices with a repeated vertex are currently interpreted as lower + dimensional simplices, but we do not guarantee this behavior in the + future. Any time a simplex is inserted, its faces are inserted as well + if needed to preserve a simplicial complex. + + :param vertex_array: the k-simplices to insert. + :type vertex_array: numpy.array of shape (k+1,n) + :param filtrations: the filtration values. + :type filtrations: numpy.array of shape (n,) + """ + cdef Py_ssize_t k = vertex_array.shape[0] + cdef Py_ssize_t n = vertex_array.shape[1] + assert filtrations.shape[0] == n + cdef Py_ssize_t i + cdef Py_ssize_t j + cdef vector[int] v + for i in range(n): + for j in range(k): + v.push_back(vertex_array[j, i]) + self.get_ptr().insert(v, filtrations[i]) + v.clear() + def get_simplices(self): """This function returns a generator with simplices and their given filtration values. diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 0436d891..a5b8ffe0 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -345,9 +345,10 @@ def test_simplices_iterator(): print("filtration is: ", simplex[1]) assert st.filtration(simplex[0]) == simplex[1] + def test_collapse_edges(): st = SimplexTree() - + assert st.insert([0, 1], filtration=1.0) == True assert st.insert([1, 2], filtration=1.0) == True assert st.insert([2, 3], filtration=1.0) == True @@ -362,33 +363,35 @@ def test_collapse_edges(): assert st.num_simplices() == 9 assert st.find([1, 3]) == False for simplex in st.get_skeleton(0): - assert simplex[1] == 1. + assert simplex[1] == 1.0 else: # If no Eigen3, collapse_edges throws an exception with pytest.raises(RuntimeError): st.collapse_edges() + def test_reset_filtration(): st = SimplexTree() - - assert st.insert([0, 1, 2], 3.) == True - assert st.insert([0, 3], 2.) == True - assert st.insert([3, 4, 5], 3.) == True - assert st.insert([0, 1, 6, 7], 4.) == True + + assert st.insert([0, 1, 2], 3.0) == True + assert st.insert([0, 3], 2.0) == True + assert st.insert([3, 4, 5], 3.0) == True + assert st.insert([0, 1, 6, 7], 4.0) == True # Guaranteed by construction for simplex in st.get_simplices(): - assert st.filtration(simplex[0]) >= 2. - + assert st.filtration(simplex[0]) >= 2.0 + # dimension until 5 even if simplex tree is of dimension 3 to test the limits for dimension in range(5, -1, -1): - st.reset_filtration(0., dimension) + st.reset_filtration(0.0, dimension) for simplex in st.get_skeleton(3): print(simplex) if len(simplex[0]) < (dimension) + 1: - assert st.filtration(simplex[0]) >= 2. + assert st.filtration(simplex[0]) >= 2.0 else: - assert st.filtration(simplex[0]) == 0. + assert st.filtration(simplex[0]) == 0.0 + def test_boundaries_iterator(): st = SimplexTree() @@ -404,16 +407,17 @@ def test_boundaries_iterator(): list(st.get_boundaries([])) with pytest.raises(RuntimeError): - list(st.get_boundaries([0, 4])) # (0, 4) does not exist + list(st.get_boundaries([0, 4])) # (0, 4) does not exist with pytest.raises(RuntimeError): - list(st.get_boundaries([6])) # (6) does not exist + list(st.get_boundaries([6])) # (6) does not exist + def test_persistence_intervals_in_dimension(): # Here is our triangulation of a 2-torus - taken from https://dioscuri-tda.org/Paris_TDA_Tutorial_2021.html # 0-----3-----4-----0 # | \ | \ | \ | \ | - # | \ | \ | \| \ | + # | \ | \ | \| \ | # 1-----8-----7-----1 # | \ | \ | \ | \ | # | \ | \ | \ | \ | @@ -422,50 +426,52 @@ def test_persistence_intervals_in_dimension(): # | \ | \ | \ | \ | # 0-----3-----4-----0 st = SimplexTree() - st.insert([0,1,8]) - st.insert([0,3,8]) - st.insert([3,7,8]) - st.insert([3,4,7]) - st.insert([1,4,7]) - st.insert([0,1,4]) - st.insert([1,2,5]) - st.insert([1,5,8]) - st.insert([5,6,8]) - st.insert([6,7,8]) - st.insert([2,6,7]) - st.insert([1,2,7]) - st.insert([0,2,3]) - st.insert([2,3,5]) - st.insert([3,4,5]) - st.insert([4,5,6]) - st.insert([0,4,6]) - st.insert([0,2,6]) + st.insert([0, 1, 8]) + st.insert([0, 3, 8]) + st.insert([3, 7, 8]) + st.insert([3, 4, 7]) + st.insert([1, 4, 7]) + st.insert([0, 1, 4]) + st.insert([1, 2, 5]) + st.insert([1, 5, 8]) + st.insert([5, 6, 8]) + st.insert([6, 7, 8]) + st.insert([2, 6, 7]) + st.insert([1, 2, 7]) + st.insert([0, 2, 3]) + st.insert([2, 3, 5]) + st.insert([3, 4, 5]) + st.insert([4, 5, 6]) + st.insert([0, 4, 6]) + st.insert([0, 2, 6]) st.compute_persistence(persistence_dim_max=True) - + H0 = st.persistence_intervals_in_dimension(0) - assert np.array_equal(H0, np.array([[ 0., float("inf")]])) + assert np.array_equal(H0, np.array([[0.0, float("inf")]])) H1 = st.persistence_intervals_in_dimension(1) - assert np.array_equal(H1, np.array([[ 0., float("inf")], [ 0., float("inf")]])) + assert np.array_equal(H1, np.array([[0.0, float("inf")], [0.0, float("inf")]])) H2 = st.persistence_intervals_in_dimension(2) - assert np.array_equal(H2, np.array([[ 0., float("inf")]])) + assert np.array_equal(H2, np.array([[0.0, float("inf")]])) # Test empty case assert st.persistence_intervals_in_dimension(3).shape == (0, 2) + def test_equality_operator(): st1 = SimplexTree() st2 = SimplexTree() assert st1 == st2 - st1.insert([1,2,3], 4.) + st1.insert([1, 2, 3], 4.0) assert st1 != st2 - st2.insert([1,2,3], 4.) + st2.insert([1, 2, 3], 4.0) assert st1 == st2 + def test_simplex_tree_deep_copy(): st = SimplexTree() - st.insert([1, 2, 3], 0.) + st.insert([1, 2, 3], 0.0) # compute persistence only on the original st.compute_persistence() @@ -484,14 +490,15 @@ def test_simplex_tree_deep_copy(): for a_splx in a_filt_list: assert a_splx in st_filt_list - + # test double free del st del st_copy + def test_simplex_tree_deep_copy_constructor(): st = SimplexTree() - st.insert([1, 2, 3], 0.) + st.insert([1, 2, 3], 0.0) # compute persistence only on the original st.compute_persistence() @@ -510,20 +517,23 @@ def test_simplex_tree_deep_copy_constructor(): for a_splx in a_filt_list: assert a_splx in st_filt_list - + # test double free del st del st_copy + def test_simplex_tree_constructor_exception(): with pytest.raises(TypeError): - st = SimplexTree(other = "Construction from a string shall raise an exception") + st = SimplexTree(other="Construction from a string shall raise an exception") + def test_create_from_array(): a = np.array([[1, 4, 13, 6], [4, 3, 11, 5], [13, 11, 10, 12], [6, 5, 12, 2]]) st = SimplexTree.create_from_array(a, max_filtration=5) assert list(st.get_filtration()) == [([0], 1.0), ([3], 2.0), ([1], 3.0), ([0, 1], 4.0), ([1, 3], 5.0)] + def test_insert_edges_from_coo_matrix(): try: from scipy.sparse import coo_matrix @@ -561,3 +571,32 @@ def test_insert_edges_from_coo_matrix(): st = SimplexTree() st.insert_edges_from_coo_matrix(edges) assert 100 < st.num_simplices() < 1000 + + +def test_insert_batch(): + st = SimplexTree() + # vertices + st.insert_batch(np.array([[6, 1, 5]]), np.array([-5.0, 2.0, -3.0])) + # triangles + st.insert_batch(np.array([[2, 10], [5, 0], [6, 11]]), np.array([4.0, 0.0])) + # edges + st.insert_batch(np.array([[1, 5], [2, 5]]), np.array([1.0, 3.0])) + + assert list(st.get_filtration()) == [ + ([6], -5.0), + ([5], -3.0), + ([0], 0.0), + ([10], 0.0), + ([0, 10], 0.0), + ([11], 0.0), + ([0, 11], 0.0), + ([10, 11], 0.0), + ([0, 10, 11], 0.0), + ([1], 1.0), + ([2], 1.0), + ([1, 2], 1.0), + ([2, 5], 4.0), + ([2, 6], 4.0), + ([5, 6], 4.0), + ([2, 5, 6], 4.0), + ] -- cgit v1.2.3 From 67b1e0ae09d8a975fb72faad1ee9b2f15f22e635 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 1 Apr 2022 23:38:10 +0200 Subject: Doc repeated simplex + nogil --- src/python/gudhi/simplex_tree.pyx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 3646d659..83d7b092 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -268,9 +268,10 @@ cdef class SimplexTree: def insert_edges_from_coo_matrix(self, edges): """Inserts edges given by a sparse matrix in `COOrdinate format `_. - Duplicate entries are not allowed. Missing entries are not inserted. Diagonal entries are interpreted as - vertices, although this is only useful if you want to insert vertices with a smaller filtration value than - the smallest edge containing it, since vertices are implicitly inserted together with the edges. + If an edge is repeated, the smallest filtration value is used. Missing entries are not inserted. + Diagonal entries are interpreted as vertices, although this is only useful if you want to insert + vertices with a smaller filtration value than the smallest edge containing it, since vertices are + implicitly inserted together with the edges. :param edges: the edges to insert and their filtration values. :type edges: scipy.sparse.coo_matrix of shape (n,n) @@ -284,7 +285,7 @@ cdef class SimplexTree: def insert_batch(self, some_int[:,:] vertex_array, some_float[:] filtrations): """Inserts k-simplices given by a sparse array in a format similar to `torch.sparse `_. - Duplicate entries are not allowed. Missing entries are not inserted. + If a simplex is repeated, the smallest filtration value is used. Simplices with a repeated vertex are currently interpreted as lower dimensional simplices, but we do not guarantee this behavior in the future. Any time a simplex is inserted, its faces are inserted as well @@ -301,11 +302,12 @@ cdef class SimplexTree: cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v - for i in range(n): - for j in range(k): - v.push_back(vertex_array[j, i]) - self.get_ptr().insert(v, filtrations[i]) - v.clear() + with nogil: + for i in range(n): + for j in range(k): + v.push_back(vertex_array[j, i]) + self.get_ptr().insert(v, filtrations[i]) + v.clear() def get_simplices(self): """This function returns a generator with simplices and their given -- cgit v1.2.3 From 7b7d71e3a8d1302dc81eb020114fe4c4d767ccb0 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Fri, 8 Apr 2022 18:25:32 +0200 Subject: name argument, assert message --- src/python/gudhi/simplex_tree.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 43461e02..1ac03afa 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -244,7 +244,7 @@ cdef class SimplexTree: the filtration values are encoded in the array, with the diagonal representing the vertices. It is the caller's responsibility to ensure that this defines a filtration, which can be achieved with either:: - filtrations[np.diag_indices_from(filtrations)] = filtrations.min(1) + filtrations[np.diag_indices_from(filtrations)] = filtrations.min(axis=1) or:: @@ -263,7 +263,7 @@ cdef class SimplexTree: cdef double[:,:] F = filtrations ret = SimplexTree() cdef int n = F.shape[0] - assert n == F.shape[1] + assert n == F.shape[1], 'create_from_array() expects a square array' with nogil: ret.get_ptr().insert_matrix(&F[0,0], n, F.strides[0], F.strides[1], max_filtration) return ret @@ -301,7 +301,7 @@ cdef class SimplexTree: """ cdef Py_ssize_t k = vertex_array.shape[0] cdef Py_ssize_t n = vertex_array.shape[1] - assert filtrations.shape[0] == n + assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations' cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v -- cgit v1.2.3 From 19412d57d281acfd2d14efd15764e45da837b87a Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Sun, 16 Oct 2022 23:03:30 +0200 Subject: doc + comments --- src/python/gudhi/simplex_tree.pyx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 6b1b5c00..372cb15c 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -272,12 +272,14 @@ cdef class SimplexTree: """Inserts edges given by a sparse matrix in `COOrdinate format `_. If an edge is repeated, the smallest filtration value is used. Missing entries are not inserted. - Diagonal entries are interpreted as vertices, although this is only useful if you want to insert - vertices with a smaller filtration value than the smallest edge containing it, since vertices are - implicitly inserted together with the edges. + Diagonal entries are currently interpreted as vertices, although we do not guarantee this behavior + in the future, and this is only useful if you want to insert vertices with a smaller filtration value + than the smallest edge containing it, since vertices are implicitly inserted together with the edges. :param edges: the edges to insert and their filtration values. :type edges: scipy.sparse.coo_matrix of shape (n,n) + + .. seealso:: :func:`insert_batch` """ # TODO: optimize this? for edge in zip(edges.row, edges.col, edges.data): @@ -299,6 +301,8 @@ cdef class SimplexTree: :param filtrations: the filtration values. :type filtrations: numpy.array of shape (n,) """ + # This may be slow if we end up inserting vertices in a bad order (flat_map). + # We could first insert the vertices from np.unique(vertex_array), or leave it to the caller. cdef Py_ssize_t k = vertex_array.shape[0] cdef Py_ssize_t n = vertex_array.shape[1] assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations' -- cgit v1.2.3 From 02b59e350fac3688b7dee24abe3c058739508aa5 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 16 Nov 2022 00:59:11 +0100 Subject: Use float for max_filtration in test Co-authored-by: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> --- src/python/test/test_simplex_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 59fd889a..2ccbfbf5 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -528,7 +528,7 @@ def test_simplex_tree_constructor_exception(): def test_create_from_array(): a = np.array([[1, 4, 13, 6], [4, 3, 11, 5], [13, 11, 10, 12], [6, 5, 12, 2]]) - st = SimplexTree.create_from_array(a, max_filtration=5) + st = SimplexTree.create_from_array(a, max_filtration=5.0) assert list(st.get_filtration()) == [([0], 1.0), ([3], 2.0), ([1], 3.0), ([0, 1], 4.0), ([1, 3], 5.0)] -- cgit v1.2.3 From 75d308755df7ebe0d0b7c6183f4c33512ae58b24 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 16 Nov 2022 09:50:18 +0100 Subject: Release notes --- .github/next_release.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/next_release.md b/.github/next_release.md index d5fcef1c..929a7ce6 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -9,6 +9,9 @@ Below is a list of changes made since GUDHI 3.6.0: - [Module](link) - ... +- [Simplex tree](https://gudhi.inria.fr/python/latest/simplex_tree_ref.html) + - New functions to initialize from a matrix or insert batches of simplices of the same dimension. + - [Rips complex](https://gudhi.inria.fr/python/latest/rips_complex_user.html) - Construction now rejects positional arguments, you need to specify `points=X`. -- cgit v1.2.3 From 1f532933658bf6788b1ead8ae1d902416872e70a Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Wed, 16 Nov 2022 09:55:36 +0100 Subject: Clarify doc. --- src/python/gudhi/simplex_tree.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index 372cb15c..24b970c4 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -290,6 +290,8 @@ cdef class SimplexTree: def insert_batch(self, some_int[:,:] vertex_array, some_float[:] filtrations): """Inserts k-simplices given by a sparse array in a format similar to `torch.sparse `_. + The n-th simplex has vertices `vertex_array[0,n]`, ..., + `vertex_array[k,n]` and filtration value `filtrations[n]`. If a simplex is repeated, the smallest filtration value is used. Simplices with a repeated vertex are currently interpreted as lower dimensional simplices, but we do not guarantee this behavior in the -- cgit v1.2.3