Files
2026-07-13 12:49:22 +08:00

90 lines
3.2 KiB
Python

from hypothesis import strategies as st
import numpy as np
from scipy.sparse import csr_matrix
@st.composite
def knn_graph_strategy(draw, num_samples, k_neighbors, min_distance=0.0, max_distance=100.0):
"""
Generate a K-nearest neighbors (KNN) graph based on the given parameters.
Parameters
----------
draw: A function used to draw values from search strategies.
num_samples (int or SearchStrategy): The number of samples in the graph.
If a SearchStrategy is provided, a value will be drawn from it.
k_neighbors (int or SearchStrategy): The number of nearest neighbors to consider for each sample.
If a SearchStrategy is provided, a value will be drawn from it.
Returns
-------
knn_graph : csr_matrix
The KNN graph represented as a sparse matrix.
Notes
-----
- The KNN graph is generated based on a symmetric distance matrix.
- The distance matrix is computed using randomly generated upper triangle values.
- The diagonal of the distance matrix is set to infinity to avoid selecting a point as its own neighbor.
- The K-nearest neighbors are computed based on the distance matrix.
- The resulting KNN graph is returned as a sparse matrix in csr format.
- The number of samples must be greater than the number of neighbors.
- The KNN graph is not guaranteed to be connected (i.e. there may be isolated subgraphs).
- The KNN graph is a directed graph (i.e. the edges are not symmetric).
- The neighbors are sorted by distance in the CSR-formatted sparse matrix,
so the first neighbor is the closest neighbor.
"""
# If the argument is a strategy, draw a value from it.
if isinstance(num_samples, st.SearchStrategy):
num_samples = draw(num_samples)
if isinstance(k_neighbors, st.SearchStrategy):
k_neighbors = draw(k_neighbors)
# Generate a symmetric distance matrix
upper_triangle = [
draw(
st.lists(
st.floats(
min_value=min_distance,
max_value=max_distance,
allow_nan=False,
allow_infinity=False,
allow_subnormal=False,
),
min_size=i,
max_size=i,
unique=True,
)
)
for i in range(1, num_samples + 1)
]
distance_matrix = np.zeros((num_samples, num_samples))
for i, row in enumerate(upper_triangle):
distance_matrix[i, : i + 1] = row
distance_matrix[: i + 1, i] = row
np.fill_diagonal(
distance_matrix, np.inf
) # To ensure we don't select a point as its own neighbor
# Compute k-nearest neighbors based on the distance matrix
sorted_indices = np.argsort(distance_matrix, axis=1)
kneighbor_indices = sorted_indices[:, :k_neighbors]
kneighbor_distances = np.array(
[distance_matrix[i, kneighbor_indices[i]] for i in range(num_samples)]
)
knn_graph = csr_matrix(
(
kneighbor_distances.flatten(),
kneighbor_indices.flatten(),
np.arange(0, (kneighbor_distances.shape[0] * k_neighbors + 1), k_neighbors),
),
shape=(num_samples, num_samples),
)
return knn_graph