Skip to main content
Ctrl+K

Sionna

  • Installation
  • Ray Tracing (RT)
  • Physical Layer (PHY)
  • System Level (SYS)
  • Research Kit (RK)
    • “Made with Sionna”
    • Citation
  • GitHub
  • PyPI
  • Installation
  • Ray Tracing (RT)
  • Physical Layer (PHY)
  • System Level (SYS)
  • Research Kit (RK)
  • “Made with Sionna”
  • Citation
  • GitHub
  • PyPI

Section Navigation

  • Tutorials
    • Beginners
      • “Hello, world!”
      • Part 1: Getting Started with Sionna
      • Part 2: Differentiable Communication Systems
      • Part 3: Advanced Link-level Simulations
      • Part 4: Toward Learned Receivers
      • Basic MIMO Simulations
      • Pulse-shaping Basics
      • Optical Channel with Lumped Amplification
    • Experts
      • 5G Channel Coding and Rate-Matching: Polar vs. LDPC Codes
      • 5G NR PUSCH Tutorial
      • Bit-Interleaved Coded Modulation (BICM)
      • MIMO OFDM Transmissions over the CDL Channel Model
      • Neural Receiver for OFDM SIMO Systems
      • Realistic Multiuser MIMO OFDM Simulations
      • OFDM MIMO Channel Estimation and Detection
      • Introduction to Iterative Detection and Decoding
      • End-to-end Learning with Autoencoders
      • Weighted Belief Propagation Decoding
      • OFDM-based Communications with Superimposed Pilots
      • Channel Models from Datasets
      • Link-level simulations with Sionna RT
  • API Documentation
    • Block
    • Configuration
    • Channel Models
      • Wireless
        • 3GPP 38.901
          • PanelArray
          • Antenna
          • AntennaArray
          • TDL
          • CDL
          • UMi
          • UMa
          • RMa
        • AWGN
        • CIRDataset
        • ChannelModel
        • Flat Fading
          • FlatFadingChannel
          • GenerateFlatFadingChannel
          • ApplyFlatFadingChannel
          • SpatialCorrelation
          • KroneckerModel
          • PerColumnModel
        • Frequency (OFDM) Domain
          • OFDMChannel
          • GenerateOFDMChannel
          • ApplyOFDMChannel
          • cir_to_ofdm_channel
        • RayleighBlockFading
        • Time Domain
          • TimeChannel
          • GenerateTimeChannel
          • ApplyTimeChannel
          • cir_to_time_channel
          • time_to_ofdm_channel
        • Utility Functions
          • subcarrier_frequencies
          • time_lag_discrete_time_channel
          • deg_2_rad
          • rad_2_deg
          • wrap_angle_0_360
          • drop_uts_in_sector
          • relocate_uts
          • set_3gpp_scenario_parameters
          • gen_single_sector_topology
          • gen_single_sector_topology_interferers
          • exp_corr_mat
          • one_ring_corr_mat
      • Optical
        • SSFM
        • EDFA
        • time_frequency_vector
      • Discrete
    • Forward Error Correction (FEC)
      • Linear Codes
        • LinearEncoder
        • OSDecoder
      • Low-Density Parity-Check (LDPC)
        • LDPC5GEncoder
        • LDPCBPDecoder
        • LDPC5GDecoder
        • Node Update Functions
          • vn_update_sum
          • cn_update_minsum
          • cn_update_offset_minsum
          • cn_update_phi
          • cn_update_tanh
        • Decoder Callbacks
          • DecoderStatisticsCallback
          • EXITCallback
          • WeightedBPCallback
      • Polar Codes
        • Polar5GEncoder
        • PolarEncoder
        • Polar5GDecoder
        • PolarSCDecoder
        • PolarSCLDecoder
        • PolarBPDecoder
        • Utility Functions
          • generate_5g_ranking
          • generate_polar_transform_mat
          • generate_rm_code
          • generate_dense_polar
      • Convolutional Codes
        • ConvEncoder
        • ViterbiDecoder
        • BCJRDecoder
        • Utility Functions
          • polynomial_selector
          • Trellis
      • Turbo Codes
        • TurboEncoder
        • TurboDecoder
        • Utility Functions
          • polynomial_selector
          • puncture_pattern
          • TurboTermination
      • Cyclic Redundancy Check (CRC)
        • CRCEncoder
        • CRCDecoder
      • Interleaving
        • RowColumnInterleaver
        • RandomInterleaver
        • Turbo3GPPInterleaver
        • Deinterleaver
      • Scrambling
        • Scrambler
        • Descrambler
        • TB5GScrambler
      • Utility Functions
        • (Binary) Linear Codes
          • load_parity_check_examples
          • alist2mat
          • load_alist
          • generate_reg_ldpc
          • make_systematic
          • gm2pcm
          • pcm2gm
          • verify_gm_pcm
        • EXIT Analysis
          • plot_exit_chart
          • get_exit_analytic
          • plot_trajectory
        • Miscellaneous
          • GaussianPriorSource
          • bin2int
          • int2bin
          • int_mod_2
          • llr2mi
          • j_fun
          • j_fun_inv
    • Mapping
      • Constellation
      • Mapper
      • Demapper
      • SymbolDemapper
      • Utility Functions
        • BinarySource
        • LLRs2SymbolLogits
        • pam
        • pam_gray
        • PAM2QAM
        • PAMSource
        • qam
        • QAM2PAM
        • QAMSource
        • SymbolInds2Bits
        • SymbolLogits2LLRs
        • SymbolLogits2Moments
        • SymbolSource
    • Multiple-Input Multiple-Output (MIMO)
      • StreamManagement
      • Precoding
        • cbf_precoding_matrix
        • rzf_precoding_matrix
        • rzf_precoder
        • grid_of_beams_dft_ula
        • grid_of_beams_dft
        • flatten_precoding_mat
        • normalize_precoding_power
      • Equalization
        • lmmse_matrix
        • lmmse_equalizer
        • zf_equalizer
        • mf_equalizer
      • Detection
        • LinearDetector
        • MaximumLikelihoodDetector
        • KBestDetector
        • EPDetector
        • MMSEPICDetector
      • Utility Functions
        • List2LLR
        • List2LLRSimple
        • complex2real_vector
        • real2complex_vector
        • complex2real_matrix
        • real2complex_matrix
        • complex2real_covariance
        • real2complex_covariance
        • complex2real_channel
        • real2complex_channel
        • whiten_channel
    • 5G NR
      • Carrier
        • CarrierConfig
      • Layer Mapping
        • LayerMapper
        • LayerDemapper
      • PUSCH
        • PUSCHConfig
        • PUSCHDMRSConfig
        • PUSCHLSChannelEstimator
        • PUSCHPilotPattern
        • PUSCHPrecoder
        • PUSCHReceiver
        • PUSCHTransmitter
      • Transport Block
        • TBConfig
        • TBEncoder
        • TBDecoder
      • Utils
        • calculate_tb_size
        • generate_prng_seq
        • decode_mcs_index
        • calculate_num_coded_bits
        • TransportBlockNR
        • CodedAWGNChannelNR
        • MCSDecoderNR
    • Object
    • Orthogonal Frequency-Division Multiplexing (OFDM)
      • Resource Grid
        • ResourceGrid
        • ResourceGridMapper
        • ResourceGridDemapper
        • RemoveNulledSubcarriers
      • Modulation & Demodulation
        • OFDMModulator
        • OFDMDemodulator
      • Pilot Pattern
        • PilotPattern
        • EmptyPilotPattern
        • KroneckerPilotPattern
      • Channel Estimation
        • BaseChannelEstimator
        • LSChannelEstimator
        • BaseChannelInterpolator
        • NearestNeighborInterpolator
        • LinearInterpolator
        • LMMSEInterpolator1D
        • LMMSEInterpolator
        • SpatialChannelFilter
        • tdl_freq_cov_mat
        • tdl_time_cov_mat
      • Precoding
        • RZFPrecoder
        • PrecodedChannel
        • CBFPrecodedChannel
        • EyePrecodedChannel
        • RZFPrecodedChannel
      • Equalization
        • OFDMEqualizer
        • LMMSEEqualizer
        • ZFEqualizer
        • MFEqualizer
        • PostEqualizationSINR
        • LMMSEPostEqualizationSINR
      • Detection
        • OFDMDetector
        • OFDMDetectorWithPrior
        • LinearDetector
        • MaximumLikelihoodDetector
        • MaximumLikelihoodDetectorWithPrior
        • KBestDetector
        • EPDetector
        • MMSEPICDetector
    • Signal
      • Filters
        • Filter
        • RaisedCosineFilter
        • RootRaisedCosineFilter
        • SincFilter
        • CustomFilter
      • Window functions
        • Window
        • HannWindow
        • HammingWindow
        • BlackmanWindow
        • CustomWindow
      • Utility functions
        • convolve
        • fft
        • ifft
        • empirical_psd
        • empirical_aclr
        • Upsampling
        • Downsampling
    • Utility Functions
      • Linear Algebra
        • inv_cholesky
        • matrix_pinv
      • Metrics
        • compute_ber
        • compute_bler
        • compute_ser
        • count_block_errors
        • count_errors
      • Misc
        • complex_normal
        • db_to_lin
        • dbm_to_watt
        • DeepUpdateDict
        • dict_keys_to_int
        • ebnodb2no
        • hard_decisions
        • Interpolate
        • lin_to_db
        • MCSDecoder
        • sample_bernoulli
        • scalar_to_shaped_tensor
        • sim_ber
        • SingleLinkChannel
        • SplineGriddataInterpolation
        • to_list
        • TransportBlock
        • watt_to_dbm
      • Numerics
        • bisection_method
        • expand_bound
      • Plotting
        • plot_ber
        • PlotBER
      • Random number generation
        • randint
        • rand
        • uniform
        • normal
      • Tensors
        • diag_part_axis
        • enumerate_indices
        • expand_to_rank
        • find_true_position
        • flatten_dims
        • flatten_last_dims
        • flatten_multi_index
        • gather_from_batched_indices
        • insert_dims
        • random_tensor_from_values
        • split_dim
        • tensor_values_are_in_set
  • Developer Guides
    • Matrix inversion
    • Random number generation
    • Sionna Block and Object
  • References
  • Physical Layer (PHY)
  • Tutorials
  • Experts
  • Link-level simulations with Sionna RT
Colab logo Run in Google Colab View on GitHub ⬇ Download notebook

Link-level simulations with Sionna RT#

In this notebook, you will use ray-traced channels for link-level simulations instead of stochastic channel models

Background Information#

Ray tracing is a technique to simulate environment-specific and physically accurate channel realizations for a given scene and user position. Sionna RT is a ray tracing extension for radio propagation modeling which is built on top of Mitsuba 3. Like all of Sionna’s components, it is differentiable.

For an introduction about how to use Sionna RT, please see the corresponding tutorials. The EM Primer provides further details on the theoretical background of ray tracing of wireless channels.

In this notebook, we will use Sionna RT for site-specific link-level simulations. For this, we evaluate the BER performance for a MU-MIMO 5G NR system in the uplink direction based on ray traced CIRs for random user positions.

We use the 5G NR PUSCH transmitter and receiver from the 5G NR PUSCH Tutorial notebook. Note that also the systems from the MIMO OFDM Transmissions over the CDL Channel Model or the Neural Receiver for OFDM SIMO Systems tutorials could be used instead.

There are different ways to implement uplink scenarios in Sionna RT. In this example, we configure the basestation as transmitter and the user equipments (UEs) as receivers which simplifies the ray tracing. Due to channel reciprocity, one can reverse the direction of the ray traced channels afterwards. For the ray tracer itself, the direction (uplink/downlink) does not change the simulated paths.

Imports#

[1]:
# Import or install Sionna
try:
    import sionna.phy
    import sionna.rt
except ImportError as e:
    import sys
    import os
    if 'google.colab' in sys.modules:
       # Install Sionna in Google Colab
       print("Installing Sionna and restarting the runtime. Please run the cell again.")
       os.system("pip install sionna")
       os.kill(os.getpid(), 5)
    else:
       raise e

import numpy as np
import torch

# For link-level simulations
from sionna.phy.channel import OFDMChannel, CIRDataset
from sionna.phy.nr import PUSCHConfig, PUSCHTransmitter, PUSCHReceiver
from sionna.phy.utils import ebnodb2no, PlotBER
from sionna.phy.ofdm import KBestDetector, LinearDetector
from sionna.phy.mimo import StreamManagement

# Import Sionna RT components
from sionna.rt import load_scene, Camera, Transmitter, Receiver, PlanarArray,\
                      PathSolver, RadioMapSolver

no_preview = True # Toggle to False to use the preview widget
                  # instead of rendering for scene visualization

Setting up the Ray Tracer#

Let’s start by defining some constants that control the system we want to simulate.

[2]:
# System parameters
subcarrier_spacing = 30e3 # Hz
num_time_steps = 14 # Total number of ofdm symbols per slot

num_tx = 4 # Number of users
num_rx = 1 # Only one receiver considered
num_tx_ant = 4 # Each user has 4 antennas
num_rx_ant = 16 # The receiver is equipped with 16 antennas

# batch_size for CIR generation
batch_size_cir = 1000

We then set up the radio propagation environment. We start by loading a scene and then add a transmitter that acts as a base station. We will later use channel reciprocity to simulate the uplink direction.

[3]:
# Load an integrated scene.
# You can try other scenes, such as `sionna.rt.scene.etoile`. Note that this would require
# updating the position of the transmitter (see below in this cell).
scene = load_scene(sionna.rt.scene.munich)

# Transmitter (=basestation) has an antenna pattern from 3GPP 38.901
scene.tx_array = PlanarArray(num_rows=1,
                             num_cols=num_rx_ant//2, # We want to transmitter to be equiped with 16 antennas
                             vertical_spacing=0.5,
                             horizontal_spacing=0.5,
                             pattern="tr38901",
                             polarization="cross")

# Create transmitter
tx = Transmitter(name="tx",
                 position=[8.5,21,27],
                 look_at=[45,90,1.5], # optional, defines view direction
                 display_radius=3.) # optinal, radius of the sphere for visualizing the device
scene.add(tx)

# Create new camera
bird_cam = Camera(position=[0,80,500], orientation=np.array([0,np.pi/2,-np.pi/2]))

We then compute a radio map for the instantiated transmitter.

[4]:
max_depth = 5

# Radio map solver
rm_solver = RadioMapSolver()

# Compute the radio map
rm = rm_solver(scene,
               max_depth=5,
               cell_size=(1., 1.),
               samples_per_tx=10**7)

Let’s visualize the computed radio map.

[5]:
if no_preview:
    # Render an image
    scene.render(camera=bird_cam,
                 radio_map=rm,
                 rm_vmin=-110,
                 clip_at=12.); # Clip the scene at rendering for visualizing the refracted field
else:
    # Show preview
    scene.preview(radio_map=rm,
                  rm_vmin=-110,
                  clip_at=12.); # Clip the scene at rendering for visualizing the refracted field
../../../../build/doctrees/nbsphinx/phy_tutorials_notebooks_Link_Level_Simulations_with_RT_14_0.png

The function RadioMap.sample_positions() allows sampling of random user positions from a radio map. It ensures that only positions that have a path gain of at least min_gain_dB dB and at most max_gain_dB dB are sampled, i.e., it ignores positions without a connection to the transmitter. Further, one can set the distances min_dist and max_dist to sample only points within a certain distance range from the transmitter.

[6]:
min_gain_db = -130 # in dB; ignore any position with less than -130 dB path gain
max_gain_db = 0 # in dB; ignore strong paths

# Sample points in a 5-400m range around the receiver
min_dist = 5 # in m
max_dist = 400 # in m

# Sample batch_size random user positions from the radio map
ue_pos, _ = rm.sample_positions(num_pos=batch_size_cir,
                                metric="path_gain",
                                min_val_db=min_gain_db,
                                max_val_db=max_gain_db,
                                min_dist=min_dist,
                                max_dist=max_dist)

We now add new receivers (=UEs) at the sampled positions.

Remark: This is an example for 5G NR PUSCH (uplink direction), we will reverse the direction of the channel for later BER simulations.

[7]:
# Configure antenna array for all receivers (=UEs)
scene.rx_array = PlanarArray(num_rows=1,
                             num_cols=num_tx_ant//2, # Each receiver is equipped with 4 antennas
                             vertical_spacing=0.5,
                             horizontal_spacing=0.5,
                             pattern="iso",
                             polarization="cross")

# Create batch_size receivers
for i in range(batch_size_cir):
    scene.remove(f"rx-{i}") # Remove old receiver if any
    rx = Receiver(name=f"rx-{i}",
                  position=ue_pos[0][i].numpy(), # Position sampled from radio map
                  velocity=(3.,3.,0),
                  display_radius=1., # optional, radius of the sphere for visualizing the device
                  color=(1,0,0) # optional, color for visualizing the device
                  )
    scene.add(rx)

# And visualize the scene
if no_preview:
    # Render an image
    scene.render(camera=bird_cam,
                 radio_map=rm,
                 rm_vmin=-110,
                 clip_at=12.); # Clip the scene at rendering for visualizing the refracted field
else:
    # Show preview
    scene.preview(radio_map=rm,
                  rm_vmin=-110,
                  clip_at=12.); # Clip the scene at rendering for visualizing the refracted field
../../../../build/doctrees/nbsphinx/phy_tutorials_notebooks_Link_Level_Simulations_with_RT_18_0.png

Each dot represents a receiver position drawn from the random sampling function of the radio map. This allows to efficiently sample batches of random channel realizations even in complex scenarios.

Creating a CIR Dataset#

We can now simulate the CIRs for many different positions which will be used later on as a dataset for link-level simulations.

Remark: Running the cells below can take some time depending on the requested number of CIRs.

[8]:
target_num_cirs = 5000 # Defines how many different CIRs are generated.
# Remark: some path are removed if no path was found for this position

max_depth = 5
min_gain_db = -130 # in dB / ignore any position with less than -130 dB path gain
max_gain_db = 0 # in dB / ignore any position with more than 0 dB path gain

# Sample points within a 10-400m radius around the transmitter
min_dist = 10 # in m
max_dist = 400 # in m

# List of channel impulse reponses
a_list = []
tau_list = []

# Maximum number of paths over all batches of CIRs.
# This is used later to concatenate all CIRs.
max_num_paths = 0

# Path solver
p_solver = PathSolver()

# Each simulation returns batch_size_cir results
num_runs = int(np.ceil(target_num_cirs/batch_size_cir))
for idx in range(num_runs):
    print(f"Progress: {idx+1}/{num_runs}", end="\r")

    # Sample random user positions
    ue_pos, _ = rm.sample_positions(
                        num_pos=batch_size_cir,
                        metric="path_gain",
                        min_val_db=min_gain_db,
                        max_val_db=max_gain_db,
                        min_dist=min_dist,
                        max_dist=max_dist,
                        seed=idx) # Change the seed from one run to the next to avoid sampling the same positions

    # Update all receiver positions
    for rx in range(batch_size_cir):
        scene.receivers[f"rx-{rx}"].position = ue_pos[0][rx].numpy()

    # Simulate CIR
    paths = p_solver(scene, max_depth=max_depth, max_num_paths_per_src=10**7)

    # Transform paths into channel impulse responses
    a, tau = paths.cir(sampling_frequency=subcarrier_spacing,
                         num_time_steps=14,
                         out_type='numpy')
    a_list.append(a)
    tau_list.append(tau)

    # Update maximum number of paths over all batches of CIRs
    num_paths = a.shape[-2]
    if num_paths > max_num_paths:
        max_num_paths = num_paths

# Concatenate all the CIRs into a single tensor along the num_rx dimension.
# First, we need to pad the CIRs to ensure they all have the same number of paths.
a = []
tau = []
for a_,tau_ in zip(a_list, tau_list):
    num_paths = a_.shape[-2]
    a.append(np.pad(a_, [[0,0],[0,0],[0,0],[0,0],[0,max_num_paths-num_paths],[0,0]], constant_values=0))
    tau.append(np.pad(tau_, [[0,0],[0,0],[0,max_num_paths-num_paths]], constant_values=0))
a = np.concatenate(a, axis=0) # Concatenate along the num_rx dimension
tau = np.concatenate(tau, axis=0)

# Let's now convert to uplink direction, by switing the receiver and transmitter
# dimensions
a = np.transpose(a, (2,3,0,1,4,5))
tau = np.transpose(tau, (1,0,2))

# Add a batch_size dimension
a = np.expand_dims(a, axis=0)
tau = np.expand_dims(tau, axis=0)

# Exchange the num_tx and batchsize dimensions
a = np.transpose(a, [3, 1, 2, 0, 4, 5, 6])
tau = np.transpose(tau, [2, 1, 0, 3])

# Remove CIRs that have no active link (i.e., a is all-zero)
p_link = np.sum(np.abs(a)**2, axis=(1,2,3,4,5,6))
a = a[p_link>0.,...]
tau = tau[p_link>0.,...]

print("Shape of a:", a.shape)
print("Shape of tau: ", tau.shape)
Shape of a: (4874, 1, 16, 1, 4, 49, 14)
Shape of tau:  (4874, 1, 1, 49)

Note that transmitters and receivers have been reversed, i.e., the transmitter now denotes the UE (with 4 antennas each) and the receiver is the base station (with 16 antennas).

Remark: We have removed all positions for which the resulting CIR had zero gain, i.e., there was no path between the transmitter and the receiver. This comes from the fact that the RadioMap.sample_positions() function samples from a radio map subdivided into cells and randomizes the position within the cells. Therefore, randomly sampled positions may have no paths connecting them to the transmitter.

Let us now define a data generator that samples random UEs from the dataset and yields the previously simulated CIRs.

[9]:
class CIRGenerator:
    """Creates a generator from a given dataset of channel impulse responses

    The generator samples ``num_tx`` different transmitters from the given path
    coefficients `a` and path delays `tau` and stacks the CIRs into a single tensor.

    Note that the generator internally samples ``num_tx`` random transmitters
    from the dataset. For this, the inputs ``a`` and ``tau`` must be given for
    a single transmitter (i.e., ``num_tx`` =1) which will then be stacked
    internally.

    Parameters
    ----------
    a : [batch size, num_rx, num_rx_ant, 1, num_tx_ant, num_paths, num_time_steps], complex
        Path coefficients per transmitter

    tau : [batch size, num_rx, 1, num_paths], float
        Path delays [s] per transmitter

    num_tx : int
        Number of transmitters

    Output
    -------
    a : [batch size, num_rx, num_rx_ant, num_tx, num_tx_ant, num_paths, num_time_steps], torch.complex
        Path coefficients

    tau : [batch size, num_rx, num_tx, num_paths], torch.float
        Path delays [s]
    """

    def __init__(self,
                 a,
                 tau,
                 num_tx):

        # Copy to PyTorch tensors on the correct device
        self._a = torch.as_tensor(a, dtype=torch.complex64, device=sionna.phy.config.device)
        self._tau = torch.as_tensor(tau, dtype=torch.float32, device=sionna.phy.config.device)
        self._dataset_size = self._a.shape[0]

        self._num_tx = num_tx

    def __call__(self):

        # Generator implements an infinite loop that yields new random samples
        while True:
            # Sample num_tx random users (unique indices without replacement)
            idx = torch.randperm(self._dataset_size, device=sionna.phy.config.device)[:self._num_tx]

            a = self._a[idx]
            tau = self._tau[idx]

            # Transpose to remove batch dimension
            a = a.permute(3, 1, 2, 0, 4, 5, 6)
            tau = tau.permute(2, 1, 0, 3)

            # And remove batch-dimension
            a = a.squeeze(0)
            tau = tau.squeeze(0)

            yield a, tau

We use Sionna’s built-in CIRDataset to initialize a channel model that can be directly used in Sionna’s OFDMChannel layer.

[10]:
batch_size = 20 # Must be the same for the BER simulations as CIRDataset returns fixed batch_size

# Init CIR generator
cir_generator = CIRGenerator(a,
                             tau,
                             num_tx)
# Initialises a channel model that can be directly used by OFDMChannel layer
channel_model = CIRDataset(cir_generator,
                           batch_size,
                           num_rx,
                           num_rx_ant,
                           num_tx,
                           num_tx_ant,
                           max_num_paths,
                           num_time_steps)

PUSCH Link-Level Simulations#

Let’s now define an end-to-end model that simulates a PUSCH transmission.

[11]:
class Model:
    """Simulate PUSCH transmissions

    This model runs BER simulations for a multi-user MIMO uplink channel
    compliant with the 5G NR PUSCH specifications.
    You can pick different scenarios, i.e., channel models, perfect or
    estimated CSI, as well as different MIMO detectors (LMMSE or KBest).

    Parameters
    ----------
    channel_model : :class:`~sionna.channel.ChannelModel` object
        An instance of a :class:`~sionna.channel.ChannelModel` object, such as
        :class:`~sionna.channel.RayleighBlockFading` or
        :class:`~sionna.channel.tr38901.UMi` or
        :class:`~sionna.channel.CIRDataset`.

    perfect_csi : bool
        Determines if perfect CSI is assumed or if the CSI is estimated

    detector : str, one of ["lmmse", "kbest"]
        MIMO detector to be used. Note that each detector has additional
        parameters that can be configured in the source code of the _init_ call.

    Input
    -----
    batch_size : int
        Number of simultaneously simulated slots

    ebno_db : float
        Signal-to-noise-ratio

    Output
    ------
    b : [batch_size, num_tx, tb_size], torch.float
        Transmitted information bits

    b_hat : [batch_size, num_tx, tb_size], torch.float
        Decoded information bits
    """
    def __init__(self,
                 channel_model,
                 perfect_csi, # bool
                 detector,    # "lmmse", "kbest"
                ):
        super().__init__()

        self._channel_model = channel_model
        self._perfect_csi = perfect_csi

        # System configuration
        self._num_prb = 16
        self._mcs_index = 14
        self._num_layers = 1
        self._mcs_table = 1
        self._domain = "freq"

        # Below parameters must equal the Path2CIR parameters
        self._num_tx_ant = 4
        self._num_tx = 4
        self._subcarrier_spacing = 30e3 # must be the same as used for Path2CIR

        # PUSCHConfig for the first transmitter
        pusch_config = PUSCHConfig()
        pusch_config.carrier.subcarrier_spacing = self._subcarrier_spacing/1000
        pusch_config.carrier.n_size_grid = self._num_prb
        pusch_config.num_antenna_ports = self._num_tx_ant
        pusch_config.num_layers = self._num_layers
        pusch_config.precoding = "codebook"
        pusch_config.tpmi = 1
        pusch_config.dmrs.dmrs_port_set = list(range(self._num_layers))
        pusch_config.dmrs.config_type = 1
        pusch_config.dmrs.length = 1
        pusch_config.dmrs.additional_position = 1
        pusch_config.dmrs.num_cdm_groups_without_data = 2
        pusch_config.tb.mcs_index = self._mcs_index
        pusch_config.tb.mcs_table = self._mcs_table

        # Create PUSCHConfigs for the other transmitters by cloning of the first PUSCHConfig
        # and modifying the used DMRS ports.
        pusch_configs = [pusch_config]
        for i in range(1, self._num_tx):
            pc = pusch_config.clone()
            pc.dmrs.dmrs_port_set = list(range(i*self._num_layers, (i+1)*self._num_layers))
            pusch_configs.append(pc)

        # Create PUSCHTransmitter
        self._pusch_transmitter = PUSCHTransmitter(pusch_configs, output_domain=self._domain)

        # Create PUSCHReceiver
        rx_tx_association = np.ones([1, self._num_tx], bool)
        stream_management = StreamManagement(rx_tx_association,
                                             self._num_layers)

        assert detector in["lmmse", "kbest"], "Unsupported MIMO detector"
        if detector=="lmmse":
            detector = LinearDetector(equalizer="lmmse",
                                      output="bit",
                                      demapping_method="maxlog",
                                      resource_grid=self._pusch_transmitter.resource_grid,
                                      stream_management=stream_management,
                                      constellation_type="qam",
                                      num_bits_per_symbol=pusch_config.tb.num_bits_per_symbol)
        elif detector=="kbest":
            detector = KBestDetector(output="bit",
                                     num_streams=self._num_tx*self._num_layers,
                                     k=64,
                                     resource_grid=self._pusch_transmitter.resource_grid,
                                     stream_management=stream_management,
                                     constellation_type="qam",
                                     num_bits_per_symbol=pusch_config.tb.num_bits_per_symbol)

        if self._perfect_csi:
            self._pusch_receiver = PUSCHReceiver(self._pusch_transmitter,
                                                 mimo_detector=detector,
                                                 input_domain=self._domain,
                                                 channel_estimator="perfect")
        else:
            self._pusch_receiver = PUSCHReceiver(self._pusch_transmitter,
                                                 mimo_detector=detector,
                                                 input_domain=self._domain)


        # Configure the actual channel
        self._channel = OFDMChannel(
                            self._channel_model,
                            self._pusch_transmitter.resource_grid,
                            normalize_channel=True,
                            return_channel=True)

    def __call__(self, batch_size, ebno_db):

        x, b = self._pusch_transmitter(batch_size)
        no = ebnodb2no(ebno_db,
                       self._pusch_transmitter._num_bits_per_symbol,
                       self._pusch_transmitter._target_coderate,
                       self._pusch_transmitter.resource_grid)
        y, h = self._channel(x, no)
        if self._perfect_csi:
            b_hat = self._pusch_receiver(y, no, h)
        else:
            b_hat = self._pusch_receiver(y, no)
        return b, b_hat

We now initialize the end-to-end model that uses the CIRDataset.

[12]:
ebno_db = 10.
e2e_model = Model(channel_model,
                  perfect_csi=False, # bool
                  detector="lmmse")  # "lmmse", "kbest"

# We can draw samples from the end-2-end link-level simulations
b, b_hat = e2e_model(batch_size, ebno_db)

Now, let’s run the BER evaluation for different system configurations.

Remark: Running the cell below can take some time.

[13]:
ebno_db = np.arange(-3, 18, 2) # sim SNR range
ber_plot = PlotBER(f"Site-Specific MU-MIMO 5G NR PUSCH")

for detector in ["lmmse", "kbest"]:
    for perf_csi in [True, False]:
        e2e_model = Model(channel_model,
                          perfect_csi=perf_csi,
                          detector=detector)
        # define legend
        csi = "Perf. CSI" if perf_csi else "Imperf. CSI"
        det = "K-Best" if detector=="kbest" else "LMMSE"
        l = det + " " + csi
        ber_plot.simulate(
                    e2e_model,
                    ebno_dbs=ebno_db, # SNR to simulate
                    legend=l, # legend string for plotting
                    max_mc_iter=500,
                    num_target_block_errors=2000,
                    batch_size=batch_size, # batch-size per Monte Carlo run
                    soft_estimates=False, # the model returns hard-estimates
                    early_stop=True,
                    show_fig=False,
                    add_bler=True,
                    compile_mode="reduce-overhead",
                    forward_keyboard_interrupt=True);
EbNo [dB] |        BER |       BLER |  bit errors |    num bits | block errors |  num blocks | runtime [s] |    status
---------------------------------------------------------------------------------------------------------------------------------------
     -3.0 | 1.3200e-01 | 9.9423e-01 |     1370609 |    10383360 |         2068 |        2080 |        24.5 |reached target block errors
     -1.0 | 5.6696e-02 | 5.9302e-01 |      973610 |    17172480 |         2040 |        3440 |         1.6 |reached target block errors
      1.0 | 2.5651e-02 | 2.4063e-01 |     1065390 |    41533440 |         2002 |        8320 |         3.8 |reached target block errors
      3.0 | 1.1871e-02 | 1.1117e-01 |     1066649 |    89856000 |         2001 |       18000 |         8.2 |reached target block errors
      5.0 | 6.3438e-03 | 6.0156e-02 |     1053926 |   166133760 |         2002 |       33280 |        15.3 |reached target block errors
      7.0 | 3.1131e-03 | 3.1300e-02 |      621633 |   199680000 |         1252 |       40000 |        18.4 |reached max iterations
      9.0 | 1.4040e-03 | 1.4075e-02 |      280356 |   199680000 |          563 |       40000 |        18.5 |reached max iterations
     11.0 | 6.1557e-04 | 6.2250e-03 |      122917 |   199680000 |          249 |       40000 |        19.3 |reached max iterations
     13.0 | 3.7316e-04 | 3.7750e-03 |       74513 |   199680000 |          151 |       40000 |        18.7 |reached max iterations
     15.0 | 2.3418e-04 | 2.0750e-03 |       46761 |   199680000 |           83 |       40000 |        18.4 |reached max iterations
     17.0 | 7.7103e-05 | 8.5000e-04 |       15396 |   199680000 |           34 |       40000 |        18.4 |reached max iterations
EbNo [dB] |        BER |       BLER |  bit errors |    num bits | block errors |  num blocks | runtime [s] |    status
---------------------------------------------------------------------------------------------------------------------------------------
     -3.0 | 2.2892e-01 | 1.0000e+00 |     2285522 |     9984000 |         2000 |        2000 |        16.0 |reached target block errors
     -1.0 | 1.7671e-01 | 9.9952e-01 |     1834856 |    10383360 |         2079 |        2080 |         1.0 |reached target block errors
      1.0 | 1.2124e-01 | 9.8365e-01 |     1258839 |    10383360 |         2046 |        2080 |         1.0 |reached target block errors
      3.0 | 4.9291e-02 | 4.5455e-01 |     1082675 |    21964800 |         2000 |        4400 |         2.1 |reached target block errors
      5.0 | 2.8396e-02 | 2.3154e-01 |     1236083 |    43530240 |         2019 |        8720 |         4.2 |reached target block errors
      7.0 | 1.5070e-02 | 1.2531e-01 |     1203635 |    79872000 |         2005 |       16000 |         7.7 |reached target block errors
      9.0 | 7.9158e-03 | 6.6622e-02 |     1188629 |   150159360 |         2004 |       30080 |        14.5 |reached target block errors
     11.0 | 4.5701e-03 | 3.7850e-02 |      912556 |   199680000 |         1514 |       40000 |        19.2 |reached max iterations
     13.0 | 2.1972e-03 | 1.8900e-02 |      438729 |   199680000 |          756 |       40000 |        19.6 |reached max iterations
     15.0 | 1.0344e-03 | 9.1500e-03 |      206543 |   199680000 |          366 |       40000 |        19.8 |reached max iterations
     17.0 | 5.7860e-04 | 5.1500e-03 |      115535 |   199680000 |          206 |       40000 |        19.4 |reached max iterations
EbNo [dB] |        BER |       BLER |  bit errors |    num bits | block errors |  num blocks | runtime [s] |    status
---------------------------------------------------------------------------------------------------------------------------------------
     -3.0 | 1.4100e-01 | 1.0000e+00 |     1407789 |     9984000 |         2000 |        2000 |        16.5 |reached target block errors
     -1.0 | 7.0461e-02 | 9.6202e-01 |      731621 |    10383360 |         2001 |        2080 |         1.5 |reached target block errors
      1.0 | 2.4670e-02 | 2.5404e-01 |      975386 |    39536640 |         2012 |        7920 |         5.7 |reached target block errors
      3.0 | 1.0236e-02 | 9.6202e-02 |     1062810 |   103833600 |         2001 |       20800 |        15.0 |reached target block errors
      5.0 | 3.6196e-03 | 3.5425e-02 |      722763 |   199680000 |         1417 |       40000 |        28.8 |reached max iterations
      7.0 | 9.8593e-04 | 1.0200e-02 |      196871 |   199680000 |          408 |       40000 |        30.1 |reached max iterations
      9.0 | 2.7429e-04 | 2.6250e-03 |       54770 |   199680000 |          105 |       40000 |        29.0 |reached max iterations
     11.0 | 6.0136e-05 | 6.7500e-04 |       12008 |   199680000 |           27 |       40000 |        28.9 |reached max iterations
     13.0 | 1.8209e-05 | 3.2500e-04 |        3636 |   199680000 |           13 |       40000 |        28.8 |reached max iterations
     15.0 | 1.6301e-05 | 1.0000e-04 |        3255 |   199680000 |            4 |       40000 |        29.3 |reached max iterations
     17.0 | 1.1944e-05 | 1.0000e-04 |        2385 |   199680000 |            4 |       40000 |        29.5 |reached max iterations
EbNo [dB] |        BER |       BLER |  bit errors |    num bits | block errors |  num blocks | runtime [s] |    status
---------------------------------------------------------------------------------------------------------------------------------------
     -3.0 | 2.3820e-01 | 1.0000e+00 |     2378198 |     9984000 |         2000 |        2000 |        18.0 |reached target block errors
     -1.0 | 1.8773e-01 | 1.0000e+00 |     1874250 |     9984000 |         2000 |        2000 |         1.5 |reached target block errors
      1.0 | 1.3566e-01 | 9.9856e-01 |     1408647 |    10383360 |         2077 |        2080 |         1.5 |reached target block errors
      3.0 | 6.4347e-02 | 7.6439e-01 |      848025 |    13178880 |         2018 |        2640 |         1.9 |reached target block errors
      5.0 | 2.7035e-02 | 2.3388e-01 |     1155250 |    42731520 |         2002 |        8560 |         6.3 |reached target block errors
      7.0 | 1.2306e-02 | 1.0230e-01 |     1204016 |    97843200 |         2005 |       19600 |        14.5 |reached target block errors
      9.0 | 4.8714e-03 | 4.2725e-02 |      972724 |   199680000 |         1709 |       40000 |        29.6 |reached max iterations
     11.0 | 1.7650e-03 | 1.5150e-02 |      352435 |   199680000 |          606 |       40000 |        31.3 |reached max iterations
     13.0 | 4.8696e-04 | 4.1250e-03 |       97236 |   199680000 |          165 |       40000 |        29.6 |reached max iterations
     15.0 | 1.7679e-04 | 1.3250e-03 |       35301 |   199680000 |           53 |       40000 |        29.7 |reached max iterations
     17.0 | 4.9955e-05 | 4.2500e-04 |        9975 |   199680000 |           17 |       40000 |        30.1 |reached max iterations
[14]:
ber_plot(show_ber=False)
../../../../build/doctrees/nbsphinx/phy_tutorials_notebooks_Link_Level_Simulations_with_RT_31_0.png

previous

Channel Models from Datasets

next

API Documentation

On this page
  • Background Information
  • Imports
  • Setting up the Ray Tracer
  • Creating a CIR Dataset
  • PUSCH Link-Level Simulations

This Page

  • Show Source

© Copyright 2021-2026 NVIDIA CORPORATION.

Created using Sphinx 9.1.0.

Built with the PyData Sphinx Theme 0.16.1.