-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdistances.py
More file actions
67 lines (50 loc) · 1.94 KB
/
distances.py
File metadata and controls
67 lines (50 loc) · 1.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import numpy as np
from spectral import solve_and_sort_std_eigv_problem, normalized_Laplacian, unnormalized_Laplacian
def CTD_matrix(
W,
laplacian_type="unnormalized"
):
"""Calculates the Commute Time Distance (CTD) matrix."""
d = np.sum(W, axis=1)
Vol = np.sum(d)
if laplacian_type not in ("normalized", "unnormalized"):
raise ValueError("Unsupported laplacian_type")
if laplacian_type == "normalized":
L = normalized_Laplacian(W)
elif laplacian_type == "unnormalized":
L = unnormalized_Laplacian(W)
# Solve Eigen-problem
eigvs, eigvecs = solve_and_sort_std_eigv_problem(L)
if np.sum(eigvs < 1e-10) > 1:
raise ValueError(
"CTD requires a single connected component."
)
# Skip the first eigenvalue/vector
# eigvecs are column-wise
vals = eigvs[1:]
Phi = eigvecs[:, 1:]
if laplacian_type == "unnormalized":
# Standard CTE: sqrt(Vol) * Phi * diag(1/sqrt(Lambda))
inv_sqrt_vals = 1.0 / np.sqrt(vals + 1e-10)
CTE = np.sqrt(Vol) * (Phi * inv_sqrt_vals) # broadcast
elif laplacian_type == "normalized":
# Normalized CTE: sqrt(Vol) * D^(-1/2) * Phi * diag(1/sqrt(Lambda))
d_inv_sqrt = 1.0 / np.sqrt(d + 1e-12)
inv_sqrt_vals = 1.0 / np.sqrt(vals + 1e-10)
# Apply degree scaling to the eigenvectors
CTE = np.sqrt(Vol) * (d_inv_sqrt[:, np.newaxis] * Phi) * inv_sqrt_vals
# Squared Euclidean Distance
sq_norms = np.sum(CTE**2, axis=1)
C = sq_norms[:, np.newaxis] + sq_norms[np.newaxis, :] - 2 * np.dot(CTE, CTE.T)
return C, vals
def asymp_CTD_matrix(W):
# Extract the diagonal (degrees)
d = np.sum(W, axis=1)
Vol = np.sum(d)
# Inverse degrees
inv_d = 1.0 / (d + 1e-12)
# Broadcasting: (N, 1) + (1, N) creates the (N, N) matrix
C = Vol * (inv_d[:, np.newaxis] + inv_d[np.newaxis, :])
# Distance to self is 0
np.fill_diagonal(C, 0)
return C