From a71694354af45e8edc2e2d2b4e14795bf9b5e5f1 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Fri, 12 Mar 2021 15:46:36 +0100 Subject: review constructor and test with one point and without points --- src/python/test/test_alpha_complex.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/python/test') diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index 814f8289..8f1424ec 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -25,12 +25,17 @@ __license__ = "MIT" def _empty_alpha(precision): + alpha_complex = gd.AlphaComplex(precision = precision) + assert alpha_complex.__is_defined() == True + +def _one_2d_point_alpha(precision): alpha_complex = gd.AlphaComplex(points=[[0, 0]], precision = precision) assert alpha_complex.__is_defined() == True def test_empty_alpha(): for precision in ['fast', 'safe', 'exact']: _empty_alpha(precision) + _one_2d_point_alpha(precision) def _infinite_alpha(precision): point_list = [[0, 0], [1, 0], [0, 1], [1, 1]] -- cgit v1.2.3 From d2ae3d4e9f17649813f64bbc3b00d540b23f21dd Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Mon, 15 Mar 2021 19:19:58 +0100 Subject: Add read_weights in reader_utils. Add exceptions for file not found and inconsistency in alpha ctor. Add UT accordingly --- src/python/gudhi/alpha_complex.pyx | 41 ++++++++++++++++++++++---- src/python/gudhi/reader_utils.pyx | 16 ++++++++++ src/python/test/test_alpha_complex.py | 55 +++++++++++++++++++++++++++++++++++ src/python/test/test_reader_utils.py | 49 +++++++++++++++++++++---------- 4 files changed, 139 insertions(+), 22 deletions(-) (limited to 'src/python/test') diff --git a/src/python/gudhi/alpha_complex.pyx b/src/python/gudhi/alpha_complex.pyx index 0fea3f37..b7c20f74 100644 --- a/src/python/gudhi/alpha_complex.pyx +++ b/src/python/gudhi/alpha_complex.pyx @@ -16,11 +16,12 @@ from libcpp.utility cimport pair from libcpp.string cimport string from libcpp cimport bool from libc.stdint cimport intptr_t +import errno import os from gudhi.simplex_tree cimport * from gudhi.simplex_tree import SimplexTree -from gudhi import read_points_from_off_file +from gudhi import read_points_from_off_file, read_weights __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -55,7 +56,7 @@ cdef class AlphaComplex: cdef Alpha_complex_interface * this_ptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, points=None, off_file='', precision='safe'): + def __init__(self, points=[], off_file='', weights=[], weight_file='', precision='safe'): """AlphaComplex constructor. :param points: A list of points in d-Dimension. @@ -65,12 +66,24 @@ cdef class AlphaComplex: read and overwritten by the points in the `off_file`. :type off_file: string + :param weights: A list of weights. If set, the number of weights must correspond to the + number of points. + :type weights: list of double + + :param weight_file: A file containing a list of weights (one per line). + `weights` are read and overwritten by the weights in the `weight_file`. + If set, the number of weights must correspond to the number of points. + :type weight_file: string + :param precision: Alpha complex precision can be 'fast', 'safe' or 'exact'. Default is 'safe'. :type precision: string + + :raises FileNotFoundError: If `off_file` and/or `weight_file` is set but not found. + :raises ValueError: In case of inconsistency between the number of points and weights. """ # The real cython constructor - def __cinit__(self, points = [], off_file = '', precision = 'safe'): + def __cinit__(self, points = [], off_file = '', weights=[], weight_file='', precision = 'safe'): assert precision in ['fast', 'safe', 'exact'], "Alpha complex precision can only be 'fast', 'safe' or 'exact'" cdef bool fast = precision == 'fast' cdef bool exact = precision == 'exact' @@ -79,12 +92,28 @@ cdef class AlphaComplex: if os.path.isfile(off_file): points = read_points_from_off_file(off_file = off_file) else: - print("file " + off_file + " not found.") + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), off_file) + + if weight_file: + if os.path.isfile(weight_file): + weights = read_weights(weight_file = weight_file) + else: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), weight_file) + # need to copy the points to use them without the gil cdef vector[vector[double]] pts + cdef vector[double] wgts pts = points - with nogil: - self.this_ptr = new Alpha_complex_interface(pts, fast, exact) + if len(weights) == 0: + with nogil: + self.this_ptr = new Alpha_complex_interface(pts, fast, exact) + else: + if len(weights) == len(points): + wgts = weights + with nogil: + self.this_ptr = new Alpha_complex_interface(pts, fast, exact) + else: + raise ValueError("Inconsistency between the number of points and weights") def __dealloc__(self): if self.this_ptr != NULL: diff --git a/src/python/gudhi/reader_utils.pyx b/src/python/gudhi/reader_utils.pyx index fe1c3a2e..f997ad3e 100644 --- a/src/python/gudhi/reader_utils.pyx +++ b/src/python/gudhi/reader_utils.pyx @@ -84,3 +84,19 @@ def read_persistence_intervals_in_dimension(persistence_file='', only_this_dim=- 'utf-8'), only_this_dim)) print("file " + persistence_file + " not set or not found.") return [] + +def read_weights(weight_file=''): + """Reads a file containing weights. Only one float value per line is read and stored. + The return value is a `list(weight)`. + + :param weight_file: A weight file style name (one weight per line). + :type weight_file: string + + :returns: A list of weights. + :rtype: List[float] + """ + weights=[] + with open(weight_file, 'r') as wfile: + weights = [float(wline) for wline in wfile if wline.strip()] + return weights + diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index 8f1424ec..35059339 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -261,3 +261,58 @@ def _3d_tetrahedrons(precision): def test_3d_tetrahedrons(): for precision in ['fast', 'safe', 'exact']: _3d_tetrahedrons(precision) + +def test_non_existing_off_file(): + with pytest.raises(FileNotFoundError): + alpha = gd.AlphaComplex(off_file="pouetpouettralala.toubiloubabdou") + +def test_non_existing_weight_file(): + off_file = open("alphacomplexdoc.off", "w") + off_file.write("OFF \n" \ + "7 0 0 \n" \ + "1.0 1.0 0.0\n" \ + "7.0 0.0 0.0\n" \ + "4.0 6.0 0.0\n" \ + "9.0 6.0 0.0\n" \ + "0.0 14.0 0.0\n" \ + "2.0 19.0 0.0\n" \ + "9.0 17.0 0.0\n" ) + off_file.close() + + with pytest.raises(FileNotFoundError): + alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", + weight_file="pouetpouettralala.toubiloubabdou") + + +def test_inconsistency_off_weight_file(): + off_file = open("alphacomplexdoc.off", "w") + off_file.write("OFF \n" \ + "7 0 0 \n" \ + "1.0 1.0 0.0\n" \ + "7.0 0.0 0.0\n" \ + "4.0 6.0 0.0\n" \ + "9.0 6.0 0.0\n" \ + "0.0 14.0 0.0\n" \ + "2.0 19.0 0.0\n" \ + "9.0 17.0 0.0\n" ) + off_file.close() + # 7 points, 8 weights, on purpose + weight_file = open("alphacomplexdoc.wgt", "w") + weight_file.write("5.0\n" \ + "2.0\n" \ + "7.0\n" \ + "4.0\n" \ + "9.0\n" \ + "0.0\n" \ + "2.0\n" \ + "9.0\n" ) + weight_file.close() + + with pytest.raises(ValueError): + alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", + weight_file="alphacomplexdoc.wgt") + + # 7 points, 6 weights, on purpose + with pytest.raises(ValueError): + alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", + weights=[1., 2., 3., 4., 5., 6.]) diff --git a/src/python/test/test_reader_utils.py b/src/python/test/test_reader_utils.py index 90da6651..91de9ba0 100755 --- a/src/python/test/test_reader_utils.py +++ b/src/python/test/test_reader_utils.py @@ -8,8 +8,9 @@ - YYYY/MM Author: Description of the modification """ -import gudhi +import gudhi as gd import numpy as np +from pytest import raises __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2017 Inria" @@ -18,7 +19,7 @@ __license__ = "MIT" def test_non_existing_csv_file(): # Try to open a non existing file - matrix = gudhi.read_lower_triangular_matrix_from_csv_file( + matrix = gd.read_lower_triangular_matrix_from_csv_file( csv_file="pouetpouettralala.toubiloubabdou" ) assert matrix == [] @@ -29,7 +30,7 @@ def test_full_square_distance_matrix_csv_file(): test_file = open("full_square_distance_matrix.csv", "w") test_file.write("0;1;2;3;\n1;0;4;5;\n2;4;0;6;\n3;5;6;0;") test_file.close() - matrix = gudhi.read_lower_triangular_matrix_from_csv_file( + matrix = gd.read_lower_triangular_matrix_from_csv_file( csv_file="full_square_distance_matrix.csv" ) assert matrix == [[], [1.0], [2.0, 4.0], [3.0, 5.0, 6.0]] @@ -40,7 +41,7 @@ def test_lower_triangular_distance_matrix_csv_file(): test_file = open("lower_triangular_distance_matrix.csv", "w") test_file.write("\n1,\n2,3,\n4,5,6,\n7,8,9,10,") test_file.close() - matrix = gudhi.read_lower_triangular_matrix_from_csv_file( + matrix = gd.read_lower_triangular_matrix_from_csv_file( csv_file="lower_triangular_distance_matrix.csv", separator="," ) assert matrix == [[], [1.0], [2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0, 10.0]] @@ -48,11 +49,11 @@ def test_lower_triangular_distance_matrix_csv_file(): def test_non_existing_persistence_file(): # Try to open a non existing file - persistence = gudhi.read_persistence_intervals_grouped_by_dimension( + persistence = gd.read_persistence_intervals_grouped_by_dimension( persistence_file="pouetpouettralala.toubiloubabdou" ) assert persistence == [] - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="pouetpouettralala.toubiloubabdou", only_this_dim=1 ) np.testing.assert_array_equal(persistence, []) @@ -65,21 +66,21 @@ def test_read_persistence_intervals_without_dimension(): "# Simple persistence diagram without dimension\n2.7 3.7\n9.6 14.\n34.2 34.974\n3. inf" ) test_file.close() - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_without_dimension.pers" ) np.testing.assert_array_equal( persistence, [(2.7, 3.7), (9.6, 14.0), (34.2, 34.974), (3.0, float("Inf"))] ) - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_without_dimension.pers", only_this_dim=0 ) np.testing.assert_array_equal(persistence, []) - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_without_dimension.pers", only_this_dim=1 ) np.testing.assert_array_equal(persistence, []) - persistence = gudhi.read_persistence_intervals_grouped_by_dimension( + persistence = gd.read_persistence_intervals_grouped_by_dimension( persistence_file="persistence_intervals_without_dimension.pers" ) assert persistence == { @@ -94,29 +95,29 @@ def test_read_persistence_intervals_with_dimension(): "# Simple persistence diagram with dimension\n0 2.7 3.7\n1 9.6 14.\n3 34.2 34.974\n1 3. inf" ) test_file.close() - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_with_dimension.pers" ) np.testing.assert_array_equal( persistence, [(2.7, 3.7), (9.6, 14.0), (34.2, 34.974), (3.0, float("Inf"))] ) - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_with_dimension.pers", only_this_dim=0 ) np.testing.assert_array_equal(persistence, [(2.7, 3.7)]) - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_with_dimension.pers", only_this_dim=1 ) np.testing.assert_array_equal(persistence, [(9.6, 14.0), (3.0, float("Inf"))]) - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_with_dimension.pers", only_this_dim=2 ) np.testing.assert_array_equal(persistence, []) - persistence = gudhi.read_persistence_intervals_in_dimension( + persistence = gd.read_persistence_intervals_in_dimension( persistence_file="persistence_intervals_with_dimension.pers", only_this_dim=3 ) np.testing.assert_array_equal(persistence, [(34.2, 34.974)]) - persistence = gudhi.read_persistence_intervals_grouped_by_dimension( + persistence = gd.read_persistence_intervals_grouped_by_dimension( persistence_file="persistence_intervals_with_dimension.pers" ) assert persistence == { @@ -124,3 +125,19 @@ def test_read_persistence_intervals_with_dimension(): 1: [(9.6, 14.0), (3.0, float("Inf"))], 3: [(34.2, 34.974)], } + + +def test_non_existing_weights_file(): + with raises(FileNotFoundError): + # Try to open a non existing file + persistence = gd.read_weights(weight_file="pouetpouettralala.toubiloubabdou") + +def test_read_weights(): + # Create test file + test_file = open("test_read_weights.wgt", "w") + test_file.write( + "2.7\n 9.6 \n\t34.2\n3.\t\n\n" + ) + test_file.close() + weights = gd.read_weights(weight_file = "test_read_weights.wgt") + assert weights == [2.7, 9.6, 34.2, 3.] -- cgit v1.2.3 From 0313c98f32363bfc75162613b3cfa9b7efa4081b Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 23 Mar 2021 16:10:24 +0100 Subject: Add simplex tree equality operator to be able to test alpha complex --- src/python/gudhi/simplex_tree.pxd | 1 + src/python/gudhi/simplex_tree.pyx | 9 +++++++++ src/python/test/test_alpha_complex.py | 37 +++++++++++++++++++++++++++++++++++ src/python/test/test_simplex_tree.py | 12 ++++++++++++ 4 files changed, 59 insertions(+) (limited to 'src/python/test') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 000323af..2aa435b1 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -74,6 +74,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil pair[Simplex_tree_boundary_iterator, Simplex_tree_boundary_iterator] get_boundary_iterators(vector[int] simplex) nogil except + + bint operator==(Simplex_tree_interface_full_featured) nogil 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 d7991417..b5a938d5 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -639,3 +639,12 @@ cdef class SimplexTree: self.thisptr = (ptr.collapse_edges(nb_iter)) # Delete old pointer del ptr + + def __eq__(self, other): + """Simplex tree equality operator using C++ depth first search operator== + + :returns: True if the 2 simplex trees are equal, False otherwise. + :rtype: bool + """ + cdef intptr_t other_int_ptr=other.thisptr + return dereference(self.get_ptr()) == dereference(other_int_ptr) \ No newline at end of file diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index 35059339..a0de46c3 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -316,3 +316,40 @@ def test_inconsistency_off_weight_file(): with pytest.raises(ValueError): alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", weights=[1., 2., 3., 4., 5., 6.]) + +def _with_or_without_weight_file(precision): + off_file = open("weightalphacomplex.off", "w") + off_file.write("OFF \n" \ + "5 0 0 \n" \ + "1. -1. -1. \n" \ + "-1. 1. -1. \n" \ + "-1. -1. 1. \n" \ + "1. 1. 1. \n" \ + "2. 2. 2.") + off_file.close() + + weight_file = open("weightalphacomplex.wgt", "w") + weight_file.write("4.0\n" \ + "4.0\n" \ + "4.0\n" \ + "4.0\n" \ + "1.0\n" ) + weight_file.close() + + stree_from_files = gd.AlphaComplex(off_file="weightalphacomplex.off", + weight_file="weightalphacomplex.wgt", + precision = precision).create_simplex_tree() + + stree_from_values = gd.AlphaComplex(points=[[ 1., -1., -1.], + [-1., 1., -1.], + [-1., -1., 1.], + [ 1., 1., 1.], + [ 2., 2., 2.]], + weights = [4., 4., 4., 4., 1.], + precision = precision).create_simplex_tree() + + assert stree_from_files == stree_from_values + +def test_with_or_without_weight_file(): + for precision in ['fast', 'safe', 'exact']: + _with_or_without_weight_file(precision) diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index a3eacaa9..83b5c268 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -404,3 +404,15 @@ def test_boundaries_iterator(): with pytest.raises(RuntimeError): list(st.get_boundaries([6])) # (6) does not exist + +def test_equality_operator(): + st1 = SimplexTree() + st2 = SimplexTree() + + assert st1 == st2 + + st1.insert([1,2,3], 4.) + assert st1 != st2 + + st2.insert([1,2,3], 4.) + assert st1 == st2 -- cgit v1.2.3 From 77e577eb28ca7622553cd0527db76d46b473c445 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Wed, 24 Mar 2021 15:27:11 +0100 Subject: Remove read_weights and depreciate off_file --- src/python/doc/alpha_complex_user.rst | 8 +- ...ex_diagram_persistence_from_off_file_example.py | 55 ++++------ .../alpha_rips_persistence_bottleneck_distance.py | 110 +++++++++---------- src/python/example/plot_alpha_complex.py | 5 +- src/python/gudhi/alpha_complex.pyx | 30 ++---- src/python/gudhi/reader_utils.pyx | 16 --- src/python/test/test_alpha_complex.py | 116 +++++++-------------- src/python/test/test_reader_utils.py | 16 --- 8 files changed, 128 insertions(+), 228 deletions(-) (limited to 'src/python/test') diff --git a/src/python/doc/alpha_complex_user.rst b/src/python/doc/alpha_complex_user.rst index f59f69e7..2b4b75cf 100644 --- a/src/python/doc/alpha_complex_user.rst +++ b/src/python/doc/alpha_complex_user.rst @@ -243,7 +243,8 @@ The output is: Example from OFF file ^^^^^^^^^^^^^^^^^^^^^ -This example builds the alpha complex from 300 random points on a 2-torus, given by an `OFF file `_. +This example builds the alpha complex from 300 random points on a 2-torus, given by an +`OFF file `_. Then, it computes the persistence diagram and displays it: @@ -252,8 +253,9 @@ Then, it computes the persistence diagram and displays it: import matplotlib.pyplot as plt import gudhi as gd - ac = gd.AlphaComplex(off_file=gd.__root_source_dir__ + '/data/points/tore3D_300.off') - stree = ac.create_simplex_tree() + off_file = gd.__root_source_dir__ + '/data/points/tore3D_300.off' + points = gd.read_points_from_off_file(off_file = off_file) + stree = gd.AlphaComplex(points = points).create_simplex_tree() dgm = stree.persistence() gd.plot_persistence_diagram(dgm, legend = True) plt.show() diff --git a/src/python/example/alpha_complex_diagram_persistence_from_off_file_example.py b/src/python/example/alpha_complex_diagram_persistence_from_off_file_example.py index fe03be31..c96121a6 100755 --- a/src/python/example/alpha_complex_diagram_persistence_from_off_file_example.py +++ b/src/python/example/alpha_complex_diagram_persistence_from_off_file_example.py @@ -1,9 +1,7 @@ #!/usr/bin/env python import argparse -import errno -import os -import gudhi +import gudhi as gd """ This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. @@ -41,33 +39,24 @@ parser.add_argument( args = parser.parse_args() -with open(args.file, "r") as f: - first_line = f.readline() - if (first_line == "OFF\n") or (first_line == "nOFF\n"): - print("##############################################################") - print("AlphaComplex creation from points read in a OFF file") - - alpha_complex = gudhi.AlphaComplex(off_file=args.file) - if args.max_alpha_square is not None: - print("with max_edge_length=", args.max_alpha_square) - simplex_tree = alpha_complex.create_simplex_tree( - max_alpha_square=args.max_alpha_square - ) - else: - simplex_tree = alpha_complex.create_simplex_tree() - - print("Number of simplices=", simplex_tree.num_simplices()) - - diag = simplex_tree.persistence() - - print("betti_numbers()=", simplex_tree.betti_numbers()) - - if args.no_diagram == False: - import matplotlib.pyplot as plot - gudhi.plot_persistence_diagram(diag, band=args.band) - plot.show() - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), - args.file) - - f.close() +print("##############################################################") +print("AlphaComplex creation from points read in a OFF file") + +points = gd.read_points_from_off_file(off_file = args.file) +alpha_complex = gd.AlphaComplex(points = points) +if args.max_alpha_square is not None: + print("with max_edge_length=", args.max_alpha_square) + simplex_tree = alpha_complex.create_simplex_tree( + max_alpha_square=args.max_alpha_square + ) +else: + simplex_tree = alpha_complex.create_simplex_tree() + +print("Number of simplices=", simplex_tree.num_simplices()) + +diag = simplex_tree.persistence() +print("betti_numbers()=", simplex_tree.betti_numbers()) +if args.no_diagram == False: + import matplotlib.pyplot as plot + gd.plot_persistence_diagram(diag, band=args.band) + plot.show() diff --git a/src/python/example/alpha_rips_persistence_bottleneck_distance.py b/src/python/example/alpha_rips_persistence_bottleneck_distance.py index 3e12b0d5..6b97fb3b 100755 --- a/src/python/example/alpha_rips_persistence_bottleneck_distance.py +++ b/src/python/example/alpha_rips_persistence_bottleneck_distance.py @@ -1,10 +1,8 @@ #!/usr/bin/env python -import gudhi +import gudhi as gd import argparse import math -import errno -import os import numpy as np """ This file is part of the Gudhi Library - https://gudhi.inria.fr/ - @@ -37,70 +35,60 @@ parser.add_argument("-t", "--threshold", type=float, default=0.5) parser.add_argument("-d", "--max_dimension", type=int, default=1) args = parser.parse_args() -with open(args.file, "r") as f: - first_line = f.readline() - if (first_line == "OFF\n") or (first_line == "nOFF\n"): - point_cloud = gudhi.read_points_from_off_file(off_file=args.file) - print("##############################################################") - print("RipsComplex creation from points read in a OFF file") +point_cloud = gd.read_points_from_off_file(off_file=args.file) +print("##############################################################") +print("RipsComplex creation from points read in a OFF file") - message = "RipsComplex with max_edge_length=" + repr(args.threshold) - print(message) +message = "RipsComplex with max_edge_length=" + repr(args.threshold) +print(message) - rips_complex = gudhi.RipsComplex( - points=point_cloud, max_edge_length=args.threshold - ) - - rips_stree = rips_complex.create_simplex_tree( - max_dimension=args.max_dimension) - - message = "Number of simplices=" + repr(rips_stree.num_simplices()) - print(message) - - rips_stree.compute_persistence() - - print("##############################################################") - print("AlphaComplex creation from points read in a OFF file") - - message = "AlphaComplex with max_edge_length=" + repr(args.threshold) - print(message) - - alpha_complex = gudhi.AlphaComplex(points=point_cloud) - alpha_stree = alpha_complex.create_simplex_tree( - max_alpha_square=(args.threshold * args.threshold) - ) - - message = "Number of simplices=" + repr(alpha_stree.num_simplices()) - print(message) +rips_complex = gd.RipsComplex( + points=point_cloud, max_edge_length=args.threshold +) - alpha_stree.compute_persistence() +rips_stree = rips_complex.create_simplex_tree( + max_dimension=args.max_dimension) - max_b_distance = 0.0 - for dim in range(args.max_dimension): - # Alpha persistence values needs to be transform because filtration - # values are alpha square values - alpha_intervals = np.sqrt(alpha_stree.persistence_intervals_in_dimension(dim)) +message = "Number of simplices=" + repr(rips_stree.num_simplices()) +print(message) - rips_intervals = rips_stree.persistence_intervals_in_dimension(dim) - bottleneck_distance = gudhi.bottleneck_distance( - rips_intervals, alpha_intervals - ) - message = ( - "In dimension " - + repr(dim) - + ", bottleneck distance = " - + repr(bottleneck_distance) - ) - print(message) - max_b_distance = max(bottleneck_distance, max_b_distance) +rips_stree.compute_persistence() - print("==============================================================") - message = "Bottleneck distance is " + repr(max_b_distance) - print(message) +print("##############################################################") +print("AlphaComplex creation from points read in a OFF file") - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), - args.file) +message = "AlphaComplex with max_edge_length=" + repr(args.threshold) +print(message) +alpha_complex = gd.AlphaComplex(points=point_cloud) +alpha_stree = alpha_complex.create_simplex_tree( + max_alpha_square=(args.threshold * args.threshold) +) - f.close() +message = "Number of simplices=" + repr(alpha_stree.num_simplices()) +print(message) + +alpha_stree.compute_persistence() + +max_b_distance = 0.0 +for dim in range(args.max_dimension): + # Alpha persistence values needs to be transform because filtration + # values are alpha square values + alpha_intervals = np.sqrt(alpha_stree.persistence_intervals_in_dimension(dim)) + + rips_intervals = rips_stree.persistence_intervals_in_dimension(dim) + bottleneck_distance = gd.bottleneck_distance( + rips_intervals, alpha_intervals + ) + message = ( + "In dimension " + + repr(dim) + + ", bottleneck distance = " + + repr(bottleneck_distance) + ) + print(message) + max_b_distance = max(bottleneck_distance, max_b_distance) + +print("==============================================================") +message = "Bottleneck distance is " + repr(max_b_distance) +print(message) diff --git a/src/python/example/plot_alpha_complex.py b/src/python/example/plot_alpha_complex.py index 99c18a7c..0924619b 100755 --- a/src/python/example/plot_alpha_complex.py +++ b/src/python/example/plot_alpha_complex.py @@ -1,8 +1,9 @@ #!/usr/bin/env python import numpy as np -import gudhi -ac = gudhi.AlphaComplex(off_file='../../data/points/tore3D_1307.off') +import gudhi as gd +points = gd.read_points_from_off_file(off_file = '../../data/points/tore3D_1307.off') +ac = gd.AlphaComplex(points = points) st = ac.create_simplex_tree() points = np.array([ac.get_point(i) for i in range(st.num_vertices())]) # We want to plot the alpha-complex with alpha=0.1. diff --git a/src/python/gudhi/alpha_complex.pyx b/src/python/gudhi/alpha_complex.pyx index 9c364b76..5d181391 100644 --- a/src/python/gudhi/alpha_complex.pyx +++ b/src/python/gudhi/alpha_complex.pyx @@ -18,10 +18,11 @@ from libcpp cimport bool from libc.stdint cimport intptr_t import errno import os +import warnings from gudhi.simplex_tree cimport * from gudhi.simplex_tree import SimplexTree -from gudhi import read_points_from_off_file, read_weights +from gudhi import read_points_from_off_file __author__ = "Vincent Rouvreau" __copyright__ = "Copyright (C) 2016 Inria" @@ -56,54 +57,45 @@ cdef class AlphaComplex: cdef Alpha_complex_interface * this_ptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, points=[], off_file='', weights=[], weight_file='', precision='safe'): + def __init__(self, points=[], off_file='', weights=[], precision='safe'): """AlphaComplex constructor. :param points: A list of points in d-Dimension. :type points: Iterable[Iterable[float]] - :param off_file: An `OFF file style `_ name. - If an `off_file` is given with `points` as arguments, only points from the - file are taken into account. + :param off_file: **[deprecated]** An `OFF file style `_ + name. + If an `off_file` is given with `points` as arguments, only points from the file are + taken into account. :type off_file: string :param weights: A list of weights. If set, the number of weights must correspond to the number of points. :type weights: Iterable[float] - :param weight_file: A file containing a list of weights (one per line). - If a `weight_file` is given with `weights` as arguments, only weights from the - file are taken into account. - - :type weight_file: string - :param precision: Alpha complex precision can be 'fast', 'safe' or 'exact'. Default is 'safe'. :type precision: string - :raises FileNotFoundError: If `off_file` and/or `weight_file` is set but not found. + :raises FileNotFoundError: **[deprecated]** If `off_file` is set but not found. :raises ValueError: In case of inconsistency between the number of points and weights. """ # The real cython constructor - def __cinit__(self, points = [], off_file = '', weights=[], weight_file='', precision = 'safe'): + def __cinit__(self, points = [], off_file = '', weights=[], precision = 'safe'): assert precision in ['fast', 'safe', 'exact'], \ "Alpha complex precision can only be 'fast', 'safe' or 'exact'" cdef bool fast = precision == 'fast' cdef bool exact = precision == 'exact' if off_file: + warnings.warn("off_file is a deprecated parameter, please consider using gudhi.read_points_from_off_file", + DeprecationWarning) if os.path.isfile(off_file): points = read_points_from_off_file(off_file = off_file) else: raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), off_file) - if weight_file: - if os.path.isfile(weight_file): - weights = read_weights(weight_file = weight_file) - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), weight_file) - # weights are set but is inconsistent with the number of points if len(weights) != 0 and len(weights) != len(points): raise ValueError("Inconsistency between the number of points and weights") diff --git a/src/python/gudhi/reader_utils.pyx b/src/python/gudhi/reader_utils.pyx index f997ad3e..fe1c3a2e 100644 --- a/src/python/gudhi/reader_utils.pyx +++ b/src/python/gudhi/reader_utils.pyx @@ -84,19 +84,3 @@ def read_persistence_intervals_in_dimension(persistence_file='', only_this_dim=- 'utf-8'), only_this_dim)) print("file " + persistence_file + " not set or not found.") return [] - -def read_weights(weight_file=''): - """Reads a file containing weights. Only one float value per line is read and stored. - The return value is a `list(weight)`. - - :param weight_file: A weight file style name (one weight per line). - :type weight_file: string - - :returns: A list of weights. - :rtype: List[float] - """ - weights=[] - with open(weight_file, 'r') as wfile: - weights = [float(wline) for wline in wfile if wline.strip()] - return weights - diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index a0de46c3..e0f2b5df 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -12,6 +12,8 @@ import gudhi as gd import math import numpy as np import pytest +import warnings + try: # python3 from itertools import zip_longest @@ -203,7 +205,13 @@ def test_delaunay_complex(): _delaunay_complex(precision) def _3d_points_on_a_plane(precision, default_filtration_value): - alpha = gd.AlphaComplex(off_file='alphacomplexdoc.off', precision = precision) + alpha = gd.AlphaComplex(points = [[1.0, 1.0 , 0.0], + [7.0, 0.0 , 0.0], + [4.0, 6.0 , 0.0], + [9.0, 6.0 , 0.0], + [0.0, 14.0, 0.0], + [2.0, 19.0, 0.0], + [9.0, 17.0, 0.0]], precision = precision) simplex_tree = alpha.create_simplex_tree(default_filtration_value = default_filtration_value) assert simplex_tree.dimension() == 2 @@ -211,28 +219,16 @@ def _3d_points_on_a_plane(precision, default_filtration_value): assert simplex_tree.num_simplices() == 25 def test_3d_points_on_a_plane(): - off_file = open("alphacomplexdoc.off", "w") - off_file.write("OFF \n" \ - "7 0 0 \n" \ - "1.0 1.0 0.0\n" \ - "7.0 0.0 0.0\n" \ - "4.0 6.0 0.0\n" \ - "9.0 6.0 0.0\n" \ - "0.0 14.0 0.0\n" \ - "2.0 19.0 0.0\n" \ - "9.0 17.0 0.0\n" ) - off_file.close() - for default_filtration_value in [True, False]: for precision in ['fast', 'safe', 'exact']: _3d_points_on_a_plane(precision, default_filtration_value) def _3d_tetrahedrons(precision): points = 10*np.random.rand(10, 3) - alpha = gd.AlphaComplex(points=points, precision = precision) + alpha = gd.AlphaComplex(points = points, precision = precision) st_alpha = alpha.create_simplex_tree(default_filtration_value = False) # New AlphaComplex for get_point to work - delaunay = gd.AlphaComplex(points=points, precision = precision) + delaunay = gd.AlphaComplex(points = points, precision = precision) st_delaunay = delaunay.create_simplex_tree(default_filtration_value = True) delaunay_tetra = [] @@ -262,11 +258,7 @@ def test_3d_tetrahedrons(): for precision in ['fast', 'safe', 'exact']: _3d_tetrahedrons(precision) -def test_non_existing_off_file(): - with pytest.raises(FileNotFoundError): - alpha = gd.AlphaComplex(off_file="pouetpouettralala.toubiloubabdou") - -def test_non_existing_weight_file(): +def test_off_file_deprecation_warning(): off_file = open("alphacomplexdoc.off", "w") off_file.write("OFF \n" \ "7 0 0 \n" \ @@ -279,67 +271,32 @@ def test_non_existing_weight_file(): "9.0 17.0 0.0\n" ) off_file.close() - with pytest.raises(FileNotFoundError): - alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", - weight_file="pouetpouettralala.toubiloubabdou") + with pytest.warns(DeprecationWarning): + alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off") +def test_non_existing_off_file(): + with pytest.raises(FileNotFoundError): + alpha = gd.AlphaComplex(off_file="pouetpouettralala.toubiloubabdou") -def test_inconsistency_off_weight_file(): - off_file = open("alphacomplexdoc.off", "w") - off_file.write("OFF \n" \ - "7 0 0 \n" \ - "1.0 1.0 0.0\n" \ - "7.0 0.0 0.0\n" \ - "4.0 6.0 0.0\n" \ - "9.0 6.0 0.0\n" \ - "0.0 14.0 0.0\n" \ - "2.0 19.0 0.0\n" \ - "9.0 17.0 0.0\n" ) - off_file.close() - # 7 points, 8 weights, on purpose - weight_file = open("alphacomplexdoc.wgt", "w") - weight_file.write("5.0\n" \ - "2.0\n" \ - "7.0\n" \ - "4.0\n" \ - "9.0\n" \ - "0.0\n" \ - "2.0\n" \ - "9.0\n" ) - weight_file.close() - +def test_inconsistency_points_and_weights(): + points = [[1.0, 1.0 , 0.0], + [7.0, 0.0 , 0.0], + [4.0, 6.0 , 0.0], + [9.0, 6.0 , 0.0], + [0.0, 14.0, 0.0], + [2.0, 19.0, 0.0], + [9.0, 17.0, 0.0]] with pytest.raises(ValueError): - alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", - weight_file="alphacomplexdoc.wgt") + # 7 points, 8 weights, on purpose + alpha = gd.AlphaComplex(points = points, + weights = [1., 2., 3., 4., 5., 6., 7., 8.]) - # 7 points, 6 weights, on purpose with pytest.raises(ValueError): - alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off", - weights=[1., 2., 3., 4., 5., 6.]) - -def _with_or_without_weight_file(precision): - off_file = open("weightalphacomplex.off", "w") - off_file.write("OFF \n" \ - "5 0 0 \n" \ - "1. -1. -1. \n" \ - "-1. 1. -1. \n" \ - "-1. -1. 1. \n" \ - "1. 1. 1. \n" \ - "2. 2. 2.") - off_file.close() - - weight_file = open("weightalphacomplex.wgt", "w") - weight_file.write("4.0\n" \ - "4.0\n" \ - "4.0\n" \ - "4.0\n" \ - "1.0\n" ) - weight_file.close() - - stree_from_files = gd.AlphaComplex(off_file="weightalphacomplex.off", - weight_file="weightalphacomplex.wgt", - precision = precision).create_simplex_tree() + # 7 points, 6 weights, on purpose + alpha = gd.AlphaComplex(points = points, + weights = [1., 2., 3., 4., 5., 6.]) +def _doc_example(precision): stree_from_values = gd.AlphaComplex(points=[[ 1., -1., -1.], [-1., 1., -1.], [-1., -1., 1.], @@ -348,8 +305,11 @@ def _with_or_without_weight_file(precision): weights = [4., 4., 4., 4., 1.], precision = precision).create_simplex_tree() - assert stree_from_files == stree_from_values + assert stree_from_values.filtration([0, 1, 2, 3]) == pytest.approx(-1.) + assert stree_from_values.filtration([0, 1, 3, 4]) == pytest.approx(95.) + assert stree_from_values.filtration([0, 2, 3, 4]) == pytest.approx(95.) + assert stree_from_values.filtration([1, 2, 3, 4]) == pytest.approx(95.) -def test_with_or_without_weight_file(): +def test_doc_example(): for precision in ['fast', 'safe', 'exact']: - _with_or_without_weight_file(precision) + _doc_example(precision) diff --git a/src/python/test/test_reader_utils.py b/src/python/test/test_reader_utils.py index 91de9ba0..4fc7c00f 100755 --- a/src/python/test/test_reader_utils.py +++ b/src/python/test/test_reader_utils.py @@ -125,19 +125,3 @@ def test_read_persistence_intervals_with_dimension(): 1: [(9.6, 14.0), (3.0, float("Inf"))], 3: [(34.2, 34.974)], } - - -def test_non_existing_weights_file(): - with raises(FileNotFoundError): - # Try to open a non existing file - persistence = gd.read_weights(weight_file="pouetpouettralala.toubiloubabdou") - -def test_read_weights(): - # Create test file - test_file = open("test_read_weights.wgt", "w") - test_file.write( - "2.7\n 9.6 \n\t34.2\n3.\t\n\n" - ) - test_file.close() - weights = gd.read_weights(weight_file = "test_read_weights.wgt") - assert weights == [2.7, 9.6, 34.2, 3.] -- cgit v1.2.3 From 98cc06acb5f4b7caf4c23645614a472f7f9b5f3a Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 25 Mar 2021 09:04:27 +0100 Subject: Move simplex tree equality operator in another branch --- src/python/gudhi/simplex_tree.pxd | 1 - src/python/gudhi/simplex_tree.pyx | 9 --------- src/python/test/test_simplex_tree.py | 12 ------------ 3 files changed, 22 deletions(-) (limited to 'src/python/test') diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 2aa435b1..000323af 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -74,7 +74,6 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": Simplex_tree_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil Simplex_tree_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil pair[Simplex_tree_boundary_iterator, Simplex_tree_boundary_iterator] get_boundary_iterators(vector[int] simplex) nogil except + - bint operator==(Simplex_tree_interface_full_featured) nogil 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 b5a938d5..d7991417 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -639,12 +639,3 @@ cdef class SimplexTree: self.thisptr = (ptr.collapse_edges(nb_iter)) # Delete old pointer del ptr - - def __eq__(self, other): - """Simplex tree equality operator using C++ depth first search operator== - - :returns: True if the 2 simplex trees are equal, False otherwise. - :rtype: bool - """ - cdef intptr_t other_int_ptr=other.thisptr - return dereference(self.get_ptr()) == dereference(other_int_ptr) \ No newline at end of file diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 83b5c268..a3eacaa9 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -404,15 +404,3 @@ def test_boundaries_iterator(): with pytest.raises(RuntimeError): list(st.get_boundaries([6])) # (6) does not exist - -def test_equality_operator(): - st1 = SimplexTree() - st2 = SimplexTree() - - assert st1 == st2 - - st1.insert([1,2,3], 4.) - assert st1 != st2 - - st2.insert([1,2,3], 4.) - assert st1 == st2 -- cgit v1.2.3 From 7cce5bc6778053ba6c2c3cce2bd9442c4e063e0d Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Mon, 21 Jun 2021 16:27:53 +0200 Subject: rename test as it is only for weighted --- src/python/test/test_alpha_complex.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/python/test') diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index e0f2b5df..b0f219e1 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -296,7 +296,7 @@ def test_inconsistency_points_and_weights(): alpha = gd.AlphaComplex(points = points, weights = [1., 2., 3., 4., 5., 6.]) -def _doc_example(precision): +def _weighted_doc_example(precision): stree_from_values = gd.AlphaComplex(points=[[ 1., -1., -1.], [-1., 1., -1.], [-1., -1., 1.], @@ -310,6 +310,6 @@ def _doc_example(precision): assert stree_from_values.filtration([0, 2, 3, 4]) == pytest.approx(95.) assert stree_from_values.filtration([1, 2, 3, 4]) == pytest.approx(95.) -def test_doc_example(): +def test_weighted_doc_example(): for precision in ['fast', 'safe', 'exact']: - _doc_example(precision) + _weighted_doc_example(precision) -- cgit v1.2.3 From 6d7c79a352023dd380b7361057cb7db371c5d269 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 22 Jun 2021 17:34:27 +0200 Subject: Fix #448. Add Alpha complex 3d python module unitary tests accordingly. --- src/Alpha_complex/include/gudhi/Alpha_complex_3d.h | 19 ++- .../test/Alpha_complex_3d_unit_test.cpp | 30 +++++ .../test/Weighted_alpha_complex_unit_test.cpp | 2 +- src/python/gudhi/alpha_complex_3d.pyx | 4 +- src/python/test/test_alpha_complex.py | 107 ++++++--------- src/python/test/test_alpha_complex_3d.py | 149 +++++++++++++++++++++ 6 files changed, 231 insertions(+), 80 deletions(-) create mode 100755 src/python/test/test_alpha_complex_3d.py (limited to 'src/python/test') diff --git a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h index 4e5fc933..73f9dd41 100644 --- a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h +++ b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h @@ -48,6 +48,7 @@ #include // for std::unique_ptr #include // for std::conditional and std::enable_if #include // for numeric_limits<> +#include // for domain_error and invalid_argument // Make compilation fail - required for external projects - https://github.com/GUDHI/gudhi-devel/issues/10 #if CGAL_VERSION_NR < 1041101000 @@ -428,19 +429,18 @@ Weighted_alpha_complex_3d::Weighted_point_3 wp0(Weighted_alpha_complex_3d::Bare_ * @param[in] max_alpha_square maximum for alpha square value. Default value is +\f$\infty\f$, and there is very * little point using anything else since it does not save time. * - * @return true if creation succeeds, false otherwise. + * @exception invalid_argument In debug mode, if `complex` given as argument is not empty. + * @exception domain_error If `points` given in the constructor are on a 2d plane. * * @pre The simplicial complex must be empty (no vertices). * */ template - bool create_complex(SimplicialComplexForAlpha3d& complex, + void create_complex(SimplicialComplexForAlpha3d& complex, Filtration_value max_alpha_square = std::numeric_limits::infinity()) { - if (complex.num_vertices() > 0) { - std::cerr << "Alpha_complex_3d create_complex - complex is not empty\n"; - return false; // ----- >> - } + GUDHI_CHECK(complex.num_vertices() == 0, + std::invalid_argument("Alpha_complex_3d create_complex - The complex given as argument is not empty")); using Complex_vertex_handle = typename SimplicialComplexForAlpha3d::Vertex_handle; using Simplex_tree_vector_vertex = std::vector; @@ -461,10 +461,8 @@ Weighted_alpha_complex_3d::Weighted_point_3 wp0(Weighted_alpha_complex_3d::Bare_ #ifdef DEBUG_TRACES std::clog << "filtration_with_alpha_values returns : " << objects.size() << " objects" << std::endl; #endif // DEBUG_TRACES - if (objects.size() == 0) { - std::cerr << "Alpha_complex_3d create_complex - no triangulation as points are on a 2d plane\n"; - return false; // ----- >> - } + if (objects.size() == 0) + throw std::domain_error("Alpha_complex_3d create_complex - no triangulation as points are on a 2d plane"); using Alpha_value_iterator = typename std::vector::const_iterator; Alpha_value_iterator alpha_value_iterator = alpha_values.begin(); @@ -559,7 +557,6 @@ Weighted_alpha_complex_3d::Weighted_point_3 wp0(Weighted_alpha_complex_3d::Bare_ // Remove all simplices that have a filtration value greater than max_alpha_square complex.prune_above_filtration(max_alpha_square); // -------------------------------------------------------------------------------------------- - return true; } /** \brief get_point returns the point corresponding to the vertex given as parameter. diff --git a/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp b/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp index a4ecb6ad..94021954 100644 --- a/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp +++ b/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp @@ -11,6 +11,7 @@ #define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE "alpha_complex_3d" #include +#include #include // float comparison #include @@ -36,6 +37,7 @@ using Safe_alpha_complex_3d = using Exact_alpha_complex_3d = Gudhi::alpha_complex::Alpha_complex_3d; + template std::vector get_points() { std::vector points; @@ -197,3 +199,31 @@ BOOST_AUTO_TEST_CASE(Alpha_complex_3d_from_points) { ++safe_sh; } } + +typedef boost::mpl::list list_of_alpha_variants; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Alpha_complex_3d_exceptions_points_on_plane, Alpha, list_of_alpha_variants) { + std::vector points_on_plane; + points_on_plane.emplace_back(1.0, 1.0 , 0.0); + points_on_plane.emplace_back(7.0, 0.0 , 0.0); + points_on_plane.emplace_back(4.0, 6.0 , 0.0); + points_on_plane.emplace_back(9.0, 6.0 , 0.0); + points_on_plane.emplace_back(0.0, 14.0, 0.0); + points_on_plane.emplace_back(2.0, 19.0, 0.0); + points_on_plane.emplace_back(9.0, 17.0, 0.0); + + Alpha alpha_complex(points_on_plane); + Gudhi::Simplex_tree<> stree; + + BOOST_CHECK_THROW(alpha_complex.create_complex(stree), std::domain_error); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Alpha_complex_3d_exceptions_non_empty_simplex_tree, Alpha, list_of_alpha_variants) { + Alpha alpha_complex(get_points()); + Gudhi::Simplex_tree<> stree; + stree.insert_simplex_and_subfaces({2,1,0}, 3.0); + +#ifdef GUDHI_DEBUG + BOOST_CHECK_THROW(alpha_complex.create_complex(stree), std::invalid_argument); +#endif // GUDHI_DEBUG +} \ No newline at end of file diff --git a/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp b/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp index 875704ee..4e1a38df 100644 --- a/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp +++ b/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(Weighted_alpha_complex_3d_comparison) { // Weighted alpha complex for 3D version Exact_weighted_alpha_complex_3d alpha_complex_3D_from_weighted_points(w_points_3); Gudhi::Simplex_tree<> w_simplex_3; - BOOST_CHECK(alpha_complex_3D_from_weighted_points.create_complex(w_simplex_3)); + alpha_complex_3D_from_weighted_points.create_complex(w_simplex_3); std::clog << "Iterator on weighted alpha complex 3D simplices in the filtration order, with [filtration value]:" << std::endl; diff --git a/src/python/gudhi/alpha_complex_3d.pyx b/src/python/gudhi/alpha_complex_3d.pyx index 4d3fe59c..40f1b43a 100644 --- a/src/python/gudhi/alpha_complex_3d.pyx +++ b/src/python/gudhi/alpha_complex_3d.pyx @@ -62,7 +62,7 @@ cdef class AlphaComplex3D: def __init__(self, points=[], weights=[], precision='safe'): """AlphaComplex3D constructor. - :param points: A list of points in d-Dimension. + :param points: A list of points in 3d. :type points: Iterable[Iterable[float]] :param weights: A list of weights. If set, the number of weights must correspond to the number of points. @@ -118,6 +118,8 @@ cdef class AlphaComplex3D: :type max_alpha_square: float :returns: A simplex tree created from the Delaunay Triangulation. :rtype: SimplexTree + + :raises ValueError: If the points given at construction time are on a plane. """ stree = SimplexTree() cdef double mas = max_alpha_square diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index b0f219e1..f15284f3 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -8,7 +8,7 @@ - YYYY/MM Author: Description of the modification """ -import gudhi as gd +from gudhi import AlphaComplex import math import numpy as np import pytest @@ -21,17 +21,14 @@ except ImportError: # python2 from itertools import izip_longest as zip_longest -__author__ = "Vincent Rouvreau" -__copyright__ = "Copyright (C) 2016 Inria" -__license__ = "MIT" def _empty_alpha(precision): - alpha_complex = gd.AlphaComplex(precision = precision) + alpha_complex = AlphaComplex(precision = precision) assert alpha_complex.__is_defined() == True def _one_2d_point_alpha(precision): - alpha_complex = gd.AlphaComplex(points=[[0, 0]], precision = precision) + alpha_complex = AlphaComplex(points=[[0, 0]], precision = precision) assert alpha_complex.__is_defined() == True def test_empty_alpha(): @@ -41,7 +38,7 @@ def test_empty_alpha(): def _infinite_alpha(precision): point_list = [[0, 0], [1, 0], [0, 1], [1, 1]] - alpha_complex = gd.AlphaComplex(points=point_list, precision = precision) + alpha_complex = AlphaComplex(points=point_list, precision = precision) assert alpha_complex.__is_defined() == True simplex_tree = alpha_complex.create_simplex_tree() @@ -76,18 +73,9 @@ def _infinite_alpha(precision): assert point_list[1] == alpha_complex.get_point(1) assert point_list[2] == alpha_complex.get_point(2) assert point_list[3] == alpha_complex.get_point(3) - try: - alpha_complex.get_point(4) == [] - except IndexError: - pass - else: - assert False - try: - alpha_complex.get_point(125) == [] - except IndexError: - pass - else: - assert False + + with pytest.raises(IndexError): + alpha_complex.get_point(len(point_list)) def test_infinite_alpha(): for precision in ['fast', 'safe', 'exact']: @@ -95,7 +83,7 @@ def test_infinite_alpha(): def _filtered_alpha(precision): point_list = [[0, 0], [1, 0], [0, 1], [1, 1]] - filtered_alpha = gd.AlphaComplex(points=point_list, precision = precision) + filtered_alpha = AlphaComplex(points=point_list, precision = precision) simplex_tree = filtered_alpha.create_simplex_tree(max_alpha_square=0.25) @@ -106,18 +94,9 @@ def _filtered_alpha(precision): assert point_list[1] == filtered_alpha.get_point(1) assert point_list[2] == filtered_alpha.get_point(2) assert point_list[3] == filtered_alpha.get_point(3) - try: - filtered_alpha.get_point(4) == [] - except IndexError: - pass - else: - assert False - try: - filtered_alpha.get_point(125) == [] - except IndexError: - pass - else: - assert False + + with pytest.raises(IndexError): + filtered_alpha.get_point(len(point_list)) assert list(simplex_tree.get_filtration()) == [ ([0], 0.0), @@ -148,10 +127,10 @@ def _safe_alpha_persistence_comparison(precision): embedding2 = [[signal[i], delayed[i]] for i in range(len(time))] #build alpha complex and simplex tree - alpha_complex1 = gd.AlphaComplex(points=embedding1, precision = precision) + alpha_complex1 = AlphaComplex(points=embedding1, precision = precision) simplex_tree1 = alpha_complex1.create_simplex_tree() - alpha_complex2 = gd.AlphaComplex(points=embedding2, precision = precision) + alpha_complex2 = AlphaComplex(points=embedding2, precision = precision) simplex_tree2 = alpha_complex2.create_simplex_tree() diag1 = simplex_tree1.persistence() @@ -169,7 +148,7 @@ def test_safe_alpha_persistence_comparison(): def _delaunay_complex(precision): point_list = [[0, 0], [1, 0], [0, 1], [1, 1]] - filtered_alpha = gd.AlphaComplex(points=point_list, precision = precision) + filtered_alpha = AlphaComplex(points=point_list, precision = precision) simplex_tree = filtered_alpha.create_simplex_tree(default_filtration_value = True) @@ -180,18 +159,11 @@ def _delaunay_complex(precision): assert point_list[1] == filtered_alpha.get_point(1) assert point_list[2] == filtered_alpha.get_point(2) assert point_list[3] == filtered_alpha.get_point(3) - try: - filtered_alpha.get_point(4) == [] - except IndexError: - pass - else: - assert False - try: - filtered_alpha.get_point(125) == [] - except IndexError: - pass - else: - assert False + + with pytest.raises(IndexError): + filtered_alpha.get_point(4) + with pytest.raises(IndexError): + filtered_alpha.get_point(125) for filtered_value in simplex_tree.get_filtration(): assert math.isnan(filtered_value[1]) @@ -205,7 +177,7 @@ def test_delaunay_complex(): _delaunay_complex(precision) def _3d_points_on_a_plane(precision, default_filtration_value): - alpha = gd.AlphaComplex(points = [[1.0, 1.0 , 0.0], + alpha = AlphaComplex(points = [[1.0, 1.0 , 0.0], [7.0, 0.0 , 0.0], [4.0, 6.0 , 0.0], [9.0, 6.0 , 0.0], @@ -225,10 +197,10 @@ def test_3d_points_on_a_plane(): def _3d_tetrahedrons(precision): points = 10*np.random.rand(10, 3) - alpha = gd.AlphaComplex(points = points, precision = precision) + alpha = AlphaComplex(points = points, precision = precision) st_alpha = alpha.create_simplex_tree(default_filtration_value = False) # New AlphaComplex for get_point to work - delaunay = gd.AlphaComplex(points = points, precision = precision) + delaunay = AlphaComplex(points = points, precision = precision) st_delaunay = delaunay.create_simplex_tree(default_filtration_value = True) delaunay_tetra = [] @@ -272,11 +244,12 @@ def test_off_file_deprecation_warning(): off_file.close() with pytest.warns(DeprecationWarning): - alpha = gd.AlphaComplex(off_file="alphacomplexdoc.off") + alpha = AlphaComplex(off_file="alphacomplexdoc.off") def test_non_existing_off_file(): - with pytest.raises(FileNotFoundError): - alpha = gd.AlphaComplex(off_file="pouetpouettralala.toubiloubabdou") + with pytest.warns(DeprecationWarning): + with pytest.raises(FileNotFoundError): + alpha = AlphaComplex(off_file="pouetpouettralala.toubiloubabdou") def test_inconsistency_points_and_weights(): points = [[1.0, 1.0 , 0.0], @@ -288,27 +261,27 @@ def test_inconsistency_points_and_weights(): [9.0, 17.0, 0.0]] with pytest.raises(ValueError): # 7 points, 8 weights, on purpose - alpha = gd.AlphaComplex(points = points, + alpha = AlphaComplex(points = points, weights = [1., 2., 3., 4., 5., 6., 7., 8.]) with pytest.raises(ValueError): # 7 points, 6 weights, on purpose - alpha = gd.AlphaComplex(points = points, + alpha = AlphaComplex(points = points, weights = [1., 2., 3., 4., 5., 6.]) def _weighted_doc_example(precision): - stree_from_values = gd.AlphaComplex(points=[[ 1., -1., -1.], - [-1., 1., -1.], - [-1., -1., 1.], - [ 1., 1., 1.], - [ 2., 2., 2.]], - weights = [4., 4., 4., 4., 1.], - precision = precision).create_simplex_tree() - - assert stree_from_values.filtration([0, 1, 2, 3]) == pytest.approx(-1.) - assert stree_from_values.filtration([0, 1, 3, 4]) == pytest.approx(95.) - assert stree_from_values.filtration([0, 2, 3, 4]) == pytest.approx(95.) - assert stree_from_values.filtration([1, 2, 3, 4]) == pytest.approx(95.) + stree = AlphaComplex(points=[[ 1., -1., -1.], + [-1., 1., -1.], + [-1., -1., 1.], + [ 1., 1., 1.], + [ 2., 2., 2.]], + weights = [4., 4., 4., 4., 1.], + precision = precision).create_simplex_tree() + + assert stree.filtration([0, 1, 2, 3]) == pytest.approx(-1.) + assert stree.filtration([0, 1, 3, 4]) == pytest.approx(95.) + assert stree.filtration([0, 2, 3, 4]) == pytest.approx(95.) + assert stree.filtration([1, 2, 3, 4]) == pytest.approx(95.) def test_weighted_doc_example(): for precision in ['fast', 'safe', 'exact']: diff --git a/src/python/test/test_alpha_complex_3d.py b/src/python/test/test_alpha_complex_3d.py new file mode 100755 index 00000000..f7bd4fda --- /dev/null +++ b/src/python/test/test_alpha_complex_3d.py @@ -0,0 +1,149 @@ +""" This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + Author(s): Vincent Rouvreau + + Copyright (C) 2021 Inria + + Modification(s): + - YYYY/MM Author: Description of the modification +""" + +from gudhi import AlphaComplex3D +import pytest + +try: + # python3 + from itertools import zip_longest +except ImportError: + # python2 + from itertools import izip_longest as zip_longest + + + +def _empty_alpha(precision): + alpha_complex = AlphaComplex3D(precision = precision) + assert alpha_complex.__is_defined() == True + +def _one_3d_point_alpha(precision): + alpha_complex = AlphaComplex3D(points=[[0, 0, 0]], precision = precision) + assert alpha_complex.__is_defined() == True + +def test_empty_alpha(): + for precision in ['fast', 'safe', 'exact']: + _empty_alpha(precision) + _one_3d_point_alpha(precision) + +def _infinite_alpha(precision): + point_list = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] + alpha_complex = AlphaComplex3D(points=point_list, precision = precision) + assert alpha_complex.__is_defined() == True + + stree = alpha_complex.create_simplex_tree() + assert stree.__is_persistence_defined() == False + + assert stree.num_simplices() == 51 + assert stree.num_vertices() == len(point_list) + + for filtration in stree.get_filtration(): + if len(filtration[0]) == 1: + assert filtration[1] == 0. + if len(filtration[0]) == 4: + assert filtration[1] == 0.75 + + for idx in range(len(point_list)): + pt_idx = point_list.index(alpha_complex.get_point(idx)) + assert pt_idx >= 0 + assert pt_idx < len(point_list) + + with pytest.raises(IndexError): + alpha_complex.get_point(len(point_list)) + +def test_infinite_alpha(): + for precision in ['fast', 'safe', 'exact']: + _infinite_alpha(precision) + +def _filtered_alpha(precision): + point_list = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] + filtered_alpha = AlphaComplex3D(points=point_list, precision = precision) + + stree = filtered_alpha.create_simplex_tree(max_alpha_square=0.25) + + assert stree.num_simplices() == 20 + assert stree.num_vertices() == len(point_list) + + for filtration in stree.get_filtration(): + if len(filtration[0]) == 1: + assert filtration[1] == 0. + elif len(filtration[0]) == 2: + assert filtration[1] == 0.25 + else: + assert False + + for idx in range(len(point_list)): + pt_idx = point_list.index(filtered_alpha.get_point(idx)) + assert pt_idx >= 0 + assert pt_idx < len(point_list) + + with pytest.raises(IndexError): + filtered_alpha.get_point(len(point_list)) + +def test_filtered_alpha(): + for precision in ['fast', 'safe', 'exact']: + _filtered_alpha(precision) + +def _3d_points_on_a_plane(precision): + alpha = AlphaComplex3D(points = [[1.0, 1.0 , 0.0], + [7.0, 0.0 , 0.0], + [4.0, 6.0 , 0.0], + [9.0, 6.0 , 0.0], + [0.0, 14.0, 0.0], + [2.0, 19.0, 0.0], + [9.0, 17.0, 0.0]], precision = precision) + + with pytest.raises(ValueError): + stree = alpha.create_simplex_tree() + +def test_3d_points_on_a_plane(): + for precision in ['fast', 'safe', 'exact']: + _3d_points_on_a_plane(precision) + +def test_inconsistency_points_and_weights(): + points = [[1.0, 1.0 , 1.0], + [7.0, 0.0 , 2.0], + [4.0, 6.0 , 0.0], + [9.0, 6.0 , 1.0], + [0.0, 14.0, 2.0], + [2.0, 19.0, 0.0], + [9.0, 17.0, 1.0]] + with pytest.raises(ValueError): + # 7 points, 8 weights, on purpose + alpha = AlphaComplex3D(points = points, + weights = [1., 2., 3., 4., 5., 6., 7., 8.]) + + with pytest.raises(ValueError): + # 7 points, 6 weights, on purpose + alpha = AlphaComplex3D(points = points, + weights = [1., 2., 3., 4., 5., 6.]) + +def _weighted_doc_example(precision): + pts = [[ 1., -1., -1.], + [-1., 1., -1.], + [-1., -1., 1.], + [ 1., 1., 1.], + [ 2., 2., 2.]] + wgts = [4., 4., 4., 4., 1.] + alpha = AlphaComplex3D(points = pts, weights = wgts, precision = precision) + stree = alpha.create_simplex_tree() + + # Needs to retrieve points as points are shuffled + get_idx = lambda idx: pts.index(alpha.get_point(idx)) + indices = [get_idx(x) for x in range(len(pts))] + + assert stree.filtration([indices[x] for x in [0, 1, 2, 3]]) == pytest.approx(-1.) + assert stree.filtration([indices[x] for x in [0, 1, 3, 4]]) == pytest.approx(95.) + assert stree.filtration([indices[x] for x in [0, 2, 3, 4]]) == pytest.approx(95.) + assert stree.filtration([indices[x] for x in [1, 2, 3, 4]]) == pytest.approx(95.) + +def test_weighted_doc_example(): + for precision in ['fast', 'safe', 'exact']: + _weighted_doc_example(precision) -- cgit v1.2.3 From 1da601457d00dfe951c89ee97b6f8053e2699c78 Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Tue, 22 Jun 2021 17:48:26 +0200 Subject: Add exception when input points not in 3d --- src/python/gudhi/alpha_complex_3d.pyx | 5 +++++ src/python/test/test_alpha_complex_3d.py | 10 ++++++++++ 2 files changed, 15 insertions(+) (limited to 'src/python/test') diff --git a/src/python/gudhi/alpha_complex_3d.pyx b/src/python/gudhi/alpha_complex_3d.pyx index 40f1b43a..578011a7 100644 --- a/src/python/gudhi/alpha_complex_3d.pyx +++ b/src/python/gudhi/alpha_complex_3d.pyx @@ -71,6 +71,7 @@ cdef class AlphaComplex3D: :param precision: Alpha complex precision can be 'fast', 'safe' or 'exact'. Default is 'safe'. :type precision: string + :raises ValueError: If the given points are not in 3d. :raises ValueError: In case of inconsistency between the number of points and weights. """ @@ -80,6 +81,10 @@ cdef class AlphaComplex3D: cdef bool fast = precision == 'fast' cdef bool exact = precision == 'exact' + if len(points) > 0: + if len(points[0]) != 3: + raise ValueError("AlphaComplex3D only accepts 3d points as an input") + # weights are set but is inconsistent with the number of points if len(weights) != 0 and len(weights) != len(points): raise ValueError("Inconsistency between the number of points and weights") diff --git a/src/python/test/test_alpha_complex_3d.py b/src/python/test/test_alpha_complex_3d.py index f7bd4fda..a5d9373b 100755 --- a/src/python/test/test_alpha_complex_3d.py +++ b/src/python/test/test_alpha_complex_3d.py @@ -10,6 +10,7 @@ from gudhi import AlphaComplex3D import pytest +import numpy as np try: # python3 @@ -147,3 +148,12 @@ def _weighted_doc_example(precision): def test_weighted_doc_example(): for precision in ['fast', 'safe', 'exact']: _weighted_doc_example(precision) + +def test_points_not_in_3d(): + with pytest.raises(ValueError): + alpha = AlphaComplex3D(points = np.random.rand(6,2)) + with pytest.raises(ValueError): + alpha = AlphaComplex3D(points = np.random.rand(6,4)) + + alpha = AlphaComplex3D(points = np.random.rand(6,3)) + assert alpha.__is_defined() == True \ No newline at end of file -- cgit v1.2.3 From f70e6f26f329428184fc5cf935ad4dfc20648bfb Mon Sep 17 00:00:00 2001 From: ROUVREAU Vincent Date: Thu, 1 Jul 2021 17:30:38 +0200 Subject: Revert AlphaComplex3D. To be done with periodic --- .../example/Alpha_complex_3d_from_points.cpp | 29 ++-- .../Weighted_alpha_complex_3d_from_points.cpp | 29 ++-- src/Alpha_complex/include/gudhi/Alpha_complex_3d.h | 19 +-- .../test/Alpha_complex_3d_unit_test.cpp | 30 ---- .../test/Weighted_alpha_complex_unit_test.cpp | 2 +- src/python/CMakeLists.txt | 2 - src/python/doc/alpha_complex_ref.rst | 6 - src/python/doc/alpha_complex_user.rst | 14 -- src/python/gudhi/alpha_complex_3d.pyx | 135 ----------------- src/python/include/Alpha_complex_factory.h | 35 ----- src/python/test/test_alpha_complex_3d.py | 159 --------------------- 11 files changed, 42 insertions(+), 418 deletions(-) delete mode 100644 src/python/gudhi/alpha_complex_3d.pyx delete mode 100755 src/python/test/test_alpha_complex_3d.py (limited to 'src/python/test') diff --git a/src/Alpha_complex/example/Alpha_complex_3d_from_points.cpp b/src/Alpha_complex/example/Alpha_complex_3d_from_points.cpp index dd3c0225..a2c85138 100644 --- a/src/Alpha_complex/example/Alpha_complex_3d_from_points.cpp +++ b/src/Alpha_complex/example/Alpha_complex_3d_from_points.cpp @@ -34,22 +34,23 @@ int main(int argc, char **argv) { Alpha_complex_3d alpha_complex_from_points(points); Gudhi::Simplex_tree<> simplex; - alpha_complex_from_points.create_complex(simplex); - // ---------------------------------------------------------------------------- - // Display information about the alpha complex - // ---------------------------------------------------------------------------- - std::clog << "Alpha complex is of dimension " << simplex.dimension() << " - " << simplex.num_simplices() - << " simplices - " << simplex.num_vertices() << " vertices." << std::endl; + if (alpha_complex_from_points.create_complex(simplex)) { + // ---------------------------------------------------------------------------- + // Display information about the alpha complex + // ---------------------------------------------------------------------------- + std::clog << "Alpha complex is of dimension " << simplex.dimension() << " - " << simplex.num_simplices() + << " simplices - " << simplex.num_vertices() << " vertices." << std::endl; - std::clog << "Iterator on alpha complex simplices in the filtration order, with [filtration value]:" << std::endl; - for (auto f_simplex : simplex.filtration_simplex_range()) { - std::clog << " ( "; - for (auto vertex : simplex.simplex_vertex_range(f_simplex)) { - std::clog << vertex << " "; + std::clog << "Iterator on alpha complex simplices in the filtration order, with [filtration value]:" << std::endl; + for (auto f_simplex : simplex.filtration_simplex_range()) { + std::clog << " ( "; + for (auto vertex : simplex.simplex_vertex_range(f_simplex)) { + std::clog << vertex << " "; + } + std::clog << ") -> " + << "[" << simplex.filtration(f_simplex) << "] "; + std::clog << std::endl; } - std::clog << ") -> " - << "[" << simplex.filtration(f_simplex) << "] "; - std::clog << std::endl; } return 0; } diff --git a/src/Alpha_complex/example/Weighted_alpha_complex_3d_from_points.cpp b/src/Alpha_complex/example/Weighted_alpha_complex_3d_from_points.cpp index 507d6413..ee12d418 100644 --- a/src/Alpha_complex/example/Weighted_alpha_complex_3d_from_points.cpp +++ b/src/Alpha_complex/example/Weighted_alpha_complex_3d_from_points.cpp @@ -30,22 +30,23 @@ int main(int argc, char **argv) { Weighted_alpha_complex_3d alpha_complex_from_points(weighted_points); Gudhi::Simplex_tree<> simplex; - alpha_complex_from_points.create_complex(simplex); - // ---------------------------------------------------------------------------- - // Display information about the alpha complex - // ---------------------------------------------------------------------------- - std::clog << "Weighted alpha complex is of dimension " << simplex.dimension() << " - " << simplex.num_simplices() - << " simplices - " << simplex.num_vertices() << " vertices." << std::endl; + if (alpha_complex_from_points.create_complex(simplex)) { + // ---------------------------------------------------------------------------- + // Display information about the alpha complex + // ---------------------------------------------------------------------------- + std::clog << "Weighted alpha complex is of dimension " << simplex.dimension() << " - " << simplex.num_simplices() + << " simplices - " << simplex.num_vertices() << " vertices." << std::endl; - std::clog << "Iterator on weighted alpha complex simplices in the filtration order, with [filtration value]:" << std::endl; - for (auto f_simplex : simplex.filtration_simplex_range()) { - std::clog << " ( "; - for (auto vertex : simplex.simplex_vertex_range(f_simplex)) { - std::clog << vertex << " "; + std::clog << "Iterator on weighted alpha complex simplices in the filtration order, with [filtration value]:" << std::endl; + for (auto f_simplex : simplex.filtration_simplex_range()) { + std::clog << " ( "; + for (auto vertex : simplex.simplex_vertex_range(f_simplex)) { + std::clog << vertex << " "; + } + std::clog << ") -> " + << "[" << simplex.filtration(f_simplex) << "] "; + std::clog << std::endl; } - std::clog << ") -> " - << "[" << simplex.filtration(f_simplex) << "] "; - std::clog << std::endl; } return 0; } diff --git a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h index 73f9dd41..4e5fc933 100644 --- a/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h +++ b/src/Alpha_complex/include/gudhi/Alpha_complex_3d.h @@ -48,7 +48,6 @@ #include // for std::unique_ptr #include // for std::conditional and std::enable_if #include // for numeric_limits<> -#include // for domain_error and invalid_argument // Make compilation fail - required for external projects - https://github.com/GUDHI/gudhi-devel/issues/10 #if CGAL_VERSION_NR < 1041101000 @@ -429,18 +428,19 @@ Weighted_alpha_complex_3d::Weighted_point_3 wp0(Weighted_alpha_complex_3d::Bare_ * @param[in] max_alpha_square maximum for alpha square value. Default value is +\f$\infty\f$, and there is very * little point using anything else since it does not save time. * - * @exception invalid_argument In debug mode, if `complex` given as argument is not empty. - * @exception domain_error If `points` given in the constructor are on a 2d plane. + * @return true if creation succeeds, false otherwise. * * @pre The simplicial complex must be empty (no vertices). * */ template - void create_complex(SimplicialComplexForAlpha3d& complex, + bool create_complex(SimplicialComplexForAlpha3d& complex, Filtration_value max_alpha_square = std::numeric_limits::infinity()) { - GUDHI_CHECK(complex.num_vertices() == 0, - std::invalid_argument("Alpha_complex_3d create_complex - The complex given as argument is not empty")); + if (complex.num_vertices() > 0) { + std::cerr << "Alpha_complex_3d create_complex - complex is not empty\n"; + return false; // ----- >> + } using Complex_vertex_handle = typename SimplicialComplexForAlpha3d::Vertex_handle; using Simplex_tree_vector_vertex = std::vector; @@ -461,8 +461,10 @@ Weighted_alpha_complex_3d::Weighted_point_3 wp0(Weighted_alpha_complex_3d::Bare_ #ifdef DEBUG_TRACES std::clog << "filtration_with_alpha_values returns : " << objects.size() << " objects" << std::endl; #endif // DEBUG_TRACES - if (objects.size() == 0) - throw std::domain_error("Alpha_complex_3d create_complex - no triangulation as points are on a 2d plane"); + if (objects.size() == 0) { + std::cerr << "Alpha_complex_3d create_complex - no triangulation as points are on a 2d plane\n"; + return false; // ----- >> + } using Alpha_value_iterator = typename std::vector::const_iterator; Alpha_value_iterator alpha_value_iterator = alpha_values.begin(); @@ -557,6 +559,7 @@ Weighted_alpha_complex_3d::Weighted_point_3 wp0(Weighted_alpha_complex_3d::Bare_ // Remove all simplices that have a filtration value greater than max_alpha_square complex.prune_above_filtration(max_alpha_square); // -------------------------------------------------------------------------------------------- + return true; } /** \brief get_point returns the point corresponding to the vertex given as parameter. diff --git a/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp b/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp index 94021954..a4ecb6ad 100644 --- a/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp +++ b/src/Alpha_complex/test/Alpha_complex_3d_unit_test.cpp @@ -11,7 +11,6 @@ #define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE "alpha_complex_3d" #include -#include #include // float comparison #include @@ -37,7 +36,6 @@ using Safe_alpha_complex_3d = using Exact_alpha_complex_3d = Gudhi::alpha_complex::Alpha_complex_3d; - template std::vector get_points() { std::vector points; @@ -199,31 +197,3 @@ BOOST_AUTO_TEST_CASE(Alpha_complex_3d_from_points) { ++safe_sh; } } - -typedef boost::mpl::list list_of_alpha_variants; - -BOOST_AUTO_TEST_CASE_TEMPLATE(Alpha_complex_3d_exceptions_points_on_plane, Alpha, list_of_alpha_variants) { - std::vector points_on_plane; - points_on_plane.emplace_back(1.0, 1.0 , 0.0); - points_on_plane.emplace_back(7.0, 0.0 , 0.0); - points_on_plane.emplace_back(4.0, 6.0 , 0.0); - points_on_plane.emplace_back(9.0, 6.0 , 0.0); - points_on_plane.emplace_back(0.0, 14.0, 0.0); - points_on_plane.emplace_back(2.0, 19.0, 0.0); - points_on_plane.emplace_back(9.0, 17.0, 0.0); - - Alpha alpha_complex(points_on_plane); - Gudhi::Simplex_tree<> stree; - - BOOST_CHECK_THROW(alpha_complex.create_complex(stree), std::domain_error); -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(Alpha_complex_3d_exceptions_non_empty_simplex_tree, Alpha, list_of_alpha_variants) { - Alpha alpha_complex(get_points()); - Gudhi::Simplex_tree<> stree; - stree.insert_simplex_and_subfaces({2,1,0}, 3.0); - -#ifdef GUDHI_DEBUG - BOOST_CHECK_THROW(alpha_complex.create_complex(stree), std::invalid_argument); -#endif // GUDHI_DEBUG -} \ No newline at end of file diff --git a/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp b/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp index 4e1a38df..875704ee 100644 --- a/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp +++ b/src/Alpha_complex/test/Weighted_alpha_complex_unit_test.cpp @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(Weighted_alpha_complex_3d_comparison) { // Weighted alpha complex for 3D version Exact_weighted_alpha_complex_3d alpha_complex_3D_from_weighted_points(w_points_3); Gudhi::Simplex_tree<> w_simplex_3; - alpha_complex_3D_from_weighted_points.create_complex(w_simplex_3); + BOOST_CHECK(alpha_complex_3D_from_weighted_points.create_complex(w_simplex_3)); std::clog << "Iterator on weighted alpha complex 3D simplices in the filtration order, with [filtration value]:" << std::endl; diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 669239b8..bfa78131 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -62,7 +62,6 @@ if(PYTHONINTERP_FOUND) set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'subsampling', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'tangential_complex', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'alpha_complex', ") - set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'alpha_complex_3d', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'euclidean_witness_complex', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'euclidean_strong_witness_complex', ") # Modules that should not be auto-imported in __init__.py @@ -158,7 +157,6 @@ if(PYTHONINTERP_FOUND) set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'nerve_gic', ") endif () if (NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) - set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'alpha_complex_3d', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'subsampling', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'tangential_complex', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'euclidean_witness_complex', ") diff --git a/src/python/doc/alpha_complex_ref.rst b/src/python/doc/alpha_complex_ref.rst index 49321368..eaa72551 100644 --- a/src/python/doc/alpha_complex_ref.rst +++ b/src/python/doc/alpha_complex_ref.rst @@ -11,9 +11,3 @@ Alpha complex reference manual :undoc-members: .. automethod:: gudhi.AlphaComplex.__init__ - -.. autoclass:: gudhi.AlphaComplex3D - :members: - :undoc-members: - - .. automethod:: gudhi.AlphaComplex3D.__init__ diff --git a/src/python/doc/alpha_complex_user.rst b/src/python/doc/alpha_complex_user.rst index d7b09246..db0ccdc9 100644 --- a/src/python/doc/alpha_complex_user.rst +++ b/src/python/doc/alpha_complex_user.rst @@ -254,17 +254,3 @@ Then, it computes the persistence diagram and displays it: dgm = stree.persistence() gd.plot_persistence_diagram(dgm, legend = True) plt.show() - -3d specific version -^^^^^^^^^^^^^^^^^^^ - -:Requires: `Eigen `_ :math:`\geq` 3.1.0 and `CGAL `_ :math:`\geq` 4.11.0. - -A specific module for Alpha complex is available in 3d (cf. :class:`~gudhi.AlphaComplex3D`) and -allows to construct standard and weighted versions of alpha complexes. - -Remark -"""""" - -* Contrary to the dD version, with the 3d version, the vertices in the output simplex tree are not guaranteed to match - the order of the input points. One can use :func:`~gudhi.AlphaComplex3D.get_point` to get the initial point back. diff --git a/src/python/gudhi/alpha_complex_3d.pyx b/src/python/gudhi/alpha_complex_3d.pyx deleted file mode 100644 index 578011a7..00000000 --- a/src/python/gudhi/alpha_complex_3d.pyx +++ /dev/null @@ -1,135 +0,0 @@ -# This file is part of the Gudhi Library - https://gudhi.inria.fr/ - -# which is released under MIT. -# See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full -# license details. -# Author(s): Vincent Rouvreau -# -# Copyright (C) 2021 Inria -# -# Modification(s): -# - YYYY/MM Author: Description of the modification - -from __future__ import print_function -from cython cimport numeric -from libcpp.vector cimport vector -from libcpp.utility cimport pair -from libcpp.string cimport string -from libcpp cimport bool -from libc.stdint cimport intptr_t -import errno -import os -import warnings - -from gudhi.simplex_tree cimport * -from gudhi.simplex_tree import SimplexTree -from gudhi import read_points_from_off_file - -__author__ = "Vincent Rouvreau" -__copyright__ = "Copyright (C) 2021 Inria" -__license__ = "GPL v3" - -cdef extern from "Alpha_complex_interface_3d.h" namespace "Gudhi": - cdef cppclass Alpha_complex_interface_3d "Gudhi::alpha_complex::Alpha_complex_interface_3d": - Alpha_complex_interface_3d(vector[vector[double]] points, vector[double] weights, bool fast_version, bool exact_version) nogil except + - vector[double] get_point(int vertex) nogil except + - void create_simplex_tree(Simplex_tree_interface_full_featured* simplex_tree, double max_alpha_square) nogil except + - -# AlphaComplex3D python interface -cdef class AlphaComplex3D: - """AlphaComplex3D is a simplicial complex constructed from the finite cells of a Delaunay Triangulation. - - The filtration value of each simplex is computed as the square of the circumradius of the simplex if the - circumsphere is empty (the simplex is then said to be Gabriel), and as the minimum of the filtration values of the - codimension 1 cofaces that make it not Gabriel otherwise. - - All simplices that have a filtration value strictly greater than a given alpha squared value are not inserted into - the complex. - - .. note:: - - When AlphaComplex3D is constructed with an infinite value of alpha, the complex is a Delaunay complex. - - .. warning:: - - Contrary to the dD version, with the 3d version, the vertices in the output simplex tree are not guaranteed to - match the order of the input points. One can use :func:`~gudhi.AlphaComplex3D.get_point` to get the initial - point back. - """ - - cdef Alpha_complex_interface_3d * this_ptr - - # Fake constructor that does nothing but documenting the constructor - def __init__(self, points=[], weights=[], precision='safe'): - """AlphaComplex3D constructor. - - :param points: A list of points in 3d. - :type points: Iterable[Iterable[float]] - - :param weights: A list of weights. If set, the number of weights must correspond to the number of points. - :type weights: Iterable[float] - - :param precision: Alpha complex precision can be 'fast', 'safe' or 'exact'. Default is 'safe'. - :type precision: string - - :raises ValueError: If the given points are not in 3d. - :raises ValueError: In case of inconsistency between the number of points and weights. - """ - - # The real cython constructor - def __cinit__(self, points = [], weights=[], precision = 'safe'): - assert precision in ['fast', 'safe', 'exact'], "Alpha complex precision can only be 'fast', 'safe' or 'exact'" - cdef bool fast = precision == 'fast' - cdef bool exact = precision == 'exact' - - if len(points) > 0: - if len(points[0]) != 3: - raise ValueError("AlphaComplex3D only accepts 3d points as an input") - - # weights are set but is inconsistent with the number of points - if len(weights) != 0 and len(weights) != len(points): - raise ValueError("Inconsistency between the number of points and weights") - - # need to copy the points to use them without the gil - cdef vector[vector[double]] pts - cdef vector[double] wgts - pts = points - wgts = weights - with nogil: - self.this_ptr = new Alpha_complex_interface_3d(pts, wgts, fast, exact) - - def __dealloc__(self): - if self.this_ptr != NULL: - del self.this_ptr - - def __is_defined(self): - """Returns true if AlphaComplex3D pointer is not NULL. - """ - return self.this_ptr != NULL - - def get_point(self, vertex): - """This function returns the point corresponding to a given vertex from the :class:`~gudhi.SimplexTree`. - - :param vertex: The vertex. - :type vertex: int - :rtype: list of float - :returns: the point. - """ - return self.this_ptr.get_point(vertex) - - def create_simplex_tree(self, max_alpha_square = float('inf')): - """ - :param max_alpha_square: The maximum alpha square threshold the simplices shall not exceed. Default is set to - infinity, and there is very little point using anything else since it does not save time. - :type max_alpha_square: float - :returns: A simplex tree created from the Delaunay Triangulation. - :rtype: SimplexTree - - :raises ValueError: If the points given at construction time are on a plane. - """ - stree = SimplexTree() - cdef double mas = max_alpha_square - cdef intptr_t stree_int_ptr=stree.thisptr - with nogil: - self.this_ptr.create_simplex_tree(stree_int_ptr, - mas) - return stree diff --git a/src/python/include/Alpha_complex_factory.h b/src/python/include/Alpha_complex_factory.h index fbbf8896..298469fe 100644 --- a/src/python/include/Alpha_complex_factory.h +++ b/src/python/include/Alpha_complex_factory.h @@ -147,41 +147,6 @@ class Inexact_alpha_complex_dD final : public Abstract_alpha_complex { Alpha_complex alpha_complex_; }; -template -class Alpha_complex_3D final : public Abstract_alpha_complex { - private: - using Bare_point = typename Alpha_complex_3d::Bare_point_3; - using Point = typename Alpha_complex_3d::Point_3; - - static Bare_point pt_cython_to_cgal_3(std::vector const& vec) { - return Bare_point(vec[0], vec[1], vec[2]); - } - - public: - Alpha_complex_3D(const std::vector>& points) - : alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal_3)) { - } - - Alpha_complex_3D(const std::vector>& points, const std::vector& weights) - : alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal_3), weights) { - } - - virtual std::vector get_point(int vh) override { - // Can be a Weighted or a Bare point in function of Weighted - return Point_cgal_to_cython()(alpha_complex_.get_point(vh)); - } - - virtual bool create_simplex_tree(Simplex_tree_interface<>* simplex_tree, double max_alpha_square, - bool default_filtration_value) override { - alpha_complex_.create_complex(*simplex_tree, max_alpha_square); - return true; - } - - private: - Alpha_complex_3d alpha_complex_; -}; - - } // namespace alpha_complex } // namespace Gudhi diff --git a/src/python/test/test_alpha_complex_3d.py b/src/python/test/test_alpha_complex_3d.py deleted file mode 100755 index a5d9373b..00000000 --- a/src/python/test/test_alpha_complex_3d.py +++ /dev/null @@ -1,159 +0,0 @@ -""" This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. - See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. - Author(s): Vincent Rouvreau - - Copyright (C) 2021 Inria - - Modification(s): - - YYYY/MM Author: Description of the modification -""" - -from gudhi import AlphaComplex3D -import pytest -import numpy as np - -try: - # python3 - from itertools import zip_longest -except ImportError: - # python2 - from itertools import izip_longest as zip_longest - - - -def _empty_alpha(precision): - alpha_complex = AlphaComplex3D(precision = precision) - assert alpha_complex.__is_defined() == True - -def _one_3d_point_alpha(precision): - alpha_complex = AlphaComplex3D(points=[[0, 0, 0]], precision = precision) - assert alpha_complex.__is_defined() == True - -def test_empty_alpha(): - for precision in ['fast', 'safe', 'exact']: - _empty_alpha(precision) - _one_3d_point_alpha(precision) - -def _infinite_alpha(precision): - point_list = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] - alpha_complex = AlphaComplex3D(points=point_list, precision = precision) - assert alpha_complex.__is_defined() == True - - stree = alpha_complex.create_simplex_tree() - assert stree.__is_persistence_defined() == False - - assert stree.num_simplices() == 51 - assert stree.num_vertices() == len(point_list) - - for filtration in stree.get_filtration(): - if len(filtration[0]) == 1: - assert filtration[1] == 0. - if len(filtration[0]) == 4: - assert filtration[1] == 0.75 - - for idx in range(len(point_list)): - pt_idx = point_list.index(alpha_complex.get_point(idx)) - assert pt_idx >= 0 - assert pt_idx < len(point_list) - - with pytest.raises(IndexError): - alpha_complex.get_point(len(point_list)) - -def test_infinite_alpha(): - for precision in ['fast', 'safe', 'exact']: - _infinite_alpha(precision) - -def _filtered_alpha(precision): - point_list = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] - filtered_alpha = AlphaComplex3D(points=point_list, precision = precision) - - stree = filtered_alpha.create_simplex_tree(max_alpha_square=0.25) - - assert stree.num_simplices() == 20 - assert stree.num_vertices() == len(point_list) - - for filtration in stree.get_filtration(): - if len(filtration[0]) == 1: - assert filtration[1] == 0. - elif len(filtration[0]) == 2: - assert filtration[1] == 0.25 - else: - assert False - - for idx in range(len(point_list)): - pt_idx = point_list.index(filtered_alpha.get_point(idx)) - assert pt_idx >= 0 - assert pt_idx < len(point_list) - - with pytest.raises(IndexError): - filtered_alpha.get_point(len(point_list)) - -def test_filtered_alpha(): - for precision in ['fast', 'safe', 'exact']: - _filtered_alpha(precision) - -def _3d_points_on_a_plane(precision): - alpha = AlphaComplex3D(points = [[1.0, 1.0 , 0.0], - [7.0, 0.0 , 0.0], - [4.0, 6.0 , 0.0], - [9.0, 6.0 , 0.0], - [0.0, 14.0, 0.0], - [2.0, 19.0, 0.0], - [9.0, 17.0, 0.0]], precision = precision) - - with pytest.raises(ValueError): - stree = alpha.create_simplex_tree() - -def test_3d_points_on_a_plane(): - for precision in ['fast', 'safe', 'exact']: - _3d_points_on_a_plane(precision) - -def test_inconsistency_points_and_weights(): - points = [[1.0, 1.0 , 1.0], - [7.0, 0.0 , 2.0], - [4.0, 6.0 , 0.0], - [9.0, 6.0 , 1.0], - [0.0, 14.0, 2.0], - [2.0, 19.0, 0.0], - [9.0, 17.0, 1.0]] - with pytest.raises(ValueError): - # 7 points, 8 weights, on purpose - alpha = AlphaComplex3D(points = points, - weights = [1., 2., 3., 4., 5., 6., 7., 8.]) - - with pytest.raises(ValueError): - # 7 points, 6 weights, on purpose - alpha = AlphaComplex3D(points = points, - weights = [1., 2., 3., 4., 5., 6.]) - -def _weighted_doc_example(precision): - pts = [[ 1., -1., -1.], - [-1., 1., -1.], - [-1., -1., 1.], - [ 1., 1., 1.], - [ 2., 2., 2.]] - wgts = [4., 4., 4., 4., 1.] - alpha = AlphaComplex3D(points = pts, weights = wgts, precision = precision) - stree = alpha.create_simplex_tree() - - # Needs to retrieve points as points are shuffled - get_idx = lambda idx: pts.index(alpha.get_point(idx)) - indices = [get_idx(x) for x in range(len(pts))] - - assert stree.filtration([indices[x] for x in [0, 1, 2, 3]]) == pytest.approx(-1.) - assert stree.filtration([indices[x] for x in [0, 1, 3, 4]]) == pytest.approx(95.) - assert stree.filtration([indices[x] for x in [0, 2, 3, 4]]) == pytest.approx(95.) - assert stree.filtration([indices[x] for x in [1, 2, 3, 4]]) == pytest.approx(95.) - -def test_weighted_doc_example(): - for precision in ['fast', 'safe', 'exact']: - _weighted_doc_example(precision) - -def test_points_not_in_3d(): - with pytest.raises(ValueError): - alpha = AlphaComplex3D(points = np.random.rand(6,2)) - with pytest.raises(ValueError): - alpha = AlphaComplex3D(points = np.random.rand(6,4)) - - alpha = AlphaComplex3D(points = np.random.rand(6,3)) - assert alpha.__is_defined() == True \ No newline at end of file -- cgit v1.2.3