angular_delay_doppler_spectrum#

sionna.phy.isac.angular_delay_doppler_spectrum(h_dd: torch.Tensor, rx_steering_vectors: torch.Tensor, tx_steering_vectors: torch.Tensor, *, mode: str = 'paired') → torch.Tensor[source]#

Compute a Bartlett-type angular delay-Doppler spectrum.

Let \(\mathbf{H}_{b,r,t,q,\ell}\in\mathbb{C}^{M_\text{R}\times M_\text{T}}\) denote the MIMO channel at Doppler bin \(q\) and delay bin \(\ell\) for transmitter \(t\), receiver \(r\), and arbitrary batch index \(b\). For receive and transmit steering vectors \(\mathbf{a}_{\text{R},r,i}\) and \(\mathbf{a}_{\text{T},t,j}\), the Cartesian spectrum is

\[P_{b,r,i,t,j,q,\ell} = \left| \mathbf{a}_{\text{R},r,i}^{\mathsf{H}} \mathbf{H}_{b,r,t,q,\ell} \mathbf{a}_{\text{T},t,j}^* \right|^2.\]

Both array responses enter conjugated, which is most apparent in index notation,

\[P = \left| \sum_{m=1}^{M_\text{R}}\sum_{n=1}^{M_\text{T}} a_{\text{R},m}^*\,H_{mn}\,a_{\text{T},n}^* \right|^2.\]

The Hermitian transpose above merely reflects that \(\mathbf{a}_\text{R}\) is contracted from the left, where a row vector is required, whereas \(\mathbf{a}_\text{T}\) must stay a column.

This conjugation pattern differs from the classical Bartlett spectrum \(\mathbf{a}^{\mathsf{H}}\mathbf{R}\mathbf{a}\), which is defined on a covariance matrix and therefore already carries a conjugation in its own outer product. A propagation channel does not: a target contributes \(\mathbf{H}=\beta\mathbf{a}_\text{R}\mathbf{a}_\text{T}^{\mathsf{T}}\) with a plain transpose, so both scan vectors must be conjugated for the phases to cancel. Scanning that target then gives \(\beta\|\mathbf{a}_\text{R}\|^2\|\mathbf{a}_\text{T}\|^2\) instead of a sum of squared phasors.

Parameters:
  • h_dd (torch.Tensor) – MIMO delay-Doppler channel, shape […, num_rx, num_rx_ant, num_tx, num_tx_ant, num_doppler_bins, num_delay_bins]. The last dimension corresponds to the time lags returned by ofdm_to_delay_doppler_channel().

  • rx_steering_vectors (torch.Tensor) – Shared receive steering vectors with shape [num_rx_directions, num_rx_ant], or receiver-specific vectors with shape [num_rx, num_rx_directions, num_rx_ant]. Array responses as returned by steering_vectors(); they are conjugated internally.

  • tx_steering_vectors (torch.Tensor) – Shared transmit steering vectors with shape [num_tx_directions, num_tx_ant], or transmitter-specific vectors with shape [num_tx, num_tx_directions, num_tx_ant]. Array responses as returned by steering_vectors(); they are conjugated internally.

  • mode (str) – If "paired", direction indices are paired. Their counts must then match or one count must be one. If "cartesian", all receive and transmit direction combinations are evaluated. Defaults to "paired".

Outputs:

spectrum – torch.float. Linear power spectrum. Paired mode returns […, num_rx, num_tx, num_direction_pairs, num_doppler_bins, num_delay_bins]. Cartesian mode returns […, num_rx, num_rx_directions, num_tx, num_tx_directions, num_doppler_bins, num_delay_bins].

Notes

The output is an angular delay-Doppler spectrum. Converting delay to range depends on the sensing geometry. For a monostatic system, \(R=c\tau/2\); for a bistatic system, delay represents the total transmitter-target-receiver path length divided by \(c\).

For the unit-norm steering vectors returned by steering_vectors(), no spectrum value exceeds the channel energy \(\|\mathbf{H}_{b,r,t,q,\ell}\|_\text{F}^2\). If the receive and transmit steering vectors each form an orthonormal basis, the spectrum summed over all direction pairs equals this energy. For single-antenna devices, the spectrum reduces to \(|H_{b,r,t,q,\ell}|^2\).

The channel and both steering banks are promoted to their common torch.promote_types() data type, so the output precision follows the widest input rather than that of h_dd.

Cartesian mode carries a separate axis for the receive and transmit directions and therefore scales with their product. Scanning the same direction at both ends, as in a monostatic system, is much cheaper in paired mode.

Examples

import torch
from sionna.phy.isac import (angular_delay_doppler_spectrum,
    steering_vectors)

# Half-wavelength uniform linear array with four antennas
wavelength = 0.1
positions = torch.zeros(4, 3)
positions[:, 1] = torch.arange(4)*wavelength/2

# Scan 25 azimuth directions in the horizontal plane, then flatten
# the grid into a list of directions
theta = torch.tensor([torch.pi/2])
phi = torch.deg2rad(torch.linspace(-60., 60., 25))
steering = steering_vectors(positions, theta, phi, wavelength)
steering = steering.reshape(-1, positions.shape[0])

# Delay-Doppler channel as returned by
# sionna.phy.channel.ofdm_to_delay_doppler_channel, with shape
# [batch, num_rx, num_rx_ant, num_tx, num_tx_ant, num_doppler_bins,
# num_delay_bins]
h_dd = torch.randn(1, 1, 4, 1, 4, 32, 64, dtype=torch.complex64)

# Monostatic scan: pair each RX direction with the same TX direction
spectrum = angular_delay_doppler_spectrum(h_dd, steering, steering)
print(spectrum.shape)
# torch.Size([1, 1, 1, 25, 32, 64])

# Cartesian mode evaluates all RX-TX direction combinations instead
spectrum = angular_delay_doppler_spectrum(h_dd, steering, steering,
                                          mode="cartesian")
print(spectrum.shape)
# torch.Size([1, 1, 25, 1, 25, 32, 64])