Skip to content

Commit f4fd945

Browse files
authored
Merge branch 'master' into master
2 parents d98e9bc + 6bb7562 commit f4fd945

16 files changed

+1212
-201
lines changed

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include *.txt
2+
include *.md

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
[![PyPI version](https://badge.fury.io/py/persim.svg)](https://badge.fury.io/py/persim)
22
[![Downloads](https://pypip.in/download/persim/badge.svg)](https://pypi.python.org/pypi/persim/)
3+
[![Conda Version](https://img.shields.io/conda/vn/conda-forge/persim.svg)](https://anaconda.org/conda-forge/persim)
4+
[![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/persim.svg)](https://anaconda.org/conda-forge/persim)
35
[![Build Status](https://travis-ci.org/scikit-tda/persim.svg?branch=master)](https://travis-ci.org/scikit-tda/persim)
46
[![codecov](https://codecov.io/gh/scikit-tda/persim/branch/master/graph/badge.svg)](https://codecov.io/gh/scikit-tda/persim)
57
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8+
9+
610
<img align="right" width="40" height="40" src="https://imgur.com/8p6VwFm.jpg">
711

812
Persim is a Python package for many tools used in analyzing Persistence Diagrams. It currently houses implementations of
913

1014
- Persistence Images
1115
- Bottleneck distance
16+
- Modified Gromov&ndash;Hausdorff distance
1217
- Sliced Wasserstein Kernel
1318
- Heat Kernel
1419
- Diagram plotting

RELEASE.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
0.1.1
2+
- Fix bug in Wasserstein and bottleneck distance.
3+
4+
0.1.0
5+
- Include Wasserstein distance.
6+
7+
0.0.10
8+
- Add license and README to distributed packages
9+
0.0.9
10+
- Include modified Gromov--Hausdorff distance
111
0.0.8
212
- Include diagram plotting
313
- revamped documentation

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Persim is a Python package for many tools used in analyzing Persistence Diagrams
44

55
- Persistence Images
66
- Bottleneck distance
7+
- Modified Gromov--Hausdorff distance
78
- Sliced Wasserstein Kernel
89
- Heat Kernel
910
- Diagram plotting

docs/reference/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ Distances
99
:toctree: stubs
1010
:nosignatures:
1111

12+
persim.wasserstein
1213
persim.bottleneck
1314
persim.sliced_wasserstein
1415
persim.heat
16+
persim.gromov_hausdorff
1517

1618

1719
Persistence Images

persim/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from .images import *
22
from .sliced_wasserstein import *
33
from .bottleneck import *
4+
from .wasserstein import *
45
from .heat import *
6+
from .gromov_hausdorff import *
57
from .visuals import *
6-
from .plot import *
78

89
from ._version import __version__

persim/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.8"
1+
__version__ = "0.1.2"

persim/bottleneck.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
3-
Implementation of the bottleneck distance
3+
Implementation of the bottleneck distance using binary
4+
search and the Hopcroft-Karp algorithm
45
56
Author: Chris Tralie
67
@@ -10,15 +11,19 @@
1011

1112
from bisect import bisect_left
1213
from hopcroftkarp import HopcroftKarp
14+
import warnings
1315

1416
__all__ = ["bottleneck"]
1517

18+
1619
def bottleneck(dgm1, dgm2, matching=False):
1720
"""
1821
Perform the Bottleneck distance matching between persistence diagrams.
1922
Assumes first two columns of S and T are the coordinates of the persistence
2023
points, but allows for other coordinate columns (which are ignored in
21-
diagonal matching)
24+
diagonal matching).
25+
26+
See the `distances` notebook for an example of how to use this.
2227
2328
Parameters
2429
-----------
@@ -27,28 +32,46 @@ def bottleneck(dgm1, dgm2, matching=False):
2732
dgm2: Nx(>=2)
2833
array of birth/death paris for PD 2
2934
matching: bool, default False
30-
if True, return matching information
35+
if True, return matching infromation and cross-similarity matrix
3136
3237
Returns
3338
--------
3439
3540
d: float
3641
bottleneck distance between dgm1 and dgm2
37-
matching: tuples of matched indices
38-
if input `matching=True`, then return matching
39-
D: (N+M)x(N+M) cross-similarity matrix
40-
if input `matching=True`, then return D
42+
(matching, D): Only returns if `matching=True`
43+
(tuples of matched indices, (N+M)x(N+M) cross-similarity matrix)
4144
"""
4245

4346
return_matching = matching
4447

4548
S = np.array(dgm1)
46-
S = S[np.isfinite(S[:, 1]), :]
49+
M = min(S.shape[0], S.size)
50+
if S.size > 0:
51+
S = S[np.isfinite(S[:, 1]), :]
52+
if S.shape[0] < M:
53+
warnings.warn(
54+
"dgm1 has points with non-finite death times;"+
55+
"ignoring those points"
56+
)
57+
M = S.shape[0]
4758
T = np.array(dgm2)
48-
T = T[np.isfinite(T[:, 1]), :]
49-
50-
N = S.shape[0]
51-
M = T.shape[0]
59+
N = min(T.shape[0], T.size)
60+
if T.size > 0:
61+
T = T[np.isfinite(T[:, 1]), :]
62+
if T.shape[0] < N:
63+
warnings.warn(
64+
"dgm2 has points with non-finite death times;"+
65+
"ignoring those points"
66+
)
67+
N = T.shape[0]
68+
69+
if M == 0:
70+
S = np.array([[0, 0]])
71+
M = 1
72+
if N == 0:
73+
T = np.array([[0, 0]])
74+
N = 1
5275

5376
# Step 1: Compute CSM between S and T, including points on diagonal
5477
# L Infinity distance
@@ -60,21 +83,19 @@ def bottleneck(dgm1, dgm2, matching=False):
6083

6184
# Put diagonal elements into the matrix, being mindful that Linfinity
6285
# balls meet the diagonal line at a diamond vertex
63-
D = np.zeros((N + M, N + M))
64-
D[0:N, 0:M] = DUL
65-
UR = np.max(D) * np.ones((N, N))
86+
D = np.zeros((M + N, M + N))
87+
D[0:M, 0:N] = DUL
88+
UR = np.max(D) * np.ones((M, M))
6689
np.fill_diagonal(UR, 0.5 * (S[:, 1] - S[:, 0]))
67-
D[0:N, M::] = UR
68-
UL = np.max(D) * np.ones((M, M))
90+
D[0:M, N::] = UR
91+
UL = np.max(D) * np.ones((N, N))
6992
np.fill_diagonal(UL, 0.5 * (T[:, 1] - T[:, 0]))
70-
D[N::, 0:M] = UL
93+
D[M::, 0:N] = UL
7194

7295
# Step 2: Perform a binary search + Hopcroft Karp to find the
7396
# bottleneck distance
74-
N = D.shape[0]
75-
ds = np.unique(D.flatten())
76-
ds = ds[ds > 0]
77-
ds = np.sort(ds)
97+
M = D.shape[0]
98+
ds = np.sort(np.unique(D.flatten()))
7899
bdist = ds[-1]
79100
matching = {}
80101
while len(ds) >= 1:
@@ -83,18 +104,18 @@ def bottleneck(dgm1, dgm2, matching=False):
83104
idx = bisect_left(range(ds.size), int(ds.size / 2))
84105
d = ds[idx]
85106
graph = {}
86-
for i in range(N):
87-
graph["%s" % i] = {j for j in range(N) if D[i, j] <= d}
107+
for i in range(M):
108+
graph["%s" % i] = {j for j in range(M) if D[i, j] <= d}
88109
res = HopcroftKarp(graph).maximum_matching()
89-
if len(res) == 2 * N and d < bdist:
110+
if len(res) == 2 * M and d <= bdist:
90111
bdist = d
91112
matching = res
92113
ds = ds[0:idx]
93114
else:
94-
ds = ds[idx + 1 : :]
115+
ds = ds[idx + 1::]
95116

96117
if return_matching:
97-
matchidx = [(i, matching["%i" % i]) for i in range(N)]
118+
matchidx = [(i, matching["%i" % i]) for i in range(M)]
98119
return bdist, (matchidx, D)
99120
else:
100121
return bdist

0 commit comments

Comments
 (0)