carrier_config.py (8408B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 """Carrier configuration for the nr (5G) sub-package of the Sionna library. 6 """ 7 # pylint: disable=line-too-long 8 9 from .config import Config 10 11 class CarrierConfig(Config): 12 """ 13 The CarrierConfig objects sets parameters for a specific OFDM numerology, 14 as described in Section 4 [3GPP38211]_. 15 16 All configurable properties can be provided as keyword arguments during the 17 initialization or changed later. 18 19 Example 20 ------- 21 >>> carrier_config = CarrierConfig(n_cell_id=41) 22 >>> carrier_config.subcarrier_spacing = 30 23 """ 24 def __init__(self, **kwargs): 25 self._name = "Carrier Configuration" 26 super().__init__(**kwargs) 27 self.check_config() 28 29 #-----------------------------# 30 #---Configurable parameters---# 31 #-----------------------------# 32 33 #---n_cell_id---# 34 @property 35 def n_cell_id(self): 36 r""" 37 int, 1 (default) | [0,...,1007] : Physical layer cell identity 38 :math:`N_\text{ID}^\text{cell}` 39 """ 40 self._ifndef("n_cell_id", 1) 41 return self._n_cell_id 42 43 @n_cell_id.setter 44 def n_cell_id(self, value): 45 assert value in range(1008), \ 46 "n_cell_id must be in the range from 0 to 1007" 47 self._n_cell_id = value 48 49 #---cyclic_prefix---# 50 @property 51 def cyclic_prefix(self): 52 """ 53 str, "normal" (default) | "extended" : Cyclic prefix length 54 55 The option "normal" corresponds to 14 OFDM symbols per slot, while 56 "extended" corresponds to 12 OFDM symbols. The latter option is 57 only possible with a `subcarrier_spacing` of 60 kHz. 58 """ 59 self._ifndef("cyclic_prefix", "normal") 60 return self._cyclic_prefix 61 62 @cyclic_prefix.setter 63 def cyclic_prefix(self, value): 64 assert value in ["normal", "extended"], "Invalid cyclic prefix" 65 self._cyclic_prefix = value 66 67 #---subcarrier_spacing---# 68 @property 69 def subcarrier_spacing(self): 70 r""" 71 float, 15 (default) | 30 | 60 | 120 | 240 | 480 | 960 : Subcarrier 72 spacing :math:`\Delta f` [kHz] 73 """ 74 self._ifndef("subcarrier_spacing", 15) 75 return self._subcarrier_spacing 76 77 @subcarrier_spacing.setter 78 def subcarrier_spacing(self, value): 79 assert value in [15, 30, 60, 120, 240, 480, 960], \ 80 "Invalid subcarrier spacing" 81 self._subcarrier_spacing = value 82 83 #---n_size_grid---# 84 @property 85 def n_size_grid(self): 86 r""" 87 int, 4 (default) | [1,...,275] : Number of resource blocks in the 88 carrier resource grid :math:`N^{\text{size},\mu}_{\text{grid},x}` 89 """ 90 self._ifndef("n_size_grid", 4) 91 return self._n_size_grid 92 93 @n_size_grid.setter 94 def n_size_grid(self, value): 95 assert value in range(1,276), \ 96 "n_size_grid must be in the range from 1 to 275" 97 self._n_size_grid = value 98 99 #---n_start_grid---# 100 @property 101 def n_start_grid(self): 102 r""" 103 int, 0 (default) | [0,...,2199] : Start of resource grid relative to 104 common resource block (CRB) 0 105 :math:`N^{\text{start},\mu}_{\text{grid},x}` 106 """ 107 self._ifndef("n_start_grid", 0) 108 return self._n_start_grid 109 110 @n_start_grid.setter 111 def n_start_grid(self, value): 112 assert value in range(0,2200), \ 113 "n_start_grid must be in the range from 0 to 2199" 114 self._n_start_grid = value 115 116 #---slot_number---# 117 @property 118 def slot_number(self): 119 r""" 120 int, 0 (default), [0,...,num_slots_per_frame] : Slot number within a frame 121 :math:`n^\mu_{s,f}` 122 """ 123 self._ifndef("slot_number", 0) 124 return self._slot_number 125 126 @slot_number.setter 127 def slot_number(self, value): 128 assert 0<=value<self.num_slots_per_frame, \ 129 "slot_number cannot exceed the number of slots per frame-1" 130 self._slot_number = value 131 132 #---frame_number---# 133 @property 134 def frame_number(self): 135 r""" 136 int, 0 (default), [0,...,1023] : System frame number :math:`n_\text{f}` 137 """ 138 self._ifndef("frame_number", 0) 139 return self._frame_number 140 141 @frame_number.setter 142 def frame_number(self, value): 143 assert value in range(0,1024), "frame_number must be in [0, 1023]" 144 self._frame_number = value 145 146 #--------------------------# 147 #---Read-only parameters---# 148 #--------------------------# 149 150 @property 151 def num_symbols_per_slot(self): 152 r""" 153 int, 14 (default) | 12, read-only : Number of OFDM symbols per slot 154 :math:`N_\text{symb}^\text{slot}` 155 156 Configured through the `cyclic_prefix`. 157 """ 158 if self.cyclic_prefix=="normal": 159 return 14 160 else: 161 return 12 162 163 @property 164 def num_slots_per_subframe(self): 165 r""" 166 int, 1 (default) | 2 | 4 | 8 | 16 | 32 | 64, read-only : Number of 167 slots per subframe :math:`N_\text{slot}^{\text{subframe},\mu}` 168 169 Depends on the `subcarrier_spacing`. 170 """ 171 if self.subcarrier_spacing==15: 172 return 1 173 elif self.subcarrier_spacing==30: 174 return 2 175 elif self.subcarrier_spacing==60: 176 return 4 177 elif self.subcarrier_spacing==120: 178 return 8 179 elif self.subcarrier_spacing==240: 180 return 16 181 elif self.subcarrier_spacing==480: 182 return 32 183 elif self.subcarrier_spacing==960: 184 return 64 185 186 @property 187 def num_slots_per_frame(self): 188 r""" 189 int, 10 (default) | 20 | 40 | 80 | 160 | 320 | 640, read-only : Number 190 of slots per frame :math:`N_\text{slot}^{\text{frame},\mu}` 191 192 Depends on the `subcarrier_spacing`. 193 """ 194 return 10*self.num_slots_per_subframe 195 196 @property 197 def mu(self): 198 r""" 199 int, 0 (default) | 1 | 2 | 3 | 4 | 5 | 6, read-only : Subcarrier 200 spacing configuration, :math:`\Delta f = 2^\mu 15` kHz 201 """ 202 return [15, 30, 60, 120, 240, 480, 960].index(self.subcarrier_spacing) 203 204 @property 205 def frame_duration(self): 206 r""" 207 float, 10e-3 (default), read-only : Duration of a frame 208 :math:`T_\text{f}` [s] 209 """ 210 return 10e-3 211 212 @property 213 def sub_frame_duration(self): 214 r""" 215 float, 1e-3 (default), read-only : Duration of a subframe 216 :math:`T_\text{sf}` [s] 217 """ 218 return 1e-3 219 220 @property 221 def t_c(self): 222 r""" 223 float, 0.509e-9 [s], read-only : Sampling time :math:`T_\text{c}` for 224 subcarrier spacing 480kHz. 225 """ 226 return 1/(480e3*4096) 227 228 @property 229 def t_s(self): 230 r""" 231 float, 32.552e-9 [s], read-only : Sampling time :math:`T_\text{s}` for 232 subcarrier spacing 15kHz. 233 """ 234 return 1/(15e3*2048) 235 236 @property 237 def kappa(self): 238 r""" 239 float, 64, read-only : The constant 240 :math:`\kappa = T_\text{s}/T_\text{c}` 241 """ 242 return 64. 243 244 @property 245 def cyclic_prefix_length(self): 246 r""" 247 float, read-only : Cyclic prefix length 248 :math:`N_{\text{CP},l}^{\mu} \cdot T_{\text{c}}` [s] 249 """ 250 if self.cyclic_prefix=="extended": 251 cp = 512*self.kappa*2**(-self.mu) 252 else: 253 cp = 144*self.kappa*2**(-self.mu) 254 if self.slot_number in [0, 7*2**self.mu]: 255 cp += 16*self.kappa 256 return cp*self.t_c 257 258 #-------------------# 259 #---Class methods---# 260 #-------------------# 261 262 def check_config(self): 263 """Test if configuration is valid""" 264 265 if self.cyclic_prefix=="extended": 266 assert self.subcarrier_spacing==60, \ 267 "Extended cyclic prefix only valid for 60kHz subcarrier spacing" 268 269 attr_list = ["n_cell_id", 270 "cyclic_prefix", 271 "subcarrier_spacing", 272 "n_size_grid", 273 "slot_number", 274 "frame_number" 275 ] 276 for attr in attr_list: 277 value = getattr(self, attr) 278 setattr(self, attr, value)