time_channel.py (8505B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 """Layer for implementing the channel in the time domain""" 6 7 import tensorflow as tf 8 9 from . import GenerateTimeChannel, ApplyTimeChannel 10 from .utils import time_lag_discrete_time_channel 11 12 class TimeChannel(tf.keras.layers.Layer): 13 # pylint: disable=line-too-long 14 r"""TimeChannel(channel_model, bandwidth, num_time_samples, maximum_delay_spread=3e-6, l_min=None, l_max=None, normalize_channel=False, add_awgn=True, return_channel=False, dtype=tf.complex64, **kwargs) 15 16 Generate channel responses and apply them to channel inputs in the time domain. 17 18 This class inherits from the Keras `Layer` class and can be used as layer 19 in a Keras model. 20 21 The channel output consists of ``num_time_samples`` + ``l_max`` - ``l_min`` 22 time samples, as it is the result of filtering the channel input of length 23 ``num_time_samples`` with the time-variant channel filter of length 24 ``l_max`` - ``l_min`` + 1. In the case of a single-input single-output link and given a sequence of channel 25 inputs :math:`x_0,\cdots,x_{N_B}`, where :math:`N_B` is ``num_time_samples``, this 26 layer outputs 27 28 .. math:: 29 y_b = \sum_{\ell = L_{\text{min}}}^{L_{\text{max}}} x_{b-\ell} \bar{h}_{b,\ell} + w_b 30 31 where :math:`L_{\text{min}}` corresponds ``l_min``, :math:`L_{\text{max}}` to ``l_max``, :math:`w_b` to 32 the additive noise, and :math:`\bar{h}_{b,\ell}` to the 33 :math:`\ell^{th}` tap of the :math:`b^{th}` channel sample. 34 This layer outputs :math:`y_b` for :math:`b` ranging from :math:`L_{\text{min}}` to 35 :math:`N_B + L_{\text{max}} - 1`, and :math:`x_{b}` is set to 0 for :math:`b < 0` or :math:`b \geq N_B`. 36 The channel taps :math:`\bar{h}_{b,\ell}` are computed assuming a sinc filter 37 is used for pulse shaping and receive filtering. Therefore, given a channel impulse response 38 :math:`(a_{m}(t), \tau_{m}), 0 \leq m \leq M-1`, generated by the ``channel_model``, 39 the channel taps are computed as follows: 40 41 .. math:: 42 \bar{h}_{b, \ell} 43 = \sum_{m=0}^{M-1} a_{m}\left(\frac{b}{W}\right) 44 \text{sinc}\left( \ell - W\tau_{m} \right) 45 46 for :math:`\ell` ranging from ``l_min`` to ``l_max``, and where :math:`W` is 47 the ``bandwidth``. 48 49 For multiple-input multiple-output (MIMO) links, the channel output is computed for each antenna of each receiver and by summing over all the antennas of all transmitters. 50 51 Parameters 52 ---------- 53 channel_model : :class:`~sionna.channel.ChannelModel` object 54 An instance of a :class:`~sionna.channel.ChannelModel`, such as 55 :class:`~sionna.channel.RayleighBlockFading` or 56 :class:`~sionna.channel.tr38901.UMi`. 57 58 bandwidth : float 59 Bandwidth (:math:`W`) [Hz] 60 61 num_time_samples : int 62 Number of time samples forming the channel input (:math:`N_B`) 63 64 maximum_delay_spread : float 65 Maximum delay spread [s]. 66 Used to compute the default value of ``l_max`` if ``l_max`` is set to 67 `None`. If a value is given for ``l_max``, this parameter is not used. 68 It defaults to 3us, which was found 69 to be large enough to include most significant paths with all channel 70 models included in Sionna assuming a nominal delay spread of 100ns. 71 72 l_min : int 73 Smallest time-lag for the discrete complex baseband channel (:math:`L_{\text{min}}`). 74 If set to `None`, defaults to the value given by :func:`time_lag_discrete_time_channel`. 75 76 l_max : int 77 Largest time-lag for the discrete complex baseband channel (:math:`L_{\text{max}}`). 78 If set to `None`, it is computed from ``bandwidth`` and ``maximum_delay_spread`` 79 using :func:`time_lag_discrete_time_channel`. If it is not set to `None`, 80 then the parameter ``maximum_delay_spread`` is not used. 81 82 add_awgn : bool 83 If set to `False`, no white Gaussian noise is added. 84 Defaults to `True`. 85 86 normalize_channel : bool 87 If set to `True`, the channel is normalized over the block size 88 to ensure unit average energy per time step. Defaults to `False`. 89 90 return_channel : bool 91 If set to `True`, the channel response is returned in addition to the 92 channel output. Defaults to `False`. 93 94 dtype : tf.DType 95 Complex datatype to use for internal processing and output. 96 Defaults to `tf.complex64`. 97 98 Input 99 ----- 100 101 (x, no) or x: 102 Tuple or Tensor: 103 104 x : [batch size, num_tx, num_tx_ant, num_time_samples], tf.complex 105 Channel inputs 106 107 no : Scalar or Tensor, tf.float 108 Scalar or tensor whose shape can be broadcast to the shape of the 109 channel outputs: [batch size, num_rx, num_rx_ant, num_time_samples]. 110 Only required if ``add_awgn`` is set to `True`. 111 The noise power ``no`` is per complex dimension. If ``no`` is a scalar, 112 noise of the same variance will be added to the outputs. 113 If ``no`` is a tensor, it must have a shape that can be broadcast to 114 the shape of the channel outputs. This allows, e.g., adding noise of 115 different variance to each example in a batch. If ``no`` has a lower 116 rank than the channel outputs, then ``no`` will be broadcast to the 117 shape of the channel outputs by adding dummy dimensions after the last 118 axis. 119 120 Output 121 ------- 122 y : [batch size, num_rx, num_rx_ant, num_time_samples + l_max - l_min], tf.complex 123 Channel outputs 124 The channel output consists of ``num_time_samples`` + ``l_max`` - ``l_min`` 125 time samples, as it is the result of filtering the channel input of length 126 ``num_time_samples`` with the time-variant channel filter of length 127 ``l_max`` - ``l_min`` + 1. 128 129 h_time : [batch size, num_rx, num_rx_ant, num_tx, num_tx_ant, num_time_samples + l_max - l_min, l_max - l_min + 1], tf.complex 130 (Optional) Channel responses. Returned only if ``return_channel`` 131 is set to `True`. 132 For each batch example, ``num_time_samples`` + ``l_max`` - ``l_min`` time 133 steps of the channel realizations are generated to filter the channel input. 134 """ 135 136 def __init__(self, channel_model, bandwidth, num_time_samples, 137 maximum_delay_spread=3e-6, l_min=None, l_max=None, 138 normalize_channel=False, add_awgn=True, return_channel=False, 139 dtype=tf.complex64, **kwargs): 140 141 super().__init__(trainable=False, dtype=dtype, **kwargs) 142 143 # Setting l_min and l_max to default values if not given by the user 144 l_min_default, l_max_default = time_lag_discrete_time_channel(bandwidth, 145 maximum_delay_spread) 146 if l_min is None: 147 l_min = l_min_default 148 if l_max is None: 149 l_max = l_max_default 150 151 self._cir_sampler = channel_model 152 self._bandwidth = bandwidth 153 self._num_time_steps = num_time_samples 154 self._l_min = l_min 155 self._l_max = l_max 156 self._l_tot = l_max-l_min+1 157 self._normalize_channel = normalize_channel 158 self._add_awgn = add_awgn 159 self._return_channel = return_channel 160 161 def build(self, input_shape): #pylint: disable=unused-argument 162 163 self._generate_channel = GenerateTimeChannel(self._cir_sampler, 164 self._bandwidth, 165 self._num_time_steps, 166 self._l_min, 167 self._l_max, 168 self._normalize_channel) 169 self._apply_channel = ApplyTimeChannel( self._num_time_steps, 170 self._l_tot, 171 self._add_awgn, 172 tf.as_dtype(self.dtype)) 173 174 def call(self, inputs): 175 176 if self._add_awgn: 177 x, no = inputs 178 else: 179 x = inputs 180 181 h_time = self._generate_channel(tf.shape(x)[0]) 182 if self._add_awgn: 183 y = self._apply_channel([x, h_time, no]) 184 else: 185 y = self._apply_channel([x, h_time]) 186 187 if self._return_channel: 188 return y, h_time 189 else: 190 return y