Sionna SYS meets Sionna RT
In this notebook, you will learn how to
Setup a Sionna RT scene;
Generate channel impulse responses;
Schedule users, allocate power, and select the modulation and coding scheme (MCS) via Sionna SYS.
This is an advanced notebook. We recommend first exploring the tutorials onphysical layer abstraction,link adaptation, andscheduling.
If you are new to Sionna RT, consider exploring therelated tutorials first.
Imports
We first import Sionna and the relevant external libraries:
[16]:
importosos.environ['TF_CPP_MIN_LOG_LEVEL']='3'ifos.getenv("CUDA_VISIBLE_DEVICES")isNone:gpu_num=0# Use "" to use the CPUifgpu_num!="":print(f'\nUsing GPU{gpu_num}\n')else:print('\nUsing CPU\n')os.environ["CUDA_VISIBLE_DEVICES"]=f"{gpu_num}"# Import Sionnatry:importsionna.phyimportsionna.sysexceptImportErrorase:importsysif'google.colab'insys.modules:# Install Sionna in Google Colabprint("Installing Sionna and restarting the runtime. Please run the cell again.")os.system("pip install sionna")os.kill(os.getpid(),5)else:raisee# Configure the notebook to use only a single GPU and allocate only as much memory as needed# For more details, see https://www.tensorflow.org/guide/gpuimporttensorflowastftf.get_logger().setLevel('ERROR')gpus=tf.config.list_physical_devices('GPU')ifgpus:try:tf.config.experimental.set_memory_growth(gpus[0],True)exceptRuntimeErrorase:print(e)
[17]:
# Additional external librariesimportmatplotlib.pyplotaspltfrommatplotlib.colorsimportListedColormapimportnumpyasnp# Sionna componentsfromsionna.rtimportload_scene,Transmitter,Receiver,PlanarArray, \RadioMapSolver,PathSolver,subcarrier_frequencies,Camerafromsionna.phyimportconfigfromsionna.phy.mimoimportStreamManagementfromsionna.phy.ofdmimportResourceGrid,RZFPrecodedChannel,LMMSEPostEqualizationSINRfromsionna.phy.constantsimportBOLTZMANN_CONSTANTfromsionna.phy.nr.utilsimportdecode_mcs_indexfromsionna.phy.utilsimportlog2,dbm_to_watt,lin_to_dbfromsionna.sysimportPHYAbstraction,OuterLoopLinkAdaptation, \PFSchedulerSUMIMO,downlink_fair_power_controlfromsionna.sys.utilsimportspread_across_subcarriers# Internal computational precisionconfig.precision='single'# 'single' or 'double'# Set random seed for reproducibilityconfig.seed=48# Toggle to False to use the preview widget# instead of rendering for scene visualizationno_preview=True
Simulation parameters
In this notebook we consider that communication is in the downlink direction from la single base station to multiple users.
[18]:
# Number of simulated slotsnum_slots=200# Time/frequency resource gridcarrier_frequency=3.5e9num_subcarriers=128subcarrier_spacing=30e3num_ofdm_symbols=12# Start/end 3D position of the users# You can try and change these values to see how the system behavesut_pos_start=np.array([[-25,0,1.5],# user 1[24,55,1.5],# user 2[88,0,1.5]])# user 3ut_pos_end=np.array([[-25,30,1.5],# user 1[24,15,1.5],# user 2[65,0,1.5]])# user 3# Base station position and look-at directionbs_pos=np.array([32.5,10.5,23])bs_look_at=np.array([22,-8,0])# Number of users and base stationsnum_bs=1num_ut=ut_pos_start.shape[0]# Environment temperaturetemperature=294# [K]# BLER target valuebler_target=.1# in [0; 1]# MCS table indexmcs_table_index=1# Base station transmit power# Low power is sufficient here thanks to lack of interferencebs_power_dbm=10# [dBm]bs_power_watt=dbm_to_watt(bs_power_dbm)# [W]# Number of antennas at the transmitter and receivernum_bs_ant=num_utnum_ut_ant=1
[19]:
# Number of streams per user and base stationnum_streams_per_ut=num_ut_antnum_streams_per_bs=num_ut_ant*num_ut# Noise power per subcarrierno=BOLTZMANN_CONSTANT*temperature*subcarrier_spacing# Stream management: Rx to Tx association# Since there is only a single BS, all UTs are connected to itrx_tx_association=np.ones([num_ut,num_bs])stream_management=StreamManagement(rx_tx_association,num_streams_per_bs)# OFDM resource gridresource_grid=ResourceGrid(num_ofdm_symbols=num_ofdm_symbols,fft_size=num_subcarriers,subcarrier_spacing=subcarrier_spacing,num_tx=num_ut,num_streams_per_tx=num_streams_per_ut)# Subcarrier frequenciesfrequencies=subcarrier_frequencies(num_subcarriers=num_subcarriers,subcarrier_spacing=subcarrier_spacing)
Scene creation
As the second step, we load the desired scene from Sionna RT. We then need to configure it and add the base station and users along straight trajectories.
Rather than moving the users in an iterative fashion, as shown in theTutorial on Mobility, we will place receivers at all future positions along the desired trajectories at once and compute the channel frequency responses (CFRs) in advance. During evaluation, the appropriate CFRs will be selected iteratively based on user positions.
[20]:
# Configure Sionna RTp_solver=PathSolver()# Load the scenescene=load_scene(sionna.rt.scene.simple_street_canyon)# Set the scene parametersscene.frequency=carrier_frequencyscene.bandwidth=num_subcarriers*subcarrier_spacingscene.tx_array=PlanarArray(num_rows=num_bs_ant,num_cols=1,pattern="tr38901",polarization='V')scene.rx_array=PlanarArray(num_rows=1,num_cols=num_ut_ant,pattern="dipole",polarization='V')# Add base station to the scenescene.add(Transmitter(f"bs0",position=bs_pos,look_at=bs_look_at,power_dbm=bs_power_dbm,display_radius=3))# Emulate moving users by placing multiple receivers along a straight linestep=(ut_pos_end-ut_pos_start)/(num_slots-1)# We add all users at all future positions at onceforslotinrange(num_slots):pos_curr=ut_pos_start+slot*step# Add usersforutinrange(num_ut):scene.add(Receiver(f"ut{ut}_slot{slot}",position=pos_curr[ut,:],display_radius=1,color=[0,0,0]))
Channel generation via Sionna RT
[21]:
# Path solver: Compute propagation paths between the antennas of all# transmitters and receivers in the scene using ray tracingpaths=p_solver(scene,max_depth=8,refraction=False)# Transform to channel frequency response (CFR)# [num_steps*num_ut, num_rx_ant, num_tx, num_tx_ant, num_ofdm_symbols, num_subcarriers]h_freq=paths.cfr(frequencies=frequencies,sampling_frequency=1/resource_grid.ofdm_symbol_duration,num_time_steps=resource_grid.num_ofdm_symbols,out_type="tf")# [num_steps*num_ut, 1, num_ut_ant, num_bs, num_bs_ant, num_ofdm_symbols, num_subcarriers]h_freq=tf.expand_dims(h_freq,axis=1)# [num_steps, num_ut, num_ut_ant, num_bs, num_bs_ant, num_ofdm_symbols, num_subcarriers]h_freq=tf.reshape(h_freq,[num_slots,num_ut]+h_freq.shape[2:])
We next compute the radio map and visualize the corresponding SINR, along with the user and base station positions.
[22]:
# Compute the Radio Map for the scene and visualize itrm_solver=RadioMapSolver()rm=rm_solver(scene,max_depth=8,refraction=False,cell_size=(1,1),samples_per_tx=10000000)ifno_preview:cam=Camera(position=[-0,0,250],orientation=np.array([0,np.pi/2,-np.pi/2]))scene.render(camera=cam,radio_map=rm,rm_metric="sinr",rm_show_color_bar=True)else:scene.preview(radio_map=rm,rm_metric="sinr")

System-level simulation
After creating the scene and generating the channel for each user across slots, we can now perform system-level simulations with Sionna SYS.
We first instantiate the main classes:
PHYAbstraction, to bypass the physical layer computations;
OuterLoopLinkAdaptation, to select the modulation and coding scheme (MCS);
PFSchedulerSUMIM, to schedule users across the OFDM resource grid in a single-user MIMO system.
The downlink transmission power is distributed fairly across users via the functiondownlink_fair_power_control.
[23]:
# Initialize the PHYAbstraction classphy_abs=PHYAbstraction()# Instantiate an OLLA objectolla=OuterLoopLinkAdaptation(phy_abs,num_ut=num_ut,bler_target=bler_target,batch_size=[num_bs])# Instantiate a Schedulerscheduler=PFSchedulerSUMIMO(num_ut,num_subcarriers,num_ofdm_symbols,batch_size=[num_bs],num_streams_per_ut=num_streams_per_ut,beta=.9)
We will next define the operations performed at each slot, namely:
Scheduling users across the resource grid;
Allocating transmit power to each user;
Computing the SINR;
Selecting MCS via link adaptation;
Generating decoded bits and HARQ feedback via physical layer abstraction.
Remark: For simplicity, we assume perfect channel knowledge in
Precoder and equalizer computation for SINR calculation;
Achievable rate estimation for scheduling purposes;
Channel quality feedback for link adaptation.
[24]:
defestimate_achievable_rate(channel_gain,no):""" Estimate the achievable rate via Shannon formula """# [num_ut, num_ut_ant, num_bs, num_bs_ant, num_ofdm_symbols, num_subcarriers]rate_achievable_est=log2(tf.cast(1,tf.float32)+channel_gain/no)# [num_ut, num_bs, num_ofdm_symbols, num_subcarriers]rate_achievable_est=tf.reduce_mean(rate_achievable_est,axis=[-3,-5])# [num_bs, num_ofdm_symbols, num_subcarriers, num_ut]rate_achievable_est=tf.transpose(rate_achievable_est,[1,2,3,0])returnrate_achievable_est
[25]:
# To speed-up the computations, we will use JIT compilation.@tf.function(jit_compile=True)defstep(h,harq_feedback,sinr_eff_feedback,num_decoded_bits):""" Perform system-level operations at a single slot: - Scheduling - Power control - SINR computation - Link adaptation - PHY abstraction Perfect channel knowledge at both transmitter and receivers is assumed. """# Compute channel gain# [num_ut, num_ut_ant, num_bs, num_bs_ant, num_ofdm_symbols, num_subcarriers]channel_gain=tf.cast(tf.abs(h)**2,tf.float32)# Estimate achievable rate via Shannon formula# [num_bs, num_ofdm_symbols, num_subcarriers, num_ut]rate_achievable_est=estimate_achievable_rate(channel_gain,no)# --------- ## Scheduler ## --------- ## Determine which stream of which user is scheduled on which RE# [num_bs, num_ofdm_symbols, num_subcarriers, num_ut, num_streams_per_ut]is_scheduled=scheduler(num_decoded_bits,rate_achievable_est)# Determine the index of the scheduled user for every RE# [num_bs, num_ofdm_symbols, num_subcarriers]ut_scheduled=tf.argmax(tf.reduce_sum(tf.cast(is_scheduled,tf.int32),axis=-1),axis=-1)# Compute the number of allocated REs per user# [num_bs, num_ut]num_allocated_re=tf.reduce_sum(tf.cast(is_scheduled,tf.int32),axis=[-4,-3,-1])# Compute the average pathloss per user# [num_ut, num_bs]pathloss_per_ut=tf.reduce_mean(1/channel_gain,axis=[1,3,4,5])# [num_bs, num_ut]pathloss_per_ut=tf.transpose(pathloss_per_ut,[1,0])# ------------- ## Power control ## ------------- ## Allocate power to each user in fair manner# [num_bs, num_ut]tx_power_per_ut,_=downlink_fair_power_control(pathloss_per_ut,no,num_allocated_re,bs_max_power_dbm=bs_power_dbm,# in [0;1]; Minimum ratio of power for each userguaranteed_power_ratio=.5,fairness=0)# Fairness parameter>=0. If 0, sum throughput across users is maximized# Spread power uniformly across allocated subcarriers and streams# [num_bs, num_tx_per_bs, num_streams_per_tx, num_ofdm_sym, num_subcarriers]tx_power=spread_across_subcarriers(tf.expand_dims(tx_power_per_ut,axis=-2),is_scheduled,num_tx=num_bs)# ---------------- ## SINR computation ## ---------------- ## Effective channel upon regularized zero-forcing precodingprecoded_channel=RZFPrecodedChannel(resource_grid=resource_grid,stream_management=stream_management)h_eff=precoded_channel(h[tf.newaxis,...],tx_power=tx_power,alpha=no)# LMMSE post-equalization SINR computationlmmse_posteq_sinr=LMMSEPostEqualizationSINR(resource_grid=resource_grid,stream_management=stream_management)# [batch_size, num_ofdm_symbols, num_subcarriers, num_ut, num_streams_per_ut]sinr=lmmse_posteq_sinr(h_eff,no=no,interference_whitening=True)# --------------- ## Link adaptation ## --------------- ## [num_bs, num_ut]mcs_index=olla(num_allocated_re=num_allocated_re,sinr_eff=sinr_eff_feedback,mcs_table_index=mcs_table_index,mcs_category=1,# downlinkharq_feedback=harq_feedback)# --------------- ## PHY abstraction ## --------------- ## [num_bs, num_ut]num_decoded_bits,harq_feedback,sinr_eff_true,*_=phy_abs(mcs_index,sinr=sinr,mcs_table_index=mcs_table_index,mcs_category=1)# downlink)sinr_eff_db_true=lin_to_db(sinr_eff_true)# SINR feedback for OLLAsinr_eff_feedback=tf.where(num_allocated_re>0,sinr_eff_true,0)# Spectral efficiency# [num_bs, num_ut]mod_order,coderate=decode_mcs_index(mcs_index,table_index=mcs_table_index,is_pusch=False)se_la=tf.where(harq_feedback==1,tf.cast(mod_order,coderate.dtype)*coderate,tf.cast(0,tf.float32))# Shannon capacityse_shannon=log2(1+sinr_eff_true)returnharq_feedback,sinr_eff_feedback,num_decoded_bits, \mcs_index,se_la,se_shannon,sinr_eff_db_true, \scheduler.pf_metric,ut_scheduled
We are now ready to simulate the system across all slots!
[26]:
# Initialize historyharq_feedback_hist=np.full([num_slots,num_bs,num_ut],np.nan)se_la_hist=np.full([num_slots,num_bs,num_ut],np.nan)se_shannon_hist=np.full([num_slots,num_bs,num_ut],np.nan)sinr_eff_db_hist=np.full([num_slots,num_bs,num_ut],np.nan)is_scheduled_hist=np.full([num_slots,num_bs,num_ofdm_symbols,num_subcarriers],np.nan)# Initialize HARQ feedback to -1 (missing)harq_feedback=-tf.ones([num_bs,num_ut],dtype=tf.int32)# Initialize SINR feedbacksinr_eff_feedback=tf.zeros([num_bs,num_ut],dtype=tf.float32)# Initialize n. decoded bitsnum_decoded_bits=tf.zeros([num_bs,num_ut],dtype=tf.int32)foriiinrange(num_slots):# [num_ut, num_ut_ant, num_bs, num_bs_ant, num_ofdm_symbols, num_subcarriers]h=h_freq[ii,...]harq_feedback,sinr_eff_feedback,num_decoded_bits,mcs_index, \se_la,se_shannon,sinr_eff_db_true, \pf_metric,ut_scheduled= \step(h,harq_feedback,sinr_eff_feedback,num_decoded_bits)# Record historyharq_feedback_hist[ii,:]=harq_feedback.numpy()se_la_hist[ii,:]=se_la.numpy()se_shannon_hist[ii,:]=se_shannon.numpy()sinr_eff_db_hist[ii,:]=sinr_eff_db_true.numpy()is_scheduled_hist[ii,...]=ut_scheduled.numpy()# Mask metrics when user is not schedulednot_scheduled=harq_feedback_hist==-1se_la_hist[not_scheduled]=np.nanse_shannon_hist[not_scheduled]=np.nansinr_eff_db_hist[not_scheduled]=np.nanharq_feedback_hist[not_scheduled]=np.nan
Finally, we visualize the achieved performance, in terms of achieved spectral efficiency and transport block error rate (TBLER).
[27]:
bs=0# Create a color map or list of colorscolors=plt.cm.Set1(np.linspace(0,1,num_ut))linestyle=' 'label='OLLA'fig,axs=plt.subplots(3,num_ut,sharex='col',sharey='row',figsize=(4*num_ut,9))ifnum_ut==1:axs=axs.reshape(3,1)foraxinaxs.flat:ax.yaxis.set_tick_params(labelleft=True)ax.xaxis.set_tick_params(labelbottom=True)ax.grid()forutinrange(num_ut):axs[0,ut].plot(sinr_eff_db_hist[:,bs,ut],color=colors[ut],marker='o')axs[0,ut].set_ylabel('Effective SINR [dB]')axs[0,ut].set_xlabel('Time step')axs[0,ut].set_title(f'User{ut+1}')forutinrange(num_ut):axs[1,ut].plot(se_la_hist[:,bs,ut],color=colors[ut],marker='o',linestyle=linestyle,label=label)axs[1,ut].plot(se_shannon_hist[:,bs,ut],'--k',marker='x',label='Shannon capacity')axs[1,ut].set_ylabel('Spectral efficiency [bps/Hz]')axs[1,ut].set_xlabel('Time step')axs[1,ut].legend()axs[2,ut].plot([0,num_slots-1],[bler_target]*2,'--k',label='BLER target')ind_ok=np.isnan(harq_feedback_hist[:,bs,ut])==Falsebler=1- \np.cumsum(harq_feedback_hist[ind_ok,bs,ut])/np.arange(1,sum(ind_ok)+1)bler_vec=np.full([num_slots],np.nan)bler_vec[ind_ok]=bleraxs[2,ut].plot(bler_vec,color=colors[ut],marker='o',linestyle=linestyle,label=label)axs[2,ut].set_ylabel('Achieved TBLER')axs[2,ut].set_xlabel('Time step')axs[2,ut].legend()fig.tight_layout()plt.show()

[28]:
cmap=plt.get_cmap('Set1',num_ut)is_scheduled_hist=np.reshape(is_scheduled_hist,[num_slots*num_ofdm_symbols,num_subcarriers])# Configure plotplt.figure(figsize=(12,5))plt.imshow(is_scheduled_hist.T,cmap=cmap,aspect='auto')# Add colorbar with custom ticks and labelscbar=plt.colorbar(ticks=[0,1,2])cbar.ax.set_yticklabels(['User 1','User 2','User 3'])cbar.set_label('User Allocation',fontsize=12)plt.gca().invert_yaxis()plt.xlabel('Time (OFDM symbols)')plt.ylabel('Subcarriers')plt.show()

Conclusions
Sionna SYS andSionna RT are fully compatible: for a given scene, Sionna RT generates the channel impulse response which Sionna SYS can then use to perform system level simulations.