Source code for sionna.phy.nr.pusch_pilot_pattern
#
# SPDX-FileCopyrightText: Copyright (c) 2021-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""PUSCH pilot pattern for the 5G NR module of Sionna PHY."""
from collections.abc import Sequence
from typing import List, Optional, Union
import warnings
import numpy as np
from sionna.phy.ofdm import PilotPattern
from .pusch_config import PUSCHConfig
__all__ = ["PUSCHPilotPattern"]
[docs]
class PUSCHPilotPattern(PilotPattern):
r"""Pilot pattern for NR PUSCH.
This class defines a :class:`~sionna.phy.ofdm.PilotPattern`
that is used to configure an OFDM :class:`~sionna.phy.ofdm.ResourceGrid`.
For every transmitter, a separate :class:`~sionna.phy.nr.PUSCHConfig`
needs to be provided from which the pilot pattern will be created.
:param pusch_configs: PUSCH configurations according to which the pilot
pattern will be created. One configuration is needed for each
transmitter.
:param precision: `None` (default) | ``"single"`` | ``"double"``.
Precision used for internal calculations and outputs.
If set to `None`,
:attr:`~sionna.phy.config.Config.precision` is used.
.. rubric:: Examples
.. code-block:: python
from sionna.phy.nr import PUSCHConfig, PUSCHPilotPattern
pusch_config = PUSCHConfig()
pilot_pattern = PUSCHPilotPattern(pusch_config)
print(pilot_pattern.mask.shape)
"""
def __init__(
self,
pusch_configs: Union[PUSCHConfig, List[PUSCHConfig]],
precision: Optional[str] = None,
):
# Check correct type of pusch_configs
if isinstance(pusch_configs, PUSCHConfig):
pusch_configs = [pusch_configs]
elif isinstance(pusch_configs, Sequence):
for c in pusch_configs:
if not isinstance(c, PUSCHConfig):
raise TypeError(
"Each element of pusch_configs must be a valid PUSCHConfig")
else:
raise ValueError("Invalid value for pusch_configs")
# Check validity of provided pusch_configs
num_tx = len(pusch_configs)
num_streams_per_tx = pusch_configs[0].num_layers
dmrs_grid = pusch_configs[0].dmrs_grid
num_subcarriers = dmrs_grid[0].shape[0]
num_ofdm_symbols = pusch_configs[0].l_d
precoding = pusch_configs[0].precoding
dmrs_ports = []
num_pilots = np.sum(pusch_configs[0].dmrs_mask)
for pusch_config in pusch_configs:
if pusch_config.num_layers != num_streams_per_tx:
raise ValueError(
"All pusch_configs must have the same number of layers")
if pusch_config.dmrs_grid[0].shape[0] != num_subcarriers:
raise ValueError(
"All pusch_configs must have the same number of subcarriers")
if pusch_config.l_d != num_ofdm_symbols:
raise ValueError(
"All pusch_configs must have the same number of OFDM symbols")
if pusch_config.precoding != precoding:
raise ValueError(
"All pusch_configs must have the same precoding method")
if np.sum(pusch_config.dmrs_mask) != num_pilots:
raise ValueError(
"All pusch_configs must have the same number of masked REs")
with warnings.catch_warnings():
warnings.simplefilter('always')
for port in pusch_config.dmrs.dmrs_port_set:
if port in dmrs_ports:
msg = f"DMRS port {port} used by multiple transmitters"
warnings.warn(msg)
dmrs_ports += pusch_config.dmrs.dmrs_port_set
# Create mask and pilots tensors
mask = np.zeros([num_tx, num_streams_per_tx, num_ofdm_symbols,
num_subcarriers], bool)
num_pilots = np.sum(pusch_configs[0].dmrs_mask)
pilots = np.zeros([num_tx, num_streams_per_tx, num_pilots], complex)
for i, pusch_config in enumerate(pusch_configs):
for j in range(num_streams_per_tx):
ind0, ind1 = pusch_config.symbol_allocation
mask[i, j] = np.transpose(
pusch_config.dmrs_mask[:, ind0:ind0 + ind1])
dmrs_grid = np.transpose(
pusch_config.dmrs_grid[j, :, ind0:ind0 + ind1])
pilots[i, j] = dmrs_grid[np.where(mask[i, j])]
# Init PilotPattern class
super().__init__(mask, pilots, normalize=False, precision=precision)