Source code for pyFDN.train.features.spatial
"""Spatial features impulse response."""
from __future__ import annotations
import torch
[docs]
def mimo_rir_eigenvalues_per_frequency(
ir: torch.Tensor,
n_fft: int = 2048,
dim: int = 0,
) -> torch.Tensor:
r"""Compute eigenvalues of a MIMO transfer matrix at each frequency bin.
The impulse response is transformed to the frequency domain using
:func:`torch.fft.rfft`, and the eigenvalues of the resulting transfer
matrix are computed independently at each frequency bin.
Parameters
----------
ir : torch.Tensor
MIMO impulse response with shape ``(n_samples, n_out, n_in)`` (time
axis at ``dim``). The system must be square, i.e. ``n_out == n_in``.
n_fft : int, default 2048
DFT length. A response longer than ``n_fft`` is truncated, a shorter
one zero-padded, as in :func:`torch.fft.rfft`.
dim : int, default 0
Temporal dimension of the impulse response; the two remaining axes
are read as ``(n_out, n_in)`` in that order.
Returns
-------
torch.Tensor
Complex eigenvalues with shape
``(n_freqs, n_out)``, where ``n_freqs = n_fft // 2 + 1``.
Notes
-----
Eigenvalues are returned in the (unsorted) order of
:func:`torch.linalg.eigvals`, which can change between neighbouring bins,
and their gradient is ill-conditioned where eigenvalues are repeated.
Raises
------
ValueError
If ``ir`` is not 3D or the MIMO system is not square.
"""
if not isinstance(ir, torch.Tensor):
raise TypeError(f"Expected torch.Tensor, got {type(ir).__name__}")
if ir.ndim != 3:
raise ValueError(
f"Expected a 3D tensor of shape (n_samples, n_out, n_in), "
f"got shape {tuple(ir.shape)}."
)
ir = ir.movedim(dim, 0)
n_out, n_in = ir.shape[1:]
if n_out != n_in:
raise ValueError(f"Expected a square MIMO system, got {n_out}x{n_in}.")
h_freq = torch.fft.rfft(ir, n=n_fft, dim=0)
return torch.linalg.eigvals(h_freq)