#
# SPDX-FileCopyrightText: Copyright (c) 2021-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Class for generating channel frequency responses."""
from typing import Optional
import torch
from sionna.phy.object import Object
from sionna.phy.config import Precision
from sionna.phy.ofdm import ResourceGrid
from .channel_model import ChannelModel
from .utils import subcarrier_frequencies, cir_to_ofdm_channel
__all__ = ["GenerateOFDMChannel"]
[docs]
class GenerateOFDMChannel(Object):
r"""Generates channel frequency responses.
The channel impulse response is constant over the duration of an OFDM symbol.
Given a channel impulse response
:math:`(a_{m}(t), \tau_{m}), 0 \leq m \leq M-1`, generated by the ``channel_model``,
the channel frequency response for the :math:`s^{th}` OFDM symbol and
:math:`n^{th}` subcarrier is computed as follows:
.. math::
\widehat{h}_{s, n} = \sum_{m=0}^{M-1} a_{m}(s) e^{-j2\pi n \Delta_f \tau_{m}}
where :math:`\Delta_f` is the subcarrier spacing, and :math:`s` is used as time
step to indicate that the channel impulse response can change from one OFDM symbol to the
next in the event of mobility, even if it is assumed static over the duration
of an OFDM symbol.
:param channel_model: Channel model to be used.
:param resource_grid: Resource grid.
:param normalize_channel: If set to `True`, the channel is normalized over the resource grid
to ensure unit average energy per resource element. Defaults to `False`.
:param precision: Precision used for internal calculations and outputs.
If set to `None`, :attr:`~sionna.phy.config.Config.precision` is used.
:param device: Device for computation (e.g., 'cpu', 'cuda:0').
If `None`, :attr:`~sionna.phy.config.Config.device` is used.
:input batch_size: `None` (default) | `int`.
Batch size. Defaults to `None` for channel models that do not require this parameter.
:output h_freq: [batch size, num_rx, num_rx_ant, num_tx, num_tx_ant, num_ofdm_symbols, num_subcarriers], `torch.complex`.
Channel frequency responses.
.. rubric:: Examples
.. code-block:: python
import torch
from sionna.phy.channel import GenerateOFDMChannel, RayleighBlockFading
# Create a simple resource grid-like object
class SimpleResourceGrid:
num_ofdm_symbols = 14
fft_size = 64
subcarrier_spacing = 15e3
cyclic_prefix_length = 4
@property
def ofdm_symbol_duration(self):
return (1 + self.cyclic_prefix_length / self.fft_size) / self.subcarrier_spacing
rg = SimpleResourceGrid()
channel_model = RayleighBlockFading(num_rx=1, num_rx_ant=2, num_tx=1, num_tx_ant=2)
gen_ch = GenerateOFDMChannel(channel_model, rg)
h_freq = gen_ch(batch_size=32)
print(h_freq.shape)
# torch.Size([32, 1, 2, 1, 2, 14, 64])
"""
def __init__(
self,
channel_model: ChannelModel,
resource_grid: ResourceGrid,
normalize_channel: bool = False,
precision: Optional[Precision] = None,
device: Optional[str] = None,
**kwargs,
) -> None:
super().__init__(precision=precision, device=device, **kwargs)
# Callable used to sample channel impulse responses
self._cir_sampler = channel_model
# Extract parameters from resource grid
self._num_ofdm_symbols = resource_grid.num_ofdm_symbols
self._subcarrier_spacing = resource_grid.subcarrier_spacing
self._num_subcarriers = resource_grid.fft_size
self._normalize_channel = normalize_channel
self._sampling_frequency = 1.0 / resource_grid.ofdm_symbol_duration
# Frequencies of the subcarriers
self._frequencies = subcarrier_frequencies(
self._num_subcarriers,
self._subcarrier_spacing,
precision=self.precision,
device=self.device,
)
def __call__(self, batch_size: Optional[int] = None) -> torch.Tensor:
"""Generate channel frequency responses."""
# Sample channel impulse responses
h, tau = self._cir_sampler(
batch_size, self._num_ofdm_symbols, self._sampling_frequency
)
# Convert CIR to OFDM channel frequency response
h_freq = cir_to_ofdm_channel(self._frequencies, h, tau, self._normalize_channel)
return h_freq