diff options
-rw-r--r-- | src/python/doc/alpha_complex_user.rst | 8 | ||||
-rwxr-xr-x | src/python/example/alpha_complex_diagram_persistence_from_off_file_example.py | 55 | ||||
-rwxr-xr-x | src/python/example/alpha_rips_persistence_bottleneck_distance.py | 110 | ||||
-rwxr-xr-x | src/python/example/plot_alpha_complex.py | 5 | ||||
-rw-r--r-- | src/python/gudhi/alpha_complex.pyx | 30 | ||||
-rw-r--r-- | src/python/gudhi/reader_utils.pyx | 16 | ||||
-rwxr-xr-x | src/python/test/test_alpha_complex.py | 116 | ||||
-rwxr-xr-x | src/python/test/test_reader_utils.py | 16 |
8 files changed, 128 insertions, 228 deletions
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 <fileformats.html#off-file-format>`_. +This example builds the alpha complex from 300 random points on a 2-torus, given by an +`OFF file <fileformats.html#off-file-format>`_. 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 <fileformats.html#off-file-format>`_ 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 <fileformats.html#off-file-format>`_ + 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.] |