From c87a1f10e048477d210ae0abd657da87bba1102a Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 12 May 2020 20:36:38 +0200 Subject: test + reformat --- src/python/test/test_dtm.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/python/test/test_dtm.py') diff --git a/src/python/test/test_dtm.py b/src/python/test/test_dtm.py index bff4c267..34d28d4d 100755 --- a/src/python/test/test_dtm.py +++ b/src/python/test/test_dtm.py @@ -8,10 +8,11 @@ - YYYY/MM Author: Description of the modification """ -from gudhi.point_cloud.dtm import DistanceToMeasure +from gudhi.point_cloud.dtm import DistanceToMeasure, DTMDensity import numpy import pytest import torch +import math def test_dtm_compare_euclidean(): @@ -66,3 +67,11 @@ def test_dtm_precomputed(): dtm = DistanceToMeasure(2, q=2, metric="neighbors") r = dtm.fit_transform(dist) assert r == pytest.approx([2.0, 0.707, 3.5355], rel=0.01) + + +def test_density_normalized(): + sample = numpy.random.normal(0, 1, (1000000, 2)) + queries = numpy.array([[0.0, 0.0], [-0.5, 0.7], [0.4, 1.7]]) + expected = numpy.exp(-(queries ** 2).sum(-1) / 2) / (2 * math.pi) + estimated = DTMDensity(k=150, normalize=True).fit(sample).transform(queries) + assert estimated == pytest.approx(expected, rel=0.4) -- cgit v1.2.3 From c5fca5477cc6fff77acedf7b5324eb5f8b417ed3 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 12 May 2020 22:31:42 +0200 Subject: More test --- src/python/doc/point_cloud_sum.inc | 4 ++-- src/python/gudhi/point_cloud/dtm.py | 4 ++-- src/python/test/test_dtm.py | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src/python/test/test_dtm.py') diff --git a/src/python/doc/point_cloud_sum.inc b/src/python/doc/point_cloud_sum.inc index d4761aba..d28f387a 100644 --- a/src/python/doc/point_cloud_sum.inc +++ b/src/python/doc/point_cloud_sum.inc @@ -3,8 +3,8 @@ +----------------------------------------------------------------+------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+ | | :math:`(x_1, x_2, \ldots, x_d)` | Utilities to process point clouds: read from file, subsample, | :Authors: Vincent Rouvreau, Marc Glisse, Masatoshi Takenouchi | - | | :math:`(y_1, y_2, \ldots, y_d)` | find neighbors, embed time series in higher dimension, etc. | | - | | | :Since: GUDHI 2.0.0 | + | | :math:`(y_1, y_2, \ldots, y_d)` | find neighbors, embed time series in higher dimension, estimate | | + | | a density, etc. | :Since: GUDHI 2.0.0 | | | | | | | | :License: MIT (`GPL v3 `_, BSD-3-Clause, Apache-2.0) | | | Parts of this package require CGAL. | | diff --git a/src/python/gudhi/point_cloud/dtm.py b/src/python/gudhi/point_cloud/dtm.py index 4454d8a2..88f197e7 100644 --- a/src/python/gudhi/point_cloud/dtm.py +++ b/src/python/gudhi/point_cloud/dtm.py @@ -89,7 +89,7 @@ class DTMDensity: weights (numpy.array): weights of each of the k neighbors, optional. They are supposed to sum to 1. q (float): order used to compute the distance to measure. Defaults to dim. dim (float): final exponent representing the dimension. Defaults to the dimension, and must be specified - when the dimension cannot be read from the input (metric="neighbors" or metric="precomputed"). + when the dimension cannot be read from the input (metric is "neighbors" or "precomputed"). normalize (bool): normalize the density so it corresponds to a probability measure on ℝᵈ. Only available for the Euclidean metric, defaults to False. n_samples (int): number of sample points used for fitting. Only needed if `normalize` is True and @@ -146,7 +146,7 @@ class DTMDensity: if q is None: q = dim if self.params["metric"] == "neighbors": - distances = X[:, : self.k] + distances = np.asarray(X)[:, : self.k] else: distances = self.knn.transform(X) distances = distances ** q diff --git a/src/python/test/test_dtm.py b/src/python/test/test_dtm.py index 34d28d4d..8ab0cc44 100755 --- a/src/python/test/test_dtm.py +++ b/src/python/test/test_dtm.py @@ -75,3 +75,10 @@ def test_density_normalized(): expected = numpy.exp(-(queries ** 2).sum(-1) / 2) / (2 * math.pi) estimated = DTMDensity(k=150, normalize=True).fit(sample).transform(queries) assert estimated == pytest.approx(expected, rel=0.4) + + +def test_density(): + distances = [[0, 1, 10], [2, 0, 30], [1, 3, 5]] + density = DTMDensity(k=2, metric="neighbors", dim=1).fit_transform(distances) + expected = numpy.array([2.0, 1.0, 0.5]) + assert density == pytest.approx(expected) -- cgit v1.2.3 From 2287b727126ffb9fc47869ac9ed6b6bd61c6605a Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Mon, 18 May 2020 23:54:02 +0200 Subject: Infer k when we pass the distances to the nearest neighbors --- src/python/gudhi/point_cloud/dtm.py | 23 +++++++++++++++++------ src/python/test/test_dtm.py | 4 ++++ 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src/python/test/test_dtm.py') diff --git a/src/python/gudhi/point_cloud/dtm.py b/src/python/gudhi/point_cloud/dtm.py index 88f197e7..d836c28d 100644 --- a/src/python/gudhi/point_cloud/dtm.py +++ b/src/python/gudhi/point_cloud/dtm.py @@ -85,7 +85,8 @@ class DTMDensity: def __init__(self, k=None, weights=None, q=None, dim=None, normalize=False, n_samples=None, **kwargs): """ Args: - k (int): number of neighbors (possibly including the point itself). + k (int): number of neighbors (possibly including the point itself). Optional if it can be guessed + from weights or metric="neighbors". weights (numpy.array): weights of each of the k neighbors, optional. They are supposed to sum to 1. q (float): order used to compute the distance to measure. Defaults to dim. dim (float): final exponent representing the dimension. Defaults to the dimension, and must be specified @@ -98,9 +99,12 @@ class DTMDensity: :func:`transform` expects an array with the distances to the k nearest neighbors. """ if weights is None: - assert k is not None, "Must specify k or weights" self.k = k - self.weights = np.full(k, 1.0 / k) + if k is None: + assert kwargs.get("metric") == "neighbors", 'Must specify k or weights, unless metric is "neighbors"' + self.weights = None + else: + self.weights = np.full(k, 1.0 / k) else: self.weights = weights self.k = len(weights) @@ -145,14 +149,21 @@ class DTMDensity: dim = len(X[0]) if q is None: q = dim + k = self.k + weights = self.weights if self.params["metric"] == "neighbors": - distances = np.asarray(X)[:, : self.k] + distances = np.asarray(X) + if weights is None: + k = distances.shape[1] + weights = np.full(k, 1.0 / k) + else: + distances = distances[:, :k] else: distances = self.knn.transform(X) distances = distances ** q - dtm = (distances * self.weights).sum(-1) + dtm = (distances * weights).sum(-1) if self.normalize: - dtm /= (np.arange(1, self.k + 1) ** (q / dim) * self.weights).sum() + dtm /= (np.arange(1, k + 1) ** (q / dim) * weights).sum() density = dtm ** (-dim / q) if self.normalize: import math diff --git a/src/python/test/test_dtm.py b/src/python/test/test_dtm.py index 8ab0cc44..8d400c7e 100755 --- a/src/python/test/test_dtm.py +++ b/src/python/test/test_dtm.py @@ -82,3 +82,7 @@ def test_density(): density = DTMDensity(k=2, metric="neighbors", dim=1).fit_transform(distances) expected = numpy.array([2.0, 1.0, 0.5]) assert density == pytest.approx(expected) + distances = [[0, 1], [2, 0], [1, 3]] + density = DTMDensity(metric="neighbors", dim=1).fit_transform(distances) + expected = numpy.array([2.0, 1.0, 0.5]) + assert density == pytest.approx(expected) -- cgit v1.2.3 From 35d5ac4e6bb79ec41b35c0df611207b9cd578f49 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 19 May 2020 18:10:16 +0200 Subject: Test with explicit weights and remove duplicated assignment --- src/python/test/test_dtm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/python/test/test_dtm.py') diff --git a/src/python/test/test_dtm.py b/src/python/test/test_dtm.py index 8d400c7e..0a52279e 100755 --- a/src/python/test/test_dtm.py +++ b/src/python/test/test_dtm.py @@ -84,5 +84,6 @@ def test_density(): assert density == pytest.approx(expected) distances = [[0, 1], [2, 0], [1, 3]] density = DTMDensity(metric="neighbors", dim=1).fit_transform(distances) - expected = numpy.array([2.0, 1.0, 0.5]) + assert density == pytest.approx(expected) + density = DTMDensity(weights=[0.5, 0.5], metric="neighbors", dim=1).fit_transform(distances) assert density == pytest.approx(expected) -- cgit v1.2.3