sionna icon indicating copy to clipboard operation
sionna copied to clipboard

Exceptionally high SNRs for SIONNA 3GPP UMa OFDM channels

Open mickwubs97 opened this issue 10 months ago • 1 comments

I got what I think are exceptionally high SNRs with the SIONNA UMa channel model when simulating OFDM channels. I am expecting 20 to -3 dB of SNRs across 1000 meters. Is it because the TOTAL transmission power was allocated to 76 OFDM subcarriers? Or is it normal (i.e., as expected for a 3GPP UMa)? I've been searching for references for SNR vs Distance curvatures under 3GPP UMa/UMi/RMa models but haven't found any.

Image

import sionna as sn
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from matplotlib import cm

# Define the number of UT and BS antennas
NUM_UT = 1
NUM_BS = 1
NUM_UT_ANT = 1
NUM_BS_ANT = 1

# The number of transmitted streams is equal to the number of UT antennas
# in both uplink and downlink
NUM_STREAMS_PER_TX = NUM_UT_ANT

# In this notebook, as we have only a single transmitter and receiver,
# the RX-TX association matrix is simply:
RX_TX_ASSOCIATION = np.array([[1]])

# Instantiate a StreamManagement object
# This determines which data streams are determined for which receiver.
# In this simple setup, this is fairly easy. However, it can get more involved
# for simulations with many transmitters and receivers.
STREAM_MANAGEMENT = sn.mimo.StreamManagement(RX_TX_ASSOCIATION, NUM_STREAMS_PER_TX)

# Setup resource grid and channel model
sm = sn.mimo.StreamManagement(np.array([[1]]), 1)
RESOURCE_GRID = sn.ofdm.ResourceGrid(num_ofdm_symbols=14,
                                     fft_size=76,
                                     subcarrier_spacing=30e3,
                                     num_tx=NUM_UT,
                                     num_streams_per_tx=NUM_STREAMS_PER_TX,
                                     cyclic_prefix_length=6,
                                     pilot_pattern="kronecker",
                                     pilot_ofdm_symbol_indices=[2, 11])
# RESOURCE_GRID.show()

CARRIER_FREQUENCY = 2.6e9  # Carrier frequency in Hz.
# This is needed here to define the antenna element spacing.

UT_ARRAY = sn.channel.tr38901.Antenna(polarization="single",
                                      polarization_type="V",
                                      antenna_pattern="38.901",
                                      carrier_frequency=CARRIER_FREQUENCY)
UT_ARRAY.show()

BS_ARRAY = sn.channel.tr38901.AntennaArray(num_rows=1,
                                           num_cols=NUM_BS_ANT,
                                           polarization="single",
                                           polarization_type="V",
                                           antenna_pattern="38.901",  # Try 'omni'
                                           carrier_frequency=CARRIER_FREQUENCY)
BS_ARRAY.show()
BATCH_SIZE = 200
distance = np.linspace(1, 1000, 250)
bs_loc = tf.zeros(shape=(BATCH_SIZE, NUM_BS, 3), dtype=tf.float32)
ut_locs = np.zeros(shape=(BATCH_SIZE, len(distance), 3))
for b in range(BATCH_SIZE):
    ut_locs[b, :, 1] = distance
ut_locs = tf.convert_to_tensor(ut_locs, dtype=tf.float32)
bs_ori = tf.zeros(shape=(BATCH_SIZE, NUM_BS, 3), dtype=tf.float32)
ut_ori = np.zeros(shape=(BATCH_SIZE, NUM_UT, 3))
ut_ori[:, :, 0] = np.pi
ut_ori = tf.convert_to_tensor(ut_ori, dtype=tf.float32)
ut_vol = tf.zeros(shape=(BATCH_SIZE, NUM_UT, 3), dtype=tf.float32)
in_state = tf.zeros(shape=(BATCH_SIZE, NUM_UT), dtype=tf.bool)

# power_tx_dbm = -20  # Downlink Power Spectrum Density (PSD)
# power_tx = 10**(power_tx_dbm/10)  # Tx power
# power_tx_sub = power_tx/RESOURCE_GRID.fft_size  # Tx power per subcarrier
# power_tx = (10 ** (power_tx_dbm / 10))*RESOURCE_GRID.subcarrier_spacing  # Tx power per symbol
power_tx = 1
k = 1.38*10**(-23)  # Boltzmann's constant
T = 293  # Absolute temperature in Kelvin
n0_sub = k*T*RESOURCE_GRID.subcarrier_spacing  # Noise power per subcarrier

SNRs = np.zeros(shape=(len(distance), RESOURCE_GRID.fft_size))

UMa = sn.channel.tr38901.UMa(carrier_frequency=3.5e9,
                             o2i_model='low',
                             ut_array=UT_ARRAY,
                             bs_array=BS_ARRAY,
                             direction='downlink',
                             enable_pathloss=True)

# Configure topology for channel model
UMa.set_topology(ut_locs, bs_loc, ut_ori, bs_ori, ut_vol, in_state=in_state, los=True)
# Generating OFDM channel
generate_channel = sn.channel.GenerateOFDMChannel(channel_model=UMa, resource_grid=RESOURCE_GRID)
# Generate a batch of channel responses
h_list = generate_channel()
check = tf.reduce_mean(tf.square(tf.abs(h_list)), axis=[0, 5])
# Apply OFDM channels
apply_channel = sn.channel.ApplyOFDMChannel(add_awgn=False)

for d in range(len(distance)):
    # Select a batch of channel responses
    h = h_list[:, d:d+1, :, :, :, :, :]
    # Generate channel inputs
    shape_in = (BATCH_SIZE, NUM_BS, NUM_BS_ANT, RESOURCE_GRID.num_ofdm_symbols, RESOURCE_GRID.fft_size)
    x_real = tf.random.uniform(shape_in, minval=0, maxval=2, dtype=tf.int32)  # Random real part
    x_real = tf.where(x_real == 0, -1, 1)
    x_real = np.sqrt(power_tx/2)*tf.cast(x_real, dtype=tf.float32)
    x_imag = tf.random.uniform(shape_in, minval=0, maxval=2, dtype=tf.int32)  # Random imaginary part
    x_imag = tf.where(x_imag == 0, -1, 1)
    x_imag = np.sqrt(power_tx/2)*tf.cast(x_imag, dtype=tf.float32)
    x = tf.complex(x_real, x_imag)
    # Generate noise variances
    shape_out = (BATCH_SIZE, NUM_UT, NUM_UT_ANT, RESOURCE_GRID.num_ofdm_symbols, RESOURCE_GRID.fft_size)
    no = n0_sub*np.ones(shape=shape_out, dtype=np.float32)
    y = apply_channel([x, h])
    no, y = np.squeeze(no), tf.squeeze(y).numpy()
    y_p = np.square(np.abs(y))
    snr_ins = 10*np.log10(np.divide(y_p, no))
    snr_ero = np.mean(snr_ins, axis=0)
    snr_ero = np.mean(snr_ero, axis=0)
    SNRs[d, :] = snr_ero
# Visualize results
X, Y = np.meshgrid(range(RESOURCE_GRID.fft_size), distance)
# Plot the surface
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
surf = ax.plot_surface(X, Y, SNRs, vmin=SNRs.min(), cmap=cm.viridis)
fig.colorbar(surf, shrink=0.5, aspect=5)
ax.set_xlabel('frequency')
ax.set_ylabel('distance')
ax.set_zlabel('SNR (dB)')
# plt.show()

plt.figure()
plt.title("SNRs vs Distance")
for cid in range(RESOURCE_GRID.fft_size):
    plt.plot(distance, SNRs[:, cid])
plt.xlabel(r"distance (m)")
plt.ylabel(r"SNR (dB)")
plt.grid()
plt.show()

Could you please tell me where I did wrong in this code? Thank you very much!

mickwubs97 avatar Mar 09 '25 13:03 mickwubs97

The problem may be related to lack of channel normalization. I suggest to try:

# Generating OFDM channel
generate_channel = sn.channel.GenerateOFDMChannel(channel_model=UMa, resource_grid=RESOURCE_GRID, normalize_channel=True)

gc1905 avatar Apr 04 '25 18:04 gc1905

Closing as there seems to be no issue with Sionna. Please re-open with a minimum reproducer in case the issue is still relevant.

SebastianCa avatar Aug 21 '25 09:08 SebastianCa