Coverage for tests/spaces/test_graph.py: 100%
55 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-16 21:43 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-16 21:43 +0000
1import warnings
3import lab as B
4import numpy as np
5import pytest
7from geometric_kernels.jax import * # noqa
8from geometric_kernels.kernels import MaternGeometricKernel
9from geometric_kernels.spaces import Graph
10from geometric_kernels.tensorflow import * # noqa
11from geometric_kernels.torch import * # noqa
13from ..data import (
14 TEST_GRAPH_ADJACENCY,
15 TEST_GRAPH_LAPLACIAN,
16 TEST_GRAPH_LAPLACIAN_NORMALIZED,
17)
18from ..helper import check_function_with_backend, np_to_backend
20warnings.filterwarnings("ignore", category=RuntimeWarning, module="scipy")
22A = TEST_GRAPH_ADJACENCY
23L = TEST_GRAPH_LAPLACIAN
26@pytest.mark.parametrize("normalized", [True, False])
27@pytest.mark.parametrize(
28 "backend", ["numpy", "tensorflow", "torch", "jax", "scipy_sparse"]
29)
30def test_laplacian(normalized, backend):
32 # Check that the Laplacian is computed correctly.
33 check_function_with_backend(
34 backend,
35 TEST_GRAPH_LAPLACIAN if not normalized else TEST_GRAPH_LAPLACIAN_NORMALIZED,
36 lambda adj: Graph(adj, normalize_laplacian=normalized)._laplacian,
37 TEST_GRAPH_ADJACENCY,
38 )
41@pytest.mark.parametrize(
42 "L", [TEST_GRAPH_ADJACENCY.shape[0], TEST_GRAPH_ADJACENCY.shape[0] // 2]
43)
44@pytest.mark.parametrize("normalized", [True, False])
45@pytest.mark.parametrize(
46 "backend", ["numpy", "tensorflow", "torch", "jax", "scipy_sparse"]
47)
48def test_eigendecomposition(L, normalized, backend):
49 laplacian = np_to_backend(
50 TEST_GRAPH_LAPLACIAN if not normalized else TEST_GRAPH_LAPLACIAN_NORMALIZED,
51 backend,
52 )
54 def eigendiff(adj):
55 graph = Graph(adj, normalize_laplacian=normalized)
57 eigenvalue_mat = B.diag_construct(graph.get_eigenvalues(L)[:, 0])
58 eigenvectors = graph.get_eigenvectors(L)
59 # If the backend is scipy_sparse, convert eigenvalues/eigenvectors,
60 # which are always supposed to be dense arrays, to sparse arrays.
61 if backend == "scipy_sparse":
62 import scipy.sparse as sp
64 eigenvalue_mat = sp.csr_array(eigenvalue_mat)
65 eigenvectors = sp.csr_array(eigenvectors)
67 laplace_x_eigvecs = laplacian @ eigenvectors
68 eigvals_x_eigvecs = eigenvectors @ eigenvalue_mat
69 return laplace_x_eigvecs - eigvals_x_eigvecs
71 # Check that the computed eigenpairs are actually eigenpairs
72 check_function_with_backend(
73 backend,
74 np.zeros((TEST_GRAPH_ADJACENCY.shape[0], L)),
75 eigendiff,
76 TEST_GRAPH_ADJACENCY,
77 )
80@pytest.mark.parametrize("nu, lengthscale", [(1.0, 1.0), (2.0, 1.0), (np.inf, 1.0)])
81@pytest.mark.parametrize("sparse_adj", [True, False])
82@pytest.mark.parametrize("normalized", [True, False])
83@pytest.mark.parametrize(
84 "backend", ["numpy", "tensorflow", "torch", "jax"]
85) # The kernels never take sparse parameters and never output sparse matrices, thus we don't test scipy_sparse. The fact that the adjacency matrix may be sparse is tested when sparse_adj is True.
86def test_matern_kernels(nu, lengthscale, sparse_adj, normalized, backend):
88 laplacian = (
89 TEST_GRAPH_LAPLACIAN if not normalized else TEST_GRAPH_LAPLACIAN_NORMALIZED
90 )
92 evals_np, evecs_np = np.linalg.eigh(laplacian)
93 evecs_np *= np.sqrt(laplacian.shape[0])
95 def evaluate_kernel(adj, nu, lengthscale):
96 dtype = B.dtype(adj)
97 if sparse_adj:
98 adj = np_to_backend(B.to_numpy(adj), "scipy_sparse")
99 graph = Graph(adj, normalize_laplacian=normalized)
100 kernel = MaternGeometricKernel(graph)
101 return kernel.K(
102 {"nu": nu, "lengthscale": lengthscale},
103 B.range(dtype, adj.shape[0])[:, None],
104 )
106 if nu < np.inf:
107 K = (
108 evecs_np
109 @ np.diag(np.power(evals_np + 2 * nu / lengthscale**2, -nu))
110 @ evecs_np.T
111 )
112 else:
113 K = evecs_np @ np.diag(np.exp(-(lengthscale**2) / 2 * evals_np)) @ evecs_np.T
114 K = K / np.mean(K.diagonal())
116 # Check that the kernel matrix is correctly computed
117 check_function_with_backend(
118 backend,
119 K,
120 evaluate_kernel,
121 TEST_GRAPH_ADJACENCY,
122 np.array([nu]),
123 np.array([lengthscale]),
124 )