anomaly-detection-material-parameters-calibration

Sionna param calibration (research proj)
git clone https://git.ea.contact/anomaly-detection-material-parameters-calibration
Log | Files | Refs | README

pusch_config.py (37683B)


      1 #
      2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      3 # SPDX-License-Identifier: Apache-2.0
      4 #
      5 """PUSCH configuration for the nr (5G) sub-package of the Sionna library.
      6 """
      7 # pylint: disable=line-too-long
      8 
      9 import numpy as np
     10 from .utils import generate_prng_seq
     11 from .config import Config
     12 from sionna import nr
     13 from .utils import calculate_tb_size
     14 
     15 class PUSCHConfig(Config):
     16     """
     17     The PUSCHConfig objects sets parameters for a physical uplink shared
     18     channel (PUSCH), as described in Sections 6.3 and 6.4 [3GPP38211]_.
     19 
     20     All configurable properties can be provided as keyword arguments during the
     21     initialization or changed later.
     22 
     23     Parameters
     24     ----------
     25     carrier_config : :class:`~sionna.nr.CarrierConfig` or `None`
     26         An instance of :class:`~sionna.nr.CarrierConfig`. If `None`, a
     27         :class:`~sionna.nr.CarrierConfig` instance with default settings
     28         will be created.
     29 
     30     pusch_dmrs_config : :class:`~sionna.nr.PUSCHDMRSConfig` or `None`
     31         An instance of :class:`~sionna.nr.PUSCHDMRSConfig`. If `None`, a
     32         :class:`~sionna.nr.PUSCHDMRSConfig` instance with default settings
     33         will be created.
     34 
     35     Example
     36     -------
     37     >>> pusch_config = PUSCHConfig(mapping_type="B")
     38     >>> pusch_config.dmrs.config_type = 2
     39     >>> pusch_config.carrier.subcarrier_spacing = 30
     40     """
     41     def __init__(self,
     42                  carrier_config=None,
     43                  pusch_dmrs_config=None,
     44                  tb_config=None,
     45                  **kwargs):
     46         super().__init__(**kwargs)
     47         self._name = "PUSCH Configuration"
     48         self.carrier = carrier_config
     49         self.dmrs = pusch_dmrs_config
     50         self.tb = tb_config
     51         self.check_config()
     52 
     53     #-----------------------------#
     54     #---Configurable parameters---#
     55     #-----------------------------#
     56 
     57     #---carrier---#
     58     @property
     59     def carrier(self):
     60         """
     61         :class:`~sionna.nr.CarrierConfig` : Carrier configuration
     62         """
     63         return self._carrier
     64 
     65     @carrier.setter
     66     def carrier(self, value):
     67         if value is None:
     68             value = nr.CarrierConfig()
     69         else:
     70             assert isinstance(value, nr.CarrierConfig), \
     71             "carrier must be an instance of CarrierConfig"
     72         self._carrier = value
     73 
     74     #---dmrs---#
     75     @property
     76     def dmrs(self):
     77         """
     78         :class:`~sionna.nr.PUSCHDMRSConfig` : PUSCH DMRS configuration
     79         """
     80         return self._dmrs
     81 
     82     @dmrs.setter
     83     def dmrs(self, value):
     84         if value is None:
     85             value = nr.PUSCHDMRSConfig()
     86         else:
     87             assert isinstance(value, nr.PUSCHDMRSConfig), \
     88             "pusch_dmrs_config must be an instance of PUSCHDMRSConfig"
     89         self._dmrs = value
     90 
     91     #---transport block---#
     92     @property
     93     def tb(self):
     94         """
     95         :class:`~sionna.nr.TBConfig` : Transport block configuration
     96         """
     97         return self._tb
     98 
     99     @tb.setter
    100     def tb(self, value):
    101         if value is None:
    102             value = nr.TBConfig(channel_type="PUSCH")
    103         else:
    104             assert isinstance(value, nr.TBConfig), \
    105             "tb must be an instance of TBConfig"
    106             assert value.channel_type=="PUSCH",\
    107                     'TBConfig must be configured for "PUSCH"'
    108         self._tb = value
    109 
    110     #---n_size_bwp---#
    111     @property
    112     def n_size_bwp(self):
    113         r"""
    114         int, None (default), [1,...,275] : Number of resource blocks in the
    115             bandwidth part (BWP) :math:`N^{\text{size},\mu}_{\text{BWP},i}`
    116 
    117             If set to `None`, the property
    118             :class:`~sionna.nr.CarrierConfig.n_size_grid` of
    119             `carrier` will be used.
    120         """
    121         self._ifndef("n_size_bwp", None)
    122         return self._n_size_bwp
    123 
    124     @n_size_bwp.setter
    125     def n_size_bwp(self, value):
    126         if value is not None:
    127             assert value in range(1,276),\
    128                 "n_size_bwp must be in the range from 1 to 275"
    129         self._n_size_bwp = value
    130 
    131     #---n_start_bwp---#
    132     @property
    133     def n_start_bwp(self):
    134         r"""
    135         int, 0 (default) | [0,...,2199] : Start of BWP relative to
    136             common resource block (CRB) 0
    137             :math:`N^{\text{start},\mu}_{\text{BWP},i}`
    138         """
    139         self._ifndef("n_start_bwp", 0)
    140         return self._n_start_bwp
    141 
    142     @n_start_bwp.setter
    143     def n_start_bwp(self, value):
    144         assert value in range(0,2474), \
    145             "n_start_bwp must be in the range from 0 to 2473"
    146         self._n_start_bwp = value
    147 
    148 
    149     #---num-layers---#
    150     @property
    151     def num_layers(self):
    152         r"""
    153         int, 1 (default) | 2 | 3 | 4: Number of transmission layers
    154             :math:`\nu`
    155 
    156             Must be smaller than or equal to
    157             :class:`~sionna.nr.PUSCHConfig.num_antenna_ports`.
    158         """
    159         self._ifndef("num_layers", 1)
    160         return self._num_layers
    161 
    162     @num_layers.setter
    163     def num_layers(self, value):
    164         assert value in [1,2,3,4], "num_layers must be in [1,...,4]"
    165         self._num_layers = value
    166 
    167     #---num-antenna-ports---#
    168     @property
    169     def num_antenna_ports(self):
    170         """
    171         int, 1 (default) | 2 | 4: Number of antenna ports
    172 
    173             Must be larger than or equal to
    174             :class:`~sionna.nr.PUSCHConfig.num_layers`.
    175         """
    176         self._ifndef("num_antenna_ports", 1)
    177         return self._num_antenna_ports
    178 
    179     @num_antenna_ports.setter
    180     def num_antenna_ports(self, value):
    181         assert value in [1,2,4], "num_antenna_ports must be in [1,2,4]"
    182         self._num_antenna_ports = value
    183 
    184     #---mapping_type---#
    185     @property
    186     def mapping_type(self):
    187         """
    188         string, "A" (default) | "B": Mapping type
    189         """
    190         self._ifndef("mapping_type", "A")
    191         return self._mapping_type
    192 
    193     @mapping_type.setter
    194     def mapping_type(self, value):
    195         assert value in ["A","B"], "mapping_type must be A or B"
    196         self._mapping_type = value
    197 
    198     #---symbol_allocation---#
    199     @property
    200     def symbol_allocation(self):
    201         """
    202         2-tuple, int, [0, 14] (default) : PUSCH symbol allocation
    203 
    204             The first elements denotes the start of the symbol allocation.
    205             The second denotes the positive number of allocated OFDM symbols.
    206             For `mapping_type` "A", the first element must be zero.
    207             For `mapping_type` "B", the first element must be in
    208             [0,...,13]. The second element must be such that the index
    209             of the last allocated OFDM symbol is not larger than 13
    210             (for "normal" cyclic prefix) or 11 (for "extended" cyclic prefix).
    211         """
    212         self._ifndef("symbol_allocation", [0, 14])
    213         return self._symbol_allocation
    214 
    215     @symbol_allocation.setter
    216     def symbol_allocation(self, value):
    217         assert len(value)==2, "symbol_allocation must have two elements"
    218         self._symbol_allocation = value
    219 
    220     #---n_rnti---#
    221     @property
    222     def n_rnti(self):
    223         r"""
    224         int, 1 (default), [0,...,65535] : Radio network temporary identifier
    225             :math:`n_\text{RNTI}`
    226         """
    227         self._ifndef("n_rnti", 1)
    228         return self._n_rnti
    229 
    230     @n_rnti.setter
    231     def n_rnti(self, value):
    232         if value is not None:
    233             assert value in range(65536), "n_rnti must be in [0, 65535]"
    234         self._n_rnti = value
    235 
    236     #---transform_precoding---#
    237     @property
    238     def precoding(self):
    239         """
    240         str, "non-codebook" (default), "codebook" : PUSCH
    241             transmission scheme
    242         """
    243         self._ifndef("precoding", "non-codebook")
    244         return self._precoding
    245 
    246     @precoding.setter
    247     def precoding(self, value):
    248         assert value in ["codebook", "non-codebook"], \
    249             "Unknown value for precoding"
    250         self._precoding = value
    251 
    252     #---transform_precoding---#
    253     @property
    254     def transform_precoding(self):
    255         """
    256         bool, False (default): Use transform precoding
    257         """
    258         self._ifndef("transform_precoding", False)
    259         return self._transform_precoding
    260 
    261     @transform_precoding.setter
    262     def transform_precoding(self, value):
    263         assert isinstance(value, bool), \
    264             """transform_precoding must be bool"""
    265         self._transform_precoding = value
    266 
    267     #---tpmi---#
    268     @property
    269     def tpmi(self):
    270         r"""
    271         int,  0 (default) | [0,...,27] : Transmit precoding matrix indicator
    272 
    273             The allowed value depends on the number of layers and
    274             the number of antenna ports according to Table 6.3.1.5-1
    275             until Table 6.3.1.5-7 [3GPP38211]_.
    276         """
    277         self._ifndef("tpmi", 0)
    278         return self._tpmi
    279 
    280     @tpmi.setter
    281     def tpmi(self, value):
    282         assert value in range(28), "tpmi must be in [0,...,27]"
    283         self._tpmi = value
    284 
    285     #-----------------------------#
    286     #---Read-only parameters------#
    287     #-----------------------------#
    288 
    289     @property
    290     def frequency_hopping(self):
    291         """
    292         str, "neither" (default), read-only : Frequency hopping configuration
    293         """
    294         return "neither"
    295 
    296     @property
    297     def l_0(self):
    298         r"""
    299         int, read-only : Position of the first DMRS symbol :math:`l_0`
    300             relative to the reference `l_ref`.
    301         """
    302         if self.mapping_type=="A":
    303             return self.dmrs.type_a_position
    304         elif self.mapping_type=="B":
    305             return 0
    306 
    307     @property
    308     def l_d(self):
    309         r"""
    310         int, read-only : Length of the symbol allocation :math:`l_\text{d}`
    311         """
    312         return self.symbol_allocation[1]
    313 
    314     @property
    315     def l_ref(self):
    316         r"""
    317         int, read-only: Reference OFDM symbol index  used for DMRS
    318             generation
    319         """
    320         if self.mapping_type=="A":
    321             return 0
    322         elif self. mapping_type=="B":
    323             return self.symbol_allocation[0]
    324 
    325     @property
    326     def l_prime(self):
    327         r"""
    328         list, elements in [0,1], read-only : List of possible values of
    329             :math:`l'` used for DMRS generation
    330         """
    331         if self.dmrs.length==1:
    332             return [0]
    333         elif self.dmrs.length==2:
    334             return [0, 1]
    335 
    336     @property
    337     def l_bar(self):
    338         r"""
    339         list, elements in [0,...,11], read-only : List of possible values of
    340             :math:`\bar{l}` used for DMRS generation
    341 
    342             Defined in Tables 6.4.1.1.3-3 and 6.4.1.1.3-4 [3GPP38211]_.
    343         """
    344         l_0 = self.l_0
    345         ind = 0 if self.l_d<4 else self.l_d-3
    346         if self.mapping_type=="A":
    347             if self.dmrs.length==1:
    348                 l_bar = [
    349                    [[],    [],        [],           []],
    350                    [[l_0], [l_0],     [l_0],        [l_0]],
    351                    [[l_0], [l_0],     [l_0],        [l_0]],
    352                    [[l_0], [l_0],     [l_0],        [l_0]],
    353                    [[l_0], [l_0],     [l_0],        [l_0]],
    354                    [[l_0], [l_0, 7],  [l_0, 7],     [l_0, 7]],
    355                    [[l_0], [l_0, 7],  [l_0, 7],     [l_0, 7]],
    356                    [[l_0], [l_0, 9],  [l_0, 6, 9],  [l_0, 6, 9]],
    357                    [[l_0], [l_0, 9],  [l_0, 6, 9],  [l_0, 6, 9]],
    358                    [[l_0], [l_0, 9],  [l_0, 6, 9],  [l_0, 5, 8, 11]],
    359                    [[l_0], [l_0, 11], [l_0, 7, 11], [l_0, 5, 8, 11]],
    360                    [[l_0], [l_0, 11], [l_0, 7, 11], [l_0, 5, 8, 11]]
    361                 ]
    362             else:
    363                 l_bar = [
    364                    [[],    []],
    365                    [[l_0], [l_0]],
    366                    [[l_0], [l_0]],
    367                    [[l_0], [l_0]],
    368                    [[l_0], [l_0]],
    369                    [[l_0], [l_0]],
    370                    [[l_0], [l_0]],
    371                    [[l_0], [l_0, 8]],
    372                    [[l_0], [l_0, 8]],
    373                    [[l_0], [l_0, 8]],
    374                    [[l_0], [l_0, 10]],
    375                    [[l_0], [l_0, 10]],
    376                 ]
    377         else: # mapping_type == "B"
    378             if self.dmrs.length==1:
    379                 l_bar = [
    380                    [[l_0], [l_0],     [l_0],        [l_0]],
    381                    [[l_0], [l_0],     [l_0],        [l_0]],
    382                    [[l_0], [l_0, 4],  [l_0, 4],     [l_0, 4]],
    383                    [[l_0], [l_0, 4],  [l_0, 4],     [l_0, 4]],
    384                    [[l_0], [l_0, 4],  [l_0, 4],     [l_0, 4]],
    385                    [[l_0], [l_0, 6],  [l_0, 3, 6],  [l_0, 3, 6]],
    386                    [[l_0], [l_0, 6],  [l_0, 3, 6],  [l_0, 3, 6]],
    387                    [[l_0], [l_0, 8],  [l_0, 4, 8],  [l_0, 3, 6, 9]],
    388                    [[l_0], [l_0, 8],  [l_0, 4, 8],  [l_0, 3, 6, 9]],
    389                    [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]],
    390                    [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]],
    391                    [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]]
    392                 ]
    393             else:
    394                 l_bar = [
    395                    [[],    []],
    396                    [[],    []],
    397                    [[l_0], [l_0]],
    398                    [[l_0], [l_0]],
    399                    [[l_0], [l_0]],
    400                    [[l_0], [l_0, 5]],
    401                    [[l_0], [l_0, 5]],
    402                    [[l_0], [l_0, 7]],
    403                    [[l_0], [l_0, 7]],
    404                    [[l_0], [l_0, 9]],
    405                    [[l_0], [l_0, 9]],
    406                    [[l_0], [l_0, 9]],
    407                 ]
    408 
    409         return l_bar[ind][self.dmrs.additional_position]
    410 
    411     @property
    412     def l(self):
    413         r"""
    414         list, int, read-only : List of possible values of the OFDM symbol
    415             indices :math:`l` carrying DMRS relative to :math:`l_0`
    416         """
    417         l = []
    418         for l_bar in self.l_bar:
    419             for l_prime in self.l_prime:
    420                 l.append(l_bar + l_prime)
    421         return l
    422 
    423     @property
    424     def n(self):
    425         """
    426         list, int, read-only: List of possible values of n
    427             used for DMRS generation
    428         """
    429         if self.dmrs.config_type==1:
    430             n_max = self.num_resource_blocks*12//4 -1
    431         else: # config_type == 2
    432             n_max = self.num_resource_blocks*12//6 -1
    433         return list(range(n_max+1))
    434 
    435     @property
    436     def dmrs_symbol_indices(self):
    437         """
    438         list, int, read-only: Indices of DMRS symbols within a slot
    439         """
    440         return [l + self.l_ref for l in self.l]
    441 
    442     @property
    443     def num_resource_blocks(self):
    444         """
    445         int, read-only : Number of allocated resource blocks for the
    446             PUSCH transmissions.
    447         """
    448         if self.n_size_bwp is None:
    449             return self.carrier.n_size_grid
    450         else:
    451             return self.n_size_bwp
    452 
    453     @property
    454     def num_subcarriers(self):
    455         """
    456         int, read-only : Number of allocated subcarriers for the
    457             PUSCH transmissions
    458         """
    459         return 12*self.num_resource_blocks
    460 
    461     @property
    462     def num_res_per_prb(self):
    463         """
    464         int, read-only : Number of resource elements per PRB
    465             available for data
    466         """
    467         # Number of DMRS symbols
    468         num_dmrs = len(self.dmrs_symbol_indices)
    469 
    470         # Number of non-DMRS symbols
    471         num_data = self.symbol_allocation[1] - num_dmrs
    472 
    473         # Number of REs on DMRS symbols
    474         if self.dmrs.config_type==1:
    475             num_res_dmrs = 12 - 6*self.dmrs.num_cdm_groups_without_data
    476         else: # dmrs.config_type == 2
    477             num_res_dmrs = 12 - 4*self.dmrs.num_cdm_groups_without_data
    478 
    479         # Number of REs on data symbols
    480         num_res_data = 12
    481 
    482         return num_data*num_res_data + num_dmrs*num_res_dmrs
    483 
    484     @property
    485     def dmrs_mask(self):
    486         """
    487         bool, [num_subcarriers, num_symbols_per_slot], read-only : Masked
    488             resource elements in the resource grid. `True` corresponds to
    489             resource elements on which no data is transmitted.
    490         """
    491         mask = np.zeros([self.num_subcarriers,
    492                          self.carrier.num_symbols_per_slot],
    493                          dtype=bool)
    494 
    495         num_cdm_groups = self.dmrs.num_cdm_groups_without_data
    496         if self.dmrs.config_type==1:
    497             cdm_ind = np.zeros([6, num_cdm_groups], np.int32)
    498             for i in range(num_cdm_groups):
    499                 cdm_ind[:,i] = np.arange(i, 12, 2)
    500         else:
    501             cdm_ind = np.zeros([4, num_cdm_groups], np.int32)
    502             for i in range(num_cdm_groups):
    503                 cdm_ind[:,i] = np.array([0,1, 6, 7])+2*i
    504 
    505         for i in self.dmrs_symbol_indices:
    506             for j in range(self.num_resource_blocks):
    507                 for k in range(num_cdm_groups):
    508                     mask[cdm_ind[:, k] + 12*j, i] = True
    509         return mask
    510 
    511     @property
    512     def dmrs_grid(self):
    513         # pylint: disable=line-too-long
    514         """
    515         complex, [num_dmrs_ports, num_subcarriers, num_symbols_per_slot], read-only : Empty
    516             resource grid for each DMRS port, filled with DMRS signals
    517 
    518             This property returns for each configured DMRS port an empty
    519             resource grid filled with DMRS signals as defined in
    520             Section 6.4.1.1 [3GPP38211]. Not all possible options are implemented,
    521             e.g., frequency hopping and transform precoding are not available.
    522 
    523             This property provides the *unprecoded* DMRS for each configured DMRS port.
    524             Precoding might be applied to map the DMRS to the antenna ports. However,
    525             in this case, the number of DMRS ports cannot be larger than the number of
    526             layers.
    527         """
    528         # Check configuration
    529         self.check_config()
    530 
    531         # Configure DMRS ports set if it has not been set
    532         reset_dmrs_port_set = False
    533         if len(self.dmrs.dmrs_port_set)==0:
    534             self.dmrs.dmrs_port_set = list(range(self.num_layers))
    535             reset_dmrs_port_set = True
    536 
    537         # Generate empty resource grid for each port
    538         a_tilde = np.zeros([len(self.dmrs.dmrs_port_set),
    539                             self.num_subcarriers,
    540                             self.carrier.num_symbols_per_slot],
    541                             dtype=complex)
    542 
    543         # For every l_bar
    544         for l_bar in self.l_bar:
    545 
    546             # For every l_prime
    547             for l_prime in self.l_prime:
    548 
    549                 # Compute c_init
    550                 l = l_bar + l_prime
    551                 c_init = self.c_init(l)
    552 
    553                 # Generate RNG
    554                 c = generate_prng_seq(2*self.num_subcarriers, c_init=c_init)
    555 
    556                 # Map to QAM
    557                 r = 1/np.sqrt(2)*((1-2*c[::2]) + 1j*(1-2*c[1::2]))
    558 
    559                 # For every port in the dmrs port set
    560                 for j_ind, _ in enumerate(self.dmrs.dmrs_port_set):
    561 
    562                     # For every n
    563                     for n in self.n:
    564 
    565                         # For every k_prime
    566                         for k_prime in [0, 1]:
    567 
    568                             if self.dmrs.config_type==1:
    569                                 k = 4*n + 2*k_prime + \
    570                                     self.dmrs.deltas[j_ind]
    571                             else: # config_type == 2
    572                                 k = 6*n + k_prime + \
    573                                     self.dmrs.deltas[j_ind]
    574 
    575                             a_tilde[j_ind, k, self.l_ref+l] = \
    576                                 r[2*n + k_prime] * \
    577                                 self.dmrs.w_f[k_prime][j_ind] * \
    578                                 self.dmrs.w_t[l_prime][j_ind]
    579 
    580         # Amplitude scaling
    581         a = self.dmrs.beta*a_tilde
    582 
    583         # Reset DMRS port set if it was not set
    584         if reset_dmrs_port_set:
    585             self.dmrs.dmrs_port_set = []
    586 
    587         return a
    588 
    589     @property
    590     def dmrs_grid_precoded(self):
    591         if self.precoding=="non-codebook":
    592             return None
    593 
    594         w = np.expand_dims(np.expand_dims(self.precoding_matrix, 0), 0)
    595         a = np.expand_dims(np.transpose(self.dmrs_grid, [1,2,0]),-1)
    596         a = np.squeeze(np.matmul(w, a), -1)
    597         a = np.transpose(a, [2, 0, 1])
    598 
    599         return a
    600 
    601     @property
    602     def precoding_matrix(self):
    603         r"""
    604         nd_array, complex, [num_antenna_ports, numLayers] : Precoding matrix
    605             :math:`\mathbf{W}` as defined in
    606             Tables 6.3.1.5-1 to 6.3.1.5-7 [3GPP38211]_.
    607 
    608             Only relevant if :class:`~sionna.nr.PUSCHCONFIG.precoding`
    609             is "codebook".
    610         """
    611         if self.precoding=="non-codebook":
    612             return None
    613         if self.num_antenna_ports==1:
    614             return None
    615         w = None
    616         if self.num_layers==1:
    617 
    618             # Table 6.3.1.5-1
    619             if self.num_antenna_ports==2:
    620                 w = np.zeros([6,2,1], complex)
    621 
    622                 #TPMI index 0-5
    623                 w[:,0,0] = [1,  0,  1,  1,  1,  1]
    624                 w[:,1,0] = [0,  1,  1, -1, 1j,-1j]
    625 
    626                 w /= np.sqrt(2)
    627 
    628             # Table 6.3.1.5-3
    629             elif self.num_antenna_ports==4:
    630                 w = np.zeros([28,4,1], complex)
    631 
    632                 # TPMI index 0-7
    633                 w[:8,0,0] = [  1,  0,  0,  0,  1,  1,  1,  1]
    634                 w[:8,1,0] = [  0,  1,  0,  0,  0,  0,  0,  0]
    635                 w[:8,2,0] = [  0,  0,  1,  0,  1, -1, 1j,-1j]
    636                 w[:8,3,0] = [  0,  0,  0,  1,  0,  0,  0,  0]
    637 
    638                 # TPMI index 8-15
    639                 w[8:16,0,0] = [  0,  0,  0,  0,  1,  1,  1,  1]
    640                 w[8:16,1,0] = [  1,  1,  1,  1,  1,  1,  1,  1]
    641                 w[8:16,2,0] = [  0,  0,  0,  0,  1, 1j, -1,-1j]
    642                 w[8:16,3,0] = [  1, -1, 1j,-1j,  1, 1j, -1,-1j]
    643 
    644                 # TPMI index 16-23
    645                 w[16:24,0,0] = [  1,  1,  1,  1,  1,  1,  1,  1]
    646                 w[16:24,1,0] = [ 1j, 1j, 1j, 1j, -1, -1, -1, -1]
    647                 w[16:24,2,0] = [  1, 1j, -1,-1j,  1, 1j, -1,-1j]
    648                 w[16:24,3,0] = [ 1j, -1,-1j,  1, -1,-1j,  1, 1j]
    649 
    650                 # TPMI index 24-27
    651                 w[24:28,0,0] = [  1,  1,  1,  1]
    652                 w[24:28,1,0] = [-1j,-1j,-1j,-1j]
    653                 w[24:28,2,0] = [  1, 1j, -1,-1j]
    654                 w[24:28,3,0] = [-1j,  1, 1j, -1]
    655 
    656                 w /= 2
    657 
    658         elif self.num_layers==2:
    659 
    660             # Table 6.3.1.5-4
    661             if self.num_antenna_ports==2:
    662                 w = np.zeros([3,2,2], complex)
    663 
    664                 # TPMI index 0-2
    665                 w[0] = [[  1,  0], [  0,  1]]
    666                 w[0] /= np.sqrt(2)
    667                 w[1] = [[  1,  1], [  1, -1]]
    668                 w[1] /= 2
    669                 w[2] = [[  1,  1], [ 1j,-1j]]
    670                 w[2] /= 2
    671 
    672             # Table 6.3.1.5-5
    673             elif self.num_antenna_ports==4:
    674                 w = np.zeros([22,4,2], complex)
    675 
    676                 # TPMI index 0-21
    677                 w[0] = [[  1,  0], [  0,  1], [  0,  0], [  0,  0]]
    678                 w[0] /= 2
    679                 w[1] = [[  1,  0], [  0,  0], [  0,  1], [  0,  0]]
    680                 w[1] /= 2
    681                 w[2] = [[  1,  0], [  0,  0], [  0,  0], [  0,  1]]
    682                 w[2] /= 2
    683                 w[3] = [[  0,  0], [  1,  0], [  0,  1], [  0,  0]]
    684                 w[3] /= 2
    685                 w[4] = [[  0,  0], [  1,  0], [  0,  0], [  0,  1]]
    686                 w[4] /= 2
    687                 w[5] = [[  0,  0], [  0,  0], [  1,  0], [  0,  1]]
    688                 w[5] /= 2
    689                 w[6] = [[  1,  0], [  0,  1], [  1,  0], [  0,-1j]]
    690                 w[6] /= 2
    691                 w[7] = [[  1,  0], [  0,  1], [  1,  0], [  0, 1j]]
    692                 w[7] /= 2
    693                 w[8] = [[  1,  0], [  0,  1], [-1j,  0], [  0,  1]]
    694                 w[8] /= 2
    695                 w[9] = [[  1,  0], [  0,  1], [-1j,  0], [  0, -1]]
    696                 w[9] /= 2
    697                 w[10] = [[  1,  0], [  0,  1], [ -1,  0], [  0,-1j]]
    698                 w[10] /= 2
    699                 w[11] = [[  1,  0], [  0,  1], [ -1,  0], [  0, 1j]]
    700                 w[11] /= 2
    701                 w[12] = [[  1,  0], [  0,  1], [ 1j,  0], [  0,  1]]
    702                 w[12] /= 2
    703                 w[13] = [[  1,  0], [  0,  1], [ 1j,  0], [  0, -1]]
    704                 w[13] /= 2
    705                 w[14] = [[  1,  1], [  1,  1], [  1, -1], [  1, -1]]
    706                 w[14] /= 2*np.sqrt(2)
    707                 w[15] = [[  1,  1], [  1,  1], [ 1j,-1j], [ 1j,-1j]]
    708                 w[15] /= 2*np.sqrt(2)
    709                 w[16] = [[  1,  1], [ 1j, 1j], [  1, -1], [ 1j,-1j]]
    710                 w[16] /= 2*np.sqrt(2)
    711                 w[17] = [[  1,  1], [ 1j, 1j], [ 1j,-1j], [ -1,  1]]
    712                 w[17] /= 2*np.sqrt(2)
    713                 w[18] = [[  1,  1], [ -1, -1], [  1, -1], [ -1,  1]]
    714                 w[18] /= 2*np.sqrt(2)
    715                 w[19] = [[  1,  1], [ -1, -1], [ 1j,-1j], [-1j, 1j]]
    716                 w[19] /= 2*np.sqrt(2)
    717                 w[20] = [[  1,  1], [-1j,-1j], [  1, -1], [-1j, 1j]]
    718                 w[20] /= 2*np.sqrt(2)
    719                 w[21] = [[  1,  1], [-1j,-1j], [1j,-1j], [  1, -1]]
    720                 w[21] /= 2*np.sqrt(2)
    721 
    722         elif self.num_layers==3:
    723 
    724             # Table 6.3.1.5-6
    725             if self.num_antenna_ports==4:
    726                 w = np.zeros([7,4,3], complex)
    727 
    728                 #TPMI index 0-6
    729                 w[0] = [[  1,  0,  0],
    730                         [  0,  1,  0],
    731                         [  0,  0,  1],
    732                         [  0,  0,  0]]
    733                 w[0] /= 2
    734 
    735                 w[1] = [[  1,  0,  0],
    736                         [  0,  1,  0],
    737                         [  1,  0,  0],
    738                         [  0,  0,  1]]
    739                 w[1] /= 2
    740 
    741                 w[2] = [[  1,  0,  0],
    742                         [  0,  1,  0],
    743                         [ -1,  0,  0],
    744                         [  0,  0,  1]]
    745                 w[2] /= 2
    746 
    747                 w[3] = [[  1,  1,  1],
    748                         [  1, -1,  1],
    749                         [  1,  1, -1],
    750                         [  1, -1, -1]]
    751                 w[3] /= (2*np.sqrt(3))
    752 
    753                 w[4] = [[  1,  1,  1],
    754                         [  1, -1,  1],
    755                         [ 1j, 1j,-1j],
    756                         [ 1j,-1j,-1j]]
    757                 w[4] /= (2*np.sqrt(3))
    758 
    759                 w[5] = [[  1,  1,  1],
    760                         [ -1,  1, -1],
    761                         [  1,  1, -1],
    762                         [ -1,  1,  1]]
    763                 w[5] /= (2*np.sqrt(3))
    764 
    765                 w[6] = [[  1,  1,  1],
    766                         [ -1,  1, -1],
    767                         [ 1j, 1j,-1j],
    768                         [-1j, 1j, 1j]]
    769                 w[6] /= (2*np.sqrt(3))
    770 
    771         elif self.num_layers==4:
    772 
    773             # Table 6.3.1.5-7
    774             if self.num_antenna_ports==4:
    775                 w = np.zeros([5,4,4], complex)
    776 
    777                 # TPMI index 0-4
    778                 w[0] = [[  1,  0,  0,  0],
    779                         [  0,  1,  0,  0],
    780                         [  0,  0,  1,  0],
    781                         [  0,  0,  0,  1]]
    782                 w[0] /= 2
    783 
    784                 w[1] = [[  1,  1,  0,  0],
    785                         [  0,  0,  1,  1],
    786                         [  1, -1,  0,  0],
    787                         [  0,  0,  1, -1]]
    788                 w[1] /= 2*np.sqrt(2)
    789 
    790                 w[2] = [[  1,  1,  0,  0],
    791                         [  0,  0,  1,  1],
    792                         [ 1j,-1j,  0,  0],
    793                         [  0,  0, 1j,-1j]]
    794                 w[2] /= 2*np.sqrt(2)
    795 
    796                 w[3] = [[  1,  1,  1,  1],
    797                         [  1, -1,  1, -1],
    798                         [  1,  1, -1, -1],
    799                         [  1, -1, -1,  1]]
    800                 w[3] /= 4
    801 
    802                 w[4] = [[  1,  1,  1,  1],
    803                         [  1, -1,  1, -1],
    804                         [ 1j, 1j,-1j,-1j],
    805                         [ 1j,-1j,-1j, 1j]]
    806                 w[4] /= 4
    807 
    808         if w is None:
    809             return w
    810         else:
    811             return w[self.tpmi]
    812 
    813     @property
    814     def num_ov(self):
    815         r"""
    816         int, 0 (default), read-only:  Number of unused resource elements due to additional overhead as specified by higher layer."""
    817         return 0
    818 
    819     @property
    820     def num_coded_bits(self):
    821         r"""
    822         int, read-only: Number of coded bits that fit into one PUSCH slot."""
    823 
    824         # compute number of data symbols
    825         n_re_per_prb = self.num_res_per_prb - self.num_ov
    826 
    827         # number of allocated REs
    828         n_re = n_re_per_prb * self.num_resource_blocks
    829 
    830         # total number of bits per slot
    831         num_coded_bits = int(self.tb.tb_scaling * self.tb.num_bits_per_symbol \
    832                              * self.num_layers * n_re)
    833 
    834         return num_coded_bits
    835 
    836     @property
    837     def tb_size(self):
    838         r"""int, read-only: Transport block size, i.e., how many information bits can be encoded into a slot for the given slot configuration."""
    839 
    840         # compute number of data symbols per prb
    841         n_re_per_prb = self.num_res_per_prb - self.num_ov
    842 
    843         # number of allocated REs
    844         # the max. number of REs per PRB is limited to 156 in 38.214
    845         n_re = min(156, n_re_per_prb) * self.num_resource_blocks
    846 
    847         # include tb_scaling as defined in Tab. 5.1.3.2-2 38.214
    848         target_tb_size = int(self.tb.target_coderate * self.tb.tb_scaling \
    849                         * n_re * self.tb.num_bits_per_symbol * self.num_layers)
    850 
    851         # and run tb_size calculation to account for quantization
    852         tb_size, _, _, _, _,_ = calculate_tb_size(
    853                             target_tb_size=target_tb_size,
    854                             num_coded_bits=self.num_coded_bits,
    855                             target_coderate=self.tb.target_coderate,
    856                             modulation_order=self.tb.num_bits_per_symbol,
    857                             verbose=False)
    858 
    859         return tb_size
    860 
    861     #-------------------#
    862     #---Class methods---#
    863     #-------------------#
    864 
    865     def c_init(self, l):
    866         # pylint: disable=line-too-long
    867         r"""Compute RNG initialization :math:`c_\text{init}` as in Section 6.4.1.1.1.1 [3GPP38211]_
    868 
    869         Input
    870         -----
    871             l : int
    872                 OFDM symbol index relative to a reference :math:`l`
    873 
    874         Output
    875         ------
    876             c_init : int
    877                 RNG initialization value
    878         """
    879         num_symbols_per_slot = self.carrier.num_symbols_per_slot
    880         slot_number = self.carrier.slot_number
    881 
    882         lambda_bar = 0
    883         n_scid_bar = self.dmrs.n_scid
    884         if self.dmrs.n_id is None:
    885             n_id = self.carrier.n_cell_id
    886         else:
    887             n_id = self.dmrs.n_id[n_scid_bar]
    888 
    889         c_init = np.mod(2**17 * (num_symbols_per_slot * slot_number + l + 1) \
    890                               * (2*n_id + 1) \
    891                         + 2**17 * np.floor(lambda_bar/2) \
    892                         + 2*n_id + n_scid_bar
    893                         , 2**31)
    894 
    895         return int(c_init)
    896 
    897     def show(self):
    898         """Print all properties of the PUSCHConfig and children"""
    899         self.carrier.show()
    900         Config.show(self)
    901         self.dmrs.show()
    902         self.tb.show()
    903 
    904     def check_config(self):
    905         """Test if the compound configuration is valid"""
    906 
    907         self.carrier.check_config()
    908         self.dmrs.check_config()
    909         if self.precoding=="codebook":
    910             # Check that dmrs_port_set matches number of layers
    911             if len(self.dmrs.dmrs_port_set)>0:
    912                 assert len(self.dmrs.dmrs_port_set)==self.num_layers, \
    913                 "num_layers must be equal to the number of dmrs ports"
    914 
    915             # Check that num_layers<=num_antenna_ports
    916             assert self.num_layers <= self.num_antenna_ports,\
    917                 "num_layers must be <= num_antenna_ports"
    918 
    919             # Check that more than one antenna port is available
    920             assert self.num_antenna_ports>=2, \
    921                 "precoding requires two or more antenna ports"
    922         else:
    923             # Check that num_layers==num_antenna_ports
    924             assert self.num_layers == self.num_antenna_ports,\
    925                 "num_layers must be == num_antenna_ports"
    926 
    927         # Check Tables 6.4.1.1.3-3/4 are valid
    928         if self.dmrs.length==1:
    929             if self.mapping_type=="A":
    930                 assert self.symbol_allocation[1]>=4, \
    931                     "Symbol allocation is too short"
    932         else:
    933             assert self.dmrs.additional_position<2, \
    934                 "dmrs.additional_position must be <2 for this dmrs.length"
    935             assert self.symbol_allocation[1]>=4, "Symbol allocation too short"
    936             if self.mapping_type=="B":
    937                 assert self.symbol_allocation[1]>=5, \
    938                     "Symbol allocation is too short"
    939 
    940         # Check type_a and additional_position position
    941         if self.mapping_type=="A":
    942             if self.dmrs.additional_position==3:
    943                 assert self.dmrs.type_a_position==2,\
    944                 "additional_position=3 only allowed for type_a_position=2"
    945 
    946         # Check for valid TMPI
    947         if self.num_layers==1:
    948             if self.num_antenna_ports==2:
    949                 assert self.tpmi in range(6), "tpmi must be in [0,...,5]"
    950             elif self.num_antenna_ports==4:
    951                 assert self.tpmi in range(28), "tpmi must be in [0,...,27]"
    952         elif self.num_layers==2:
    953             if self.num_antenna_ports==2:
    954                 assert self.tpmi in range(3), "tpmi must be in [0,...,2]"
    955             elif self.num_antenna_ports==4:
    956                 assert self.tpmi in range(22), "tpmi must be in [0,...,21]"
    957         elif self.num_layers==3:
    958             assert self.tpmi in range(7), "tpmi must be in [0,...,6]"
    959         elif self.num_layers==4:
    960             assert self.tpmi in range(5), "tpmi must be in [0,...,4]"
    961 
    962         # Check that symbol allocation is valid
    963         if self.carrier.cyclic_prefix=="normal":
    964             max_length = 14
    965         else: # cyclic_prefix == "extended"
    966             max_length = 12
    967         if self.mapping_type=="A":
    968             assert self.symbol_allocation[0]==0, \
    969                 "symbol_allocation[0] must be 0 for mapping_type A"
    970             assert 4<=self.symbol_allocation[1]<=max_length, \
    971                 "symbol_allocation[1] must be in [4, 14 (or 12)]"
    972             if self.dmrs.length==2:
    973                 assert self.symbol_allocation[1]>=4, \
    974                     "symbol_allocation[1] must be >=4 for dmrs.length==2"
    975         elif self.mapping_type=="B":
    976             assert 0<=self.symbol_allocation[0]<=13, \
    977                 "symbol_allocation[0] must be in [0,13] for mapping_type B"
    978             assert 1<=self.symbol_allocation[1]<=max_length, \
    979                 "symbol_allocation[1] must be in [1, 14 (or 12)]"
    980             if self.dmrs.length==2:
    981                 assert self.symbol_allocation[1]>=5, \
    982                     "symbol_allocation[1] must be >=5 for dmrs.length==2"
    983         assert self.symbol_allocation[0] \
    984                + self.symbol_allocation[1]<=max_length, \
    985             "symbol_allocation[0]+symbol_allocation[1] must be < 14 (or 12)"
    986 
    987         attr_list = ["n_size_bwp",
    988                      "n_start_bwp",
    989                      "num_layers",
    990                      "mapping_type",
    991                      "symbol_allocation",
    992                      "n_rnti",
    993                      "precoding",
    994                      "transform_precoding",
    995                      "tpmi"
    996                     ]
    997         for attr in attr_list:
    998             value = getattr(self, attr)
    999             setattr(self, attr, value)
   1000 
   1001         # check that TBConfig is configured for "PUSCH"
   1002         assert self.tb.channel_type=="PUSCH", \
   1003                 'TB_config must be configured for "PUSCH" transmission.'
   1004 
   1005         # Check that the number of DMRS ports equals the number of layers
   1006         # if dmrs_port_set has been set. Otherwise, this is
   1007         # automatically ensured.
   1008         if len(self.dmrs.dmrs_port_set)>0:
   1009             assert self.num_layers==len(self.dmrs.dmrs_port_set), \
   1010                 "num_layers must equal the number of DMRS ports"
   1011 
   1012         return True
   1013 
   1014 def check_pusch_configs(pusch_configs):
   1015 
   1016     # Check that pusch_configs is a list
   1017     assert isinstance(pusch_configs, list), \
   1018         """pusch_configs must be a Sequence of instances of PUSCHConfig"""
   1019 
   1020     # Iterate through all pusch_configs and check their type and validity
   1021     for pusch_config in pusch_configs:
   1022         assert isinstance(pusch_config, PUSCHConfig), \
   1023         """All elements of pusch_configs must be instances of PUSCHConfig"""
   1024 
   1025         pusch_config.check_config()
   1026 
   1027     # Create dictionary with extracted configuration parameters
   1028     pc = pusch_configs[0]
   1029     carrier = pc.carrier
   1030 
   1031     params = {
   1032         "num_bits_per_symbol" : pc.tb.num_bits_per_symbol,
   1033         "num_tx" : len(pusch_configs),
   1034         "num_layers" : pc.num_layers,
   1035         "num_subcarriers" : pc.num_subcarriers,
   1036         "num_ofdm_symbols" : pc.symbol_allocation[1],
   1037         "subcarrier_spacing" : pc.carrier.subcarrier_spacing*1e3,
   1038         "num_antenna_ports" : pc.num_antenna_ports,
   1039         "precoding" : pc.precoding,
   1040         "precoding_matrices" : [],
   1041         "pusch_config" : pc,
   1042         "carrier_config" : pc.carrier,
   1043         "num_coded_bits" : pc.num_coded_bits,
   1044         "target_coderate" : pc.tb.target_coderate,
   1045         "n_id" : [],
   1046         "n_rnti" : [],
   1047         "tb_size" : pc.tb_size,
   1048         "dmrs_length" : pc.dmrs.length,
   1049         "dmrs_additional_position" : pc.dmrs.additional_position,
   1050         "num_cdm_groups_without_data" : pc.dmrs.num_cdm_groups_without_data
   1051     }
   1052     params["bandwidth"] = params["num_subcarriers"]*params["subcarrier_spacing"]
   1053     params["cyclic_prefix_length"] = np.ceil(carrier.cyclic_prefix_length *
   1054                                              params["bandwidth"])
   1055 
   1056     for pusch_config in pusch_configs:
   1057         if params["precoding"]=="codebook":
   1058             params["precoding_matrices"].append(pusch_config.precoding_matrix)
   1059 
   1060         # n_rnti and n_id are given per tx
   1061         if pusch_config.tb.n_id is None:
   1062             params["n_id"].append(pusch_config.carrier.n_cell_id)
   1063         else:
   1064             params["n_id"].append(pusch_config.tb.n_id)
   1065         params["n_rnti"].append(pusch_config.n_rnti)
   1066 
   1067     return params