Source code for sionna.phy.nr.carrier_config

#
# SPDX-FileCopyrightText: Copyright (c) 2021-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Carrier configuration for the NR (5G) module of Sionna PHY."""

from .config import Config


__all__ = ["CarrierConfig"]


[docs] class CarrierConfig(Config): """Sets parameters for a specific OFDM numerology, as described in Section 4 :cite:p:`3GPPTS38211`. All configurable properties can be provided as keyword arguments during initialization or changed later. .. rubric:: Examples >>> from sionna.phy.nr import CarrierConfig >>> carrier_config = CarrierConfig(n_cell_id=41) >>> carrier_config.subcarrier_spacing = 30 """ def __init__(self, **kwargs): self._name = "Carrier Configuration" super().__init__(**kwargs) self.check_config() # ----------------------------- # Configurable parameters # ----------------------------- @property def n_cell_id(self) -> int: r"""`int`, (default 1) | [0,...,1007] : Physical layer cell identity :math:`N_\text{ID}^\text{cell}`.""" self._ifndef("n_cell_id", 1) return self._n_cell_id @n_cell_id.setter def n_cell_id(self, value: int) -> None: if value not in range(1008): raise ValueError("n_cell_id must be in the range from 0 to 1007") self._n_cell_id = value @property def cyclic_prefix(self) -> str: """'normal' (default) | 'extended' : Cyclic prefix length. The option 'normal' corresponds to 14 OFDM symbols per slot, while 'extended' corresponds to 12 OFDM symbols. The latter option is only possible with a `subcarrier_spacing` of 60 kHz. """ self._ifndef("cyclic_prefix", "normal") return self._cyclic_prefix @cyclic_prefix.setter def cyclic_prefix(self, value: str) -> None: if value not in ["normal", "extended"]: raise ValueError("Invalid cyclic prefix") self._cyclic_prefix = value @property def subcarrier_spacing(self) -> float: r"""`float`, (default 15) | 30 | 60 | 120 | 240 | 480 | 960 : Subcarrier spacing :math:`\Delta f` [kHz].""" self._ifndef("subcarrier_spacing", 15) return self._subcarrier_spacing @subcarrier_spacing.setter def subcarrier_spacing(self, value: float) -> None: if value not in [15, 30, 60, 120, 240, 480, 960]: raise ValueError("Invalid subcarrier spacing") self._subcarrier_spacing = value @property def n_size_grid(self) -> int: r"""`int`, (default 4) | [1,...,275] : Number of resource blocks in the carrier resource grid :math:`N^{\text{size},\mu}_{\text{grid},x}`.""" self._ifndef("n_size_grid", 4) return self._n_size_grid @n_size_grid.setter def n_size_grid(self, value: int) -> None: if value not in range(1, 276): raise ValueError("n_size_grid must be in the range from 1 to 275") self._n_size_grid = value @property def n_start_grid(self) -> int: r"""`int`, (default 0) | [0,...,2199] : Start of resource grid relative to common resource block (CRB) 0 :math:`N^{\text{start},\mu}_{\text{grid},x}`.""" self._ifndef("n_start_grid", 0) return self._n_start_grid @n_start_grid.setter def n_start_grid(self, value: int) -> None: if value not in range(0, 2200): raise ValueError("n_start_grid must be in the range from 0 to 2199") self._n_start_grid = value @property def slot_number(self) -> int: r"""`int`, (default 0), [0,...,num_slots_per_frame] : Slot number within a frame :math:`n^\mu_{s,f}`.""" self._ifndef("slot_number", 0) return self._slot_number @slot_number.setter def slot_number(self, value: int) -> None: if not 0 <= value < self.num_slots_per_frame: raise ValueError("slot_number cannot exceed the number of slots per frame-1") self._slot_number = value @property def frame_number(self) -> int: r"""`int`, (default 0), [0,...,1023] : System frame number :math:`n_\text{f}`.""" self._ifndef("frame_number", 0) return self._frame_number @frame_number.setter def frame_number(self, value: int) -> None: if value not in range(0, 1024): raise ValueError("frame_number must be in [0, 1023]") self._frame_number = value # -------------------------- # Read-only parameters # -------------------------- @property def num_symbols_per_slot(self) -> int: r"""`int`, (default 14) | 12 : Number of OFDM symbols per slot :math:`N_\text{symb}^\text{slot}`. Configured through the `cyclic_prefix`. """ if self.cyclic_prefix == "normal": return 14 else: return 12 @property def num_slots_per_subframe(self) -> int: r"""`int`, (default 1) | 2 | 4 | 8 | 16 | 32 | 64 : Number of slots per subframe :math:`N_\text{slot}^{\text{subframe},\mu}`. Depends on the `subcarrier_spacing`. """ spacing_map = {15: 1, 30: 2, 60: 4, 120: 8, 240: 16, 480: 32, 960: 64} return spacing_map[self.subcarrier_spacing] @property def num_slots_per_frame(self) -> int: r"""`int`, (default 10) | 20 | 40 | 80 | 160 | 320 | 640 : Number of slots per frame :math:`N_\text{slot}^{\text{frame},\mu}`. Depends on the `subcarrier_spacing`. """ return 10 * self.num_slots_per_subframe @property def mu(self) -> int: r"""`int`, (default 0) | 1 | 2 | 3 | 4 | 5 | 6 : Subcarrier spacing configuration, :math:`\Delta f = 2^\mu 15` kHz.""" return [15, 30, 60, 120, 240, 480, 960].index(self.subcarrier_spacing) @property def frame_duration(self) -> float: r"""`float`, (default 10e-3) : Duration of a frame :math:`T_\text{f}` [s].""" return 10e-3 @property def sub_frame_duration(self) -> float: r"""`float`, (default 1e-3), read-only : Duration of a subframe :math:`T_\text{sf}` [s].""" return 1e-3 @property def t_c(self) -> float: r"""`float`, (default 0.509e-9) : Sampling time :math:`T_\text{c}` [s] for subcarrier spacing 480kHz.""" return 1 / (480e3 * 4096) @property def t_s(self) -> float: r"""`float`, (default 32.552e-9) : Sampling time :math:`T_\text{s}` [s] for subcarrier spacing 15kHz.""" return 1 / (15e3 * 2048) @property def kappa(self) -> float: r"""`float`, (default 64) : The constant :math:`\kappa = T_\text{s}/T_\text{c}`.""" return 64.0 @property def cyclic_prefix_length(self) -> float: r"""`float` : Cyclic prefix length :math:`N_{\text{CP},l}^{\mu} \cdot T_{\text{c}}` [s].""" if self.cyclic_prefix == "extended": cp = 512 * self.kappa * 2 ** (-self.mu) else: cp = 144 * self.kappa * 2 ** (-self.mu) if self.slot_number in [0, 7 * 2**self.mu]: cp += 16 * self.kappa return cp * self.t_c # ------------------- # Class methods # -------------------
[docs] def check_config(self) -> None: """Test if the configuration is valid.""" if self.cyclic_prefix == "extended": if self.subcarrier_spacing != 60: raise ValueError( "Extended cyclic prefix only valid for 60kHz subcarrier spacing" ) attr_list = [ "n_cell_id", "cyclic_prefix", "subcarrier_spacing", "n_size_grid", "slot_number", "frame_number", ] for attr in attr_list: value = getattr(self, attr) setattr(self, attr, value)