diff options
author | tlacombe <lacombe1993@gmail.com> | 2021-04-12 10:37:27 +0200 |
---|---|---|
committer | tlacombe <lacombe1993@gmail.com> | 2021-04-12 10:37:27 +0200 |
commit | 69341c88c7c7819656c9a9b935fecc3bea50e4af (patch) | |
tree | 7fa0646180c04fb32854ca0aaf29d192d5e4118f /src/python/test | |
parent | e94892f972357283e70c7534f84662dfaa21cc3e (diff) | |
parent | 7e05e915adc1be285e04eb00d3ab7ba1b797f38d (diff) |
merge upstream/master into essential parts
Diffstat (limited to 'src/python/test')
-rwxr-xr-x | src/python/test/test_alpha_complex.py | 15 | ||||
-rwxr-xr-x | src/python/test/test_bottleneck_distance.py | 12 | ||||
-rwxr-xr-x | src/python/test/test_representations.py | 61 | ||||
-rwxr-xr-x | src/python/test/test_simplex_tree.py | 66 | ||||
-rwxr-xr-x | src/python/test/test_subsampling.py | 16 | ||||
-rwxr-xr-x | src/python/test/test_wasserstein_with_tensors.py | 47 |
6 files changed, 206 insertions, 11 deletions
diff --git a/src/python/test/test_alpha_complex.py b/src/python/test/test_alpha_complex.py index a4ee260b..814f8289 100755 --- a/src/python/test/test_alpha_complex.py +++ b/src/python/test/test_alpha_complex.py @@ -198,8 +198,7 @@ def test_delaunay_complex(): _delaunay_complex(precision) def _3d_points_on_a_plane(precision, default_filtration_value): - alpha = gd.AlphaComplex(off_file=gd.__root_source_dir__ + '/data/points/alphacomplexdoc.off', - precision = precision) + alpha = gd.AlphaComplex(off_file='alphacomplexdoc.off', precision = precision) simplex_tree = alpha.create_simplex_tree(default_filtration_value = default_filtration_value) assert simplex_tree.dimension() == 2 @@ -207,6 +206,18 @@ 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) diff --git a/src/python/test/test_bottleneck_distance.py b/src/python/test/test_bottleneck_distance.py index 6915bea8..07fcc9cc 100755 --- a/src/python/test/test_bottleneck_distance.py +++ b/src/python/test/test_bottleneck_distance.py @@ -25,3 +25,15 @@ def test_basic_bottleneck(): assert gudhi.bottleneck_distance(diag1, diag2, 0.1) == pytest.approx(0.75, abs=0.1) assert gudhi.hera.bottleneck_distance(diag1, diag2, 0) == 0.75 assert gudhi.hera.bottleneck_distance(diag1, diag2, 0.1) == pytest.approx(0.75, rel=0.1) + + import numpy as np + + # Translating both diagrams along the diagonal should not affect the distance, + # checks that negative numbers are not an issue + diag1 = np.array(diag1) - 100 + diag2 = np.array(diag2) - 100 + + assert gudhi.bottleneck_distance(diag1, diag2) == 0.75 + assert gudhi.bottleneck_distance(diag1, diag2, 0.1) == pytest.approx(0.75, abs=0.1) + assert gudhi.hera.bottleneck_distance(diag1, diag2, 0) == 0.75 + assert gudhi.hera.bottleneck_distance(diag1, diag2, 0.1) == pytest.approx(0.75, rel=0.1) diff --git a/src/python/test/test_representations.py b/src/python/test/test_representations.py index 589cee00..cda1a15b 100755 --- a/src/python/test/test_representations.py +++ b/src/python/test/test_representations.py @@ -4,6 +4,8 @@ import matplotlib.pyplot as plt import numpy as np import pytest +from sklearn.cluster import KMeans + def test_representations_examples(): # Disable graphics for testing purposes @@ -15,6 +17,7 @@ def test_representations_examples(): return None +from gudhi.representations.vector_methods import Atol from gudhi.representations.metrics import * from gudhi.representations.kernel_methods import * @@ -36,8 +39,62 @@ def test_multiple(): d2 = BottleneckDistance(epsilon=0.00001).fit_transform(l1) d3 = pairwise_persistence_diagram_distances(l1, l1b, e=0.00001, n_jobs=4) assert d1 == pytest.approx(d2) - assert d3 == pytest.approx(d2, abs=1e-5) # Because of 0 entries (on the diagonal) + assert d3 == pytest.approx(d2, abs=1e-5) # Because of 0 entries (on the diagonal) d1 = pairwise_persistence_diagram_distances(l1, l2, metric="wasserstein", order=2, internal_p=2) d2 = WassersteinDistance(order=2, internal_p=2, n_jobs=4).fit(l2).transform(l1) print(d1.shape, d2.shape) - assert d1 == pytest.approx(d2, rel=.02) + assert d1 == pytest.approx(d2, rel=0.02) + + +# Test sorted values as points order can be inverted, and sorted test is not documentation-friendly +# Note the test below must be up to date with the Atol class documentation +def test_atol_doc(): + a = np.array([[1, 2, 4], [1, 4, 0], [1, 0, 4]]) + b = np.array([[4, 2, 0], [4, 4, 0], [4, 0, 2]]) + c = np.array([[3, 2, -1], [1, 2, -1]]) + + atol_vectoriser = Atol(quantiser=KMeans(n_clusters=2, random_state=202006)) + # Atol will do + # X = np.concatenate([a,b,c]) + # kmeans = KMeans(n_clusters=2, random_state=202006).fit(X) + # kmeans.labels_ will be : array([1, 0, 1, 0, 0, 1, 0, 0]) + first_cluster = np.asarray([a[0], a[2], b[2]]) + second_cluster = np.asarray([a[1], b[0], b[2], c[0], c[1]]) + + # Check the center of the first_cluster and second_cluster are in Atol centers + centers = atol_vectoriser.fit(X=[a, b, c]).centers + np.isclose(centers, first_cluster.mean(axis=0)).all(1).any() + np.isclose(centers, second_cluster.mean(axis=0)).all(1).any() + + vectorization = atol_vectoriser.transform(X=[a, b, c]) + assert np.allclose(vectorization[0], atol_vectoriser(a)) + assert np.allclose(vectorization[1], atol_vectoriser(b)) + assert np.allclose(vectorization[2], atol_vectoriser(c)) + + +def test_dummy_atol(): + a = np.array([[1, 2, 4], [1, 4, 0], [1, 0, 4]]) + b = np.array([[4, 2, 0], [4, 4, 0], [4, 0, 2]]) + c = np.array([[3, 2, -1], [1, 2, -1]]) + + for weighting_method in ["cloud", "iidproba"]: + for contrast in ["gaussian", "laplacian", "indicator"]: + atol_vectoriser = Atol( + quantiser=KMeans(n_clusters=1, random_state=202006), + weighting_method=weighting_method, + contrast=contrast, + ) + atol_vectoriser.fit([a, b, c]) + atol_vectoriser(a) + atol_vectoriser.transform(X=[a, b, c]) + + +from gudhi.representations.vector_methods import BettiCurve + + +def test_infinity(): + a = np.array([[1.0, 8.0], [2.0, np.inf], [3.0, 4.0]]) + c = BettiCurve(20, [0.0, 10.0])(a) + assert c[1] == 0 + assert c[7] == 3 + assert c[9] == 2 diff --git a/src/python/test/test_simplex_tree.py b/src/python/test/test_simplex_tree.py index 2137d822..a3eacaa9 100755 --- a/src/python/test/test_simplex_tree.py +++ b/src/python/test/test_simplex_tree.py @@ -8,7 +8,7 @@ - YYYY/MM Author: Description of the modification """ -from gudhi import SimplexTree +from gudhi import SimplexTree, __GUDHI_USE_EIGEN3 import pytest __author__ = "Vincent Rouvreau" @@ -340,3 +340,67 @@ def test_simplices_iterator(): assert st.find(simplex[0]) == True print("filtration is: ", simplex[1]) assert st.filtration(simplex[0]) == simplex[1] + +def test_collapse_edges(): + st = SimplexTree() + + assert st.insert([0, 1], filtration=1.0) == True + assert st.insert([1, 2], filtration=1.0) == True + assert st.insert([2, 3], filtration=1.0) == True + assert st.insert([0, 3], filtration=1.0) == True + assert st.insert([0, 2], filtration=2.0) == True + assert st.insert([1, 3], filtration=2.0) == True + + assert st.num_simplices() == 10 + + if __GUDHI_USE_EIGEN3: + st.collapse_edges() + assert st.num_simplices() == 9 + assert st.find([1, 3]) == False + for simplex in st.get_skeleton(0): + assert simplex[1] == 1. + else: + # If no Eigen3, collapse_edges throws an exception + with pytest.raises(RuntimeError): + st.collapse_edges() + +def test_reset_filtration(): + st = SimplexTree() + + assert st.insert([0, 1, 2], 3.) == True + assert st.insert([0, 3], 2.) == True + assert st.insert([3, 4, 5], 3.) == True + assert st.insert([0, 1, 6, 7], 4.) == True + + # Guaranteed by construction + for simplex in st.get_simplices(): + assert st.filtration(simplex[0]) >= 2. + + # dimension until 5 even if simplex tree is of dimension 3 to test the limits + for dimension in range(5, -1, -1): + st.reset_filtration(0., dimension) + for simplex in st.get_skeleton(3): + print(simplex) + if len(simplex[0]) < (dimension) + 1: + assert st.filtration(simplex[0]) >= 2. + else: + assert st.filtration(simplex[0]) == 0. + +def test_boundaries_iterator(): + st = SimplexTree() + + assert st.insert([0, 1, 2, 3], filtration=1.0) == True + assert st.insert([1, 2, 3, 4], filtration=2.0) == True + + assert list(st.get_boundaries([1, 2, 3])) == [([1, 2], 1.0), ([1, 3], 1.0), ([2, 3], 1.0)] + assert list(st.get_boundaries([2, 3, 4])) == [([2, 3], 1.0), ([2, 4], 2.0), ([3, 4], 2.0)] + assert list(st.get_boundaries([2])) == [] + + with pytest.raises(RuntimeError): + list(st.get_boundaries([])) + + with pytest.raises(RuntimeError): + list(st.get_boundaries([0, 4])) # (0, 4) does not exist + + with pytest.raises(RuntimeError): + list(st.get_boundaries([6])) # (6) does not exist diff --git a/src/python/test/test_subsampling.py b/src/python/test/test_subsampling.py index 31f64e32..4019852e 100755 --- a/src/python/test/test_subsampling.py +++ b/src/python/test/test_subsampling.py @@ -141,12 +141,16 @@ def test_simple_sparsify_points(): # assert gudhi.sparsify_point_set(points = [], min_squared_dist = 0.0) == [] # assert gudhi.sparsify_point_set(points = [], min_squared_dist = 10.0) == [] assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=0.0) == point_set - assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=1.0) == point_set - assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=2.0) == [ + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=0.999) == point_set + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=1.001) == [ [0, 1], [1, 0], ] - assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=2.01) == [[0, 1]] + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=1.999) == [ + [0, 1], + [1, 0], + ] + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=2.001) == [[0, 1]] assert ( len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=0.0)) @@ -157,11 +161,11 @@ def test_simple_sparsify_points(): == 5 ) assert ( - len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=40.0)) + len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=40.1)) == 4 ) assert ( - len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=90.0)) + len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=89.9)) == 3 ) assert ( @@ -169,7 +173,7 @@ def test_simple_sparsify_points(): == 2 ) assert ( - len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=325.0)) + len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=324.9)) == 2 ) assert ( diff --git a/src/python/test/test_wasserstein_with_tensors.py b/src/python/test/test_wasserstein_with_tensors.py new file mode 100755 index 00000000..e3f1411a --- /dev/null +++ b/src/python/test/test_wasserstein_with_tensors.py @@ -0,0 +1,47 @@ +""" 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): Mathieu Carriere + + Copyright (C) 2020 Inria + + Modification(s): + - YYYY/MM Author: Description of the modification +""" + +from gudhi.wasserstein import wasserstein_distance as pot +import numpy as np +import torch +import tensorflow as tf + +def test_wasserstein_distance_grad(): + diag1 = torch.tensor([[2.7, 3.7], [9.6, 14.0], [34.2, 34.974]], requires_grad=True) + diag2 = torch.tensor([[2.8, 4.45], [9.5, 14.1]], requires_grad=True) + diag3 = torch.tensor([[2.8, 4.45], [9.5, 14.1]], requires_grad=True) + assert diag1.grad is None and diag2.grad is None and diag3.grad is None + dist12 = pot(diag1, diag2, internal_p=2, order=2, enable_autodiff=True) + dist30 = pot(diag3, torch.tensor([]), internal_p=2, order=2, enable_autodiff=True) + dist12.backward() + dist30.backward() + assert not torch.isnan(diag1.grad).any() and not torch.isnan(diag2.grad).any() and not torch.isnan(diag3.grad).any() + diag4 = torch.tensor([[0., 10.]], requires_grad=True) + diag5 = torch.tensor([[1., 11.], [3., 4.]], requires_grad=True) + dist45 = pot(diag4, diag5, internal_p=1, order=1, enable_autodiff=True) + assert dist45 == 3. + dist45.backward() + assert np.array_equal(diag4.grad, [[-1., -1.]]) + assert np.array_equal(diag5.grad, [[1., 1.], [-1., 1.]]) + diag6 = torch.tensor([[5., 10.]], requires_grad=True) + pot(diag6, diag6, internal_p=2, order=2, enable_autodiff=True).backward() + # https://github.com/jonasrauber/eagerpy/issues/6 + # assert np.array_equal(diag6.grad, [[0., 0.]]) + +def test_wasserstein_distance_grad_tensorflow(): + with tf.GradientTape() as tape: + diag4 = tf.convert_to_tensor(tf.Variable(initial_value=np.array([[0., 10.]]), trainable=True)) + diag5 = tf.convert_to_tensor(tf.Variable(initial_value=np.array([[1., 11.], [3., 4.]]), trainable=True)) + dist45 = pot(diag4, diag5, internal_p=1, order=1, enable_autodiff=True) + assert dist45 == 3. + + grads = tape.gradient(dist45, [diag4, diag5]) + assert np.array_equal(grads[0].values, [[-1., -1.]]) + assert np.array_equal(grads[1].values, [[1., 1.], [-1., 1.]])
\ No newline at end of file |