omnibias.graph.torch.ops.spectral
Spectral graph operators and differentiable relaxations for ordering, assignment, and graph-structured learning.
import omnibias.graph.torch.ops.spectralModule contract & mathematical conventions
Differentiable spectral graph operators (torch).
All operators take a weighted adjacency matrix A of shape (n, n) (assumed
symmetric / undirected, non-negative, zero diagonal) and are differentiable in
A. The three Laplacians follow the standard definitions with the isolated-node
convention 0 for the inverse-degree factors:
L = D - A, \qquad L_{\mathrm{sym}} = I - D^{-1/2} A D^{-1/2}, \qquad L_{\mathrm{rw}} = I - D^{-1} A,
with D = \operatorname{diag}(A\mathbf 1). Spectral embedding, the heat
kernel e^{-tL}, and the Rayleigh-Ritz cut relaxation are built from the
symmetric eigendecomposition (torch.linalg.eigh), which is differentiable for
matrices with simple spectrum.
Shared formulas do not imply universal floating-point identity. Verify dtype, shape, derivative-order support, and the stated numerical assumptions for the path you use.
CutRelaxation
omnibias.graph.torch.ops.CutRelaxationSource ↗class CutRelaxationDataclass constructor fields; inherited fields, if any, are declared by the base class. A field(...) expression describes a default factory or field options, not a literal argument default.
Rayleigh-Ritz relaxation of the k-way ratio / normalized cut.
Attributes
embedding (n, k) matrix of the k smallest Laplacian eigenvectors -- the continuous relaxation of the cluster-indicator matrix.
relaxed_cut_value sum of the k smallest eigenvalues: a lower bound on the discrete minimum cut (the relaxation only *loosens* the combinatorial problem).
eigenvalues The k smallest eigenvalues (ascending).
Parameters
| Name | Type & default | Contract |
|---|---|---|
embeddingdataclass field | TensorRequired | (n, k) matrix of the k smallest Laplacian eigenvectors -- the continuous relaxation of the cluster-indicator matrix. |
relaxed_cut_valuedataclass field | TensorRequired | sum of the k smallest eigenvalues: a lower bound on the discrete minimum cut (the relaxation only *loosens* the combinatorial problem). |
eigenvaluesdataclass field | TensorRequired | The k smallest eigenvalues (ascending). |
Fields & methods
embeddingfield
embedding: Tensor(n, k) matrix of the k smallest Laplacian eigenvectors -- the continuous relaxation of the cluster-indicator matrix.
relaxed_cut_valuefield
relaxed_cut_value: Tensorsum of the k smallest eigenvalues: a lower bound on the discrete minimum cut (the relaxation only *loosens* the combinatorial problem).
Read the implementation
class CutRelaxation:
"""Rayleigh-Ritz relaxation of the ``k``-way ratio / normalized cut.
Attributes
----------
embedding
``(n, k)`` matrix of the ``k`` smallest Laplacian eigenvectors -- the
continuous relaxation of the cluster-indicator matrix.
relaxed_cut_value
``sum`` of the ``k`` smallest eigenvalues: a lower bound on the discrete
minimum cut (the relaxation only *loosens* the combinatorial problem).
eigenvalues
The ``k`` smallest eigenvalues (ascending).
"""
embedding: Tensor
relaxed_cut_value: Tensor
eigenvalues: TensorOther public import paths
omnibias.graph.torch.ops.CutRelaxationomnibias.graph.torch.ops.spectral.CutRelaxation
degree
omnibias.graph.torch.ops.degreeSource ↗degree(adjacency: Tensor) -> TensorWeighted node degrees d_i = sum_j A_{ij} -> shape (n,).
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
TensorRead the implementation
def degree(adjacency: Tensor) -> Tensor:
r"""Weighted node degrees ``d_i = sum_j A_{ij}`` -> shape ``(n,)``."""
_check_adjacency(adjacency)
return adjacency.sum(dim=-1)Other public import paths
omnibias.graph.torch.ops.degreeomnibias.graph.torch.ops.spectral.degree
fiedler_vector
omnibias.graph.torch.ops.fiedler_vectorSource ↗fiedler_vector(adjacency: Tensor, *, normalized: bool=False) -> TensorThe eigenvector of the second-smallest Laplacian eigenvalue -> (n,).
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
normalizedkeyword-only | boolDefault: False | See the operation contract and implementation below. |
TensorExecuted regression · fiedler separates blocks
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_fiedler_separates_blocks(self) -> None:
a = two_block_sbm(5, 1.0, 0.02)
v = G.fiedler_vector(a).numpy()
block0, block1 = v[:5], v[5:]
# each block has a consistent sign; the two blocks have opposite sign
assert np.all(np.sign(block0) == np.sign(block0[0]))
assert np.all(np.sign(block1) == np.sign(block1[0]))
assert np.sign(block0[0]) != np.sign(block1[0])python -m pytest "packages/omnibias-graph/tests/test_spectral_torch.py::TestSBMFiedler::test_fiedler_separates_blocks" -qRead the implementation
def fiedler_vector(adjacency: Tensor, *, normalized: bool = False) -> Tensor:
r"""The eigenvector of the second-smallest Laplacian eigenvalue -> ``(n,)``."""
_, evecs = laplacian_spectrum(adjacency, normalized=normalized)
return evecs[:, 1]Other public import paths
omnibias.graph.torch.ops.fiedler_vectoromnibias.graph.torch.ops.spectral.fiedler_vector
graph_heat_kernel
omnibias.graph.torch.ops.graph_heat_kernelSource ↗graph_heat_kernel(adjacency: Tensor, t: float, *, normalized: bool=False) -> TensorHeat kernel H = exp(-t L) via the symmetric eigendecomposition.
H_{ij} is the amount of heat diffused from node j to node i after
time t. For the combinatorial Laplacian each column sums to 1 only in
the t -> inf equilibrium; H is symmetric positive-definite for all
t >= 0.
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
tpositional-or-keyword | floatRequired | See the operation contract and implementation below. |
normalizedkeyword-only | boolDefault: False | See the operation contract and implementation below. |
TensorExplicit validation & failure cases
- When
t < 0, raisesValueError(f'diffusion time t must be >= 0; got {t}').
These are explicit source guards. Backend operations may also raise for invalid shapes, dtypes, or unsupported devices.
Executed regression · negative time raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_negative_time_raises(self) -> None:
with pytest.raises(ValueError, match="t must be"):
G.graph_heat_kernel(ring_adjacency(4), -0.1)python -m pytest "packages/omnibias-graph/tests/test_spectral_torch.py::TestValidation::test_negative_time_raises" -qRegression example · heat kernel parity
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_heat_kernel_parity(t: float) -> None:
m = _sym_adjacency(5)
_assert_close(GT.graph_heat_kernel(torch.tensor(m), t), GJ.graph_heat_kernel(jnp.asarray(m), t))python -m pytest packages/omnibias-graph/tests/test_cross_backend.py -k test_heat_kernel_parity -qRead the implementation
def graph_heat_kernel(
adjacency: Tensor, t: float, *, normalized: bool = False
) -> Tensor:
r"""Heat kernel ``H = exp(-t L)`` via the symmetric eigendecomposition.
``H_{ij}`` is the amount of heat diffused from node ``j`` to node ``i`` after
time ``t``. For the combinatorial Laplacian each column sums to ``1`` only in
the ``t -> inf`` equilibrium; ``H`` is symmetric positive-definite for all
``t >= 0``.
"""
if t < 0:
raise ValueError(f"diffusion time t must be >= 0; got {t}")
evals, evecs = laplacian_spectrum(adjacency, normalized=normalized)
damp = torch.exp(-t * evals)
return (evecs * damp.unsqueeze(-2)) @ evecs.transpose(-1, -2)Other public import paths
omnibias.graph.torch.ops.graph_heat_kernelomnibias.graph.torch.ops.spectral.graph_heat_kernel
graph_laplacian
omnibias.graph.torch.ops.graph_laplacianSource ↗graph_laplacian(adjacency: Tensor) -> TensorCombinatorial Laplacian L = D - A.
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
TensorExecuted regression · non square raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_non_square_raises(self) -> None:
with pytest.raises(ValueError, match="square"):
G.graph_laplacian(torch.zeros(3, 4))python -m pytest "packages/omnibias-graph/tests/test_spectral_torch.py::TestValidation::test_non_square_raises" -qRegression example · graph laplacian definition
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_graph_laplacian_definition(self) -> None:
a = two_block_sbm(3, 1.0, 0.1)
lap = G.graph_laplacian(a)
d = a.sum(dim=-1)
assert torch.allclose(torch.diag(lap), d)
# off-diagonal is -A
off = lap - torch.diag(torch.diag(lap))
assert torch.allclose(off, -a + torch.diag(torch.diag(a)))
# rows of the combinatorial Laplacian sum to zero
assert torch.allclose(lap.sum(dim=-1), torch.zeros(6), atol=1e-12)python -m pytest packages/omnibias-graph/tests/test_spectral_torch.py -k test_graph_laplacian_definition -qRead the implementation
def graph_laplacian(adjacency: Tensor) -> Tensor:
r"""Combinatorial Laplacian ``L = D - A``."""
_check_adjacency(adjacency)
d = adjacency.sum(dim=-1)
return torch.diag(d) - adjacencyOther public import paths
omnibias.graph.torch.ops.graph_laplacianomnibias.graph.torch.ops.spectral.graph_laplacian
laplacian_spectrum
omnibias.graph.torch.ops.laplacian_spectrumSource ↗laplacian_spectrum(adjacency: Tensor, *, normalized: bool=False) -> tuple[Tensor, Tensor]Ascending eigenvalues / eigenvectors of the (symmetric) Laplacian.
Returns (eigenvalues (n,), eigenvectors (n, n)) with columns as
eigenvectors, sorted by ascending eigenvalue (torch.linalg.eigh).
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
normalizedkeyword-only | boolDefault: False | See the operation contract and implementation below. |
tuple[Tensor, Tensor]Executed regression · heat kernel parity
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_heat_kernel_parity(t: float) -> None:
m = _sym_adjacency(5)
_assert_close(GT.graph_heat_kernel(torch.tensor(m), t), GJ.graph_heat_kernel(jnp.asarray(m), t))python -m pytest "packages/omnibias-graph/tests/test_cross_backend.py::test_heat_kernel_parity[0.1]" -qRegression example · eigenvalue parity
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_eigenvalue_parity(normalized: bool) -> None:
m = _sym_adjacency(3)
et, _ = GT.laplacian_spectrum(torch.tensor(m), normalized=normalized)
ej, _ = GJ.laplacian_spectrum(jnp.asarray(m), normalized=normalized)
_assert_close(et, ej)python -m pytest packages/omnibias-graph/tests/test_cross_backend.py -k test_eigenvalue_parity -qRead the implementation
def laplacian_spectrum(
adjacency: Tensor, *, normalized: bool = False
) -> tuple[Tensor, Tensor]:
r"""Ascending eigenvalues / eigenvectors of the (symmetric) Laplacian.
Returns ``(eigenvalues (n,), eigenvectors (n, n))`` with columns as
eigenvectors, sorted by ascending eigenvalue (`torch.linalg.eigh`).
"""
lap = _symmetric_laplacian(adjacency, normalized=normalized)
lap = 0.5 * (lap + lap.transpose(-1, -2))
evals, evecs = torch.linalg.eigh(lap)
return evals, evecsOther public import paths
omnibias.graph.torch.ops.laplacian_spectrumomnibias.graph.torch.ops.spectral.laplacian_spectrum
normalized_laplacian
omnibias.graph.torch.ops.normalized_laplacianSource ↗normalized_laplacian(adjacency: Tensor) -> TensorSymmetric normalized Laplacian L_sym = I - D^{-1/2} A D^{-1/2}.
Isolated nodes (d_i = 0) contribute 0 to the normalization. The
spectrum lies in [0, 2].
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
TensorExecuted regression · laplacian parity
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_laplacian_parity(seed: int, name: str) -> None:
m = _sym_adjacency(seed)
_assert_close(getattr(GT, name)(torch.tensor(m)), getattr(GJ, name)(jnp.asarray(m)))python -m pytest "packages/omnibias-graph/tests/test_cross_backend.py::test_laplacian_parity[normalized_laplacian-0]" -qRegression example · normalized spectrum in 0 2
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_normalized_spectrum_in_0_2(self) -> None:
a = ring_adjacency(10)
ln = G.normalized_laplacian(a)
ev = torch.linalg.eigvalsh(ln)
assert float(ev.min()) > -1e-10
assert float(ev.max()) < 2.0 + 1e-10
# smallest eigenvalue is 0 (connected graph)
assert abs(float(ev.min())) < 1e-10python -m pytest packages/omnibias-graph/tests/test_spectral_torch.py -k test_normalized_spectrum_in_0_2 -qRead the implementation
def normalized_laplacian(adjacency: Tensor) -> Tensor:
r"""Symmetric normalized Laplacian ``L_sym = I - D^{-1/2} A D^{-1/2}``.
Isolated nodes (``d_i = 0``) contribute ``0`` to the normalization. The
spectrum lies in ``[0, 2]``.
"""
_check_adjacency(adjacency)
d = adjacency.sum(dim=-1)
d_inv_sqrt = torch.where(
d > 0, d.rsqrt(), torch.zeros_like(d)
)
a_norm = d_inv_sqrt.unsqueeze(-1) * adjacency * d_inv_sqrt.unsqueeze(-2)
n = adjacency.shape[0]
eye = torch.eye(n, dtype=adjacency.dtype, device=adjacency.device)
return eye - a_normOther public import paths
omnibias.graph.torch.ops.normalized_laplacianomnibias.graph.torch.ops.spectral.normalized_laplacian
random_walk_laplacian
omnibias.graph.torch.ops.random_walk_laplacianSource ↗random_walk_laplacian(adjacency: Tensor) -> TensorRandom-walk Laplacian L_rw = I - D^{-1} A (row-stochastic transition).
Not symmetric in general; isolated nodes contribute 0.
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
TensorExecuted regression · laplacian parity
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_laplacian_parity(seed: int, name: str) -> None:
m = _sym_adjacency(seed)
_assert_close(getattr(GT, name)(torch.tensor(m)), getattr(GJ, name)(jnp.asarray(m)))python -m pytest "packages/omnibias-graph/tests/test_cross_backend.py::test_laplacian_parity[random_walk_laplacian-0]" -qRegression example · random walk rows of transition sum to one
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_random_walk_rows_of_transition_sum_to_one(self) -> None:
a = two_block_sbm(3, 1.0, 0.2)
lrw = G.random_walk_laplacian(a)
# L_rw = I - P, so P = I - L_rw has rows summing to 1
n = a.shape[0]
p = torch.eye(n) - lrw
assert torch.allclose(p.sum(dim=-1), torch.ones(n), atol=1e-12)python -m pytest packages/omnibias-graph/tests/test_spectral_torch.py -k test_random_walk_rows_of_transition_sum_to_one -qRead the implementation
def random_walk_laplacian(adjacency: Tensor) -> Tensor:
r"""Random-walk Laplacian ``L_rw = I - D^{-1} A`` (row-stochastic transition).
Not symmetric in general; isolated nodes contribute ``0``.
"""
_check_adjacency(adjacency)
d = adjacency.sum(dim=-1)
d_inv = torch.where(d > 0, d.reciprocal(), torch.zeros_like(d))
n = adjacency.shape[0]
eye = torch.eye(n, dtype=adjacency.dtype, device=adjacency.device)
return eye - d_inv.unsqueeze(-1) * adjacencyOther public import paths
omnibias.graph.torch.ops.random_walk_laplacianomnibias.graph.torch.ops.spectral.random_walk_laplacian
spectral_clustering_relaxation
omnibias.graph.torch.ops.spectral_clustering_relaxationSource ↗spectral_clustering_relaxation(adjacency: Tensor, k: int, *, normalized: bool=True) -> CutRelaxationContinuous eigenvector relaxation of the k-way graph cut.
The discrete balanced k-cut is NP-hard; its standard convex *relaxation*
min_Y tr(Y^T L Y) s.t. Y^T Y = I is solved exactly by the k
smallest Laplacian eigenvectors (Rayleigh-Ritz). This returns that relaxation
only -- the discrete rounding step (k-means on the embedding) is **not**
performed and exact balanced partitioning is out of scope (see the
graph-limitation cookbook).
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
kpositional-or-keyword | intRequired | See the operation contract and implementation below. |
normalizedkeyword-only | boolDefault: True | See the operation contract and implementation below. |
CutRelaxationExplicit validation & failure cases
- When
k < 1, raisesValueError(f'k must be >= 1; got {k}'). - When
k > adjacency.shape[0], raisesValueError(f'k={k} exceeds the number of nodes {adjacency.shape[0]}').
These are explicit source guards. Backend operations may also raise for invalid shapes, dtypes, or unsupported devices.
Executed regression · bad k raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_bad_k_raises(self) -> None:
with pytest.raises(ValueError, match="exceeds"):
G.spectral_clustering_relaxation(ring_adjacency(4), k=5)python -m pytest "packages/omnibias-graph/tests/test_spectral_torch.py::TestValidation::test_bad_k_raises" -qRegression example · relaxed value lower bounds are nonnegative
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_relaxed_value_lower_bounds_are_nonnegative(self) -> None:
a = ring_adjacency(9)
rel = G.spectral_clustering_relaxation(a, k=3, normalized=True)
assert float(rel.relaxed_cut_value) >= -1e-12python -m pytest packages/omnibias-graph/tests/test_spectral_torch.py -k test_relaxed_value_lower_bounds_are_nonnegative -qRead the implementation
def spectral_clustering_relaxation(
adjacency: Tensor, k: int, *, normalized: bool = True
) -> CutRelaxation:
r"""Continuous eigenvector relaxation of the ``k``-way graph cut.
The discrete balanced ``k``-cut is NP-hard; its standard convex *relaxation*
``min_Y tr(Y^T L Y)`` s.t. ``Y^T Y = I`` is solved exactly by the ``k``
smallest Laplacian eigenvectors (Rayleigh-Ritz). This returns that relaxation
only -- the discrete rounding step (k-means on the embedding) is **not**
performed and exact balanced partitioning is out of scope (see the
graph-limitation cookbook).
"""
if k < 1:
raise ValueError(f"k must be >= 1; got {k}")
if k > adjacency.shape[0]:
raise ValueError(f"k={k} exceeds the number of nodes {adjacency.shape[0]}")
evals, evecs = laplacian_spectrum(adjacency, normalized=normalized)
return CutRelaxation(
embedding=evecs[:, :k],
relaxed_cut_value=evals[:k].sum(),
eigenvalues=evals[:k],
)Other public import paths
omnibias.graph.torch.ops.spectral_clustering_relaxationomnibias.graph.torch.ops.spectral.spectral_clustering_relaxation
spectral_embedding
omnibias.graph.torch.ops.spectral_embeddingSource ↗spectral_embedding(adjacency: Tensor, n_components: int, *, normalized: bool=True, drop_first: bool=True) -> TensorLaplacian-eigenmaps embedding: the smallest non-trivial eigenvectors.
Returns an (n, n_components) matrix whose columns are the eigenvectors of
the Laplacian for the smallest eigenvalues. With drop_first=True the
(constant, eigenvalue-0) null vector of a connected graph is skipped so
the first returned column is the **Fiedler vector**.
Parameters
| Name | Type & default | Contract |
|---|---|---|
adjacencypositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
n_componentspositional-or-keyword | intRequired | See the operation contract and implementation below. |
normalizedkeyword-only | boolDefault: True | See the operation contract and implementation below. |
drop_firstkeyword-only | boolDefault: True | See the operation contract and implementation below. |
TensorExplicit validation & failure cases
- When
n_components < 1, raisesValueError(f'n_components must be >= 1; got {n_components}'). - When
start + n_components > n, raisesValueError(f'cannot take {n_components} components (drop_first={drop_first}) from an {n}-node graph').
These are explicit source guards. Backend operations may also raise for invalid shapes, dtypes, or unsupported devices.
Executed regression · too many components raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_too_many_components_raises(self) -> None:
with pytest.raises(ValueError, match="cannot take"):
G.spectral_embedding(ring_adjacency(3), n_components=3, drop_first=True)python -m pytest "packages/omnibias-graph/tests/test_spectral_torch.py::TestValidation::test_too_many_components_raises" -qRegression example · embedding shape and drop first
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_embedding_shape_and_drop_first(self) -> None:
a = two_block_sbm(4, 1.0, 0.05)
emb = G.spectral_embedding(a, n_components=2, normalized=True)
assert emb.shape == (8, 2)
# with drop_first the constant null vector is excluded: columns are
# orthogonal to the all-ones vector up to normalization scale
col0 = emb[:, 0]
assert abs(float(col0.sum())) < 1e-6python -m pytest packages/omnibias-graph/tests/test_spectral_torch.py -k test_embedding_shape_and_drop_first -qRead the implementation
def spectral_embedding(
adjacency: Tensor,
n_components: int,
*,
normalized: bool = True,
drop_first: bool = True,
) -> Tensor:
r"""Laplacian-eigenmaps embedding: the smallest non-trivial eigenvectors.
Returns an ``(n, n_components)`` matrix whose columns are the eigenvectors of
the Laplacian for the smallest eigenvalues. With ``drop_first=True`` the
(constant, eigenvalue-``0``) null vector of a connected graph is skipped so
the first returned column is the **Fiedler vector**.
"""
if n_components < 1:
raise ValueError(f"n_components must be >= 1; got {n_components}")
n = adjacency.shape[0]
start = 1 if drop_first else 0
if start + n_components > n:
raise ValueError(
f"cannot take {n_components} components (drop_first={drop_first}) "
f"from an {n}-node graph"
)
_, evecs = laplacian_spectrum(adjacency, normalized=normalized)
return evecs[:, start : start + n_components]Other public import paths
omnibias.graph.torch.ops.spectral_embeddingomnibias.graph.torch.ops.spectral.spectral_embedding