umi_scenario.py (7899B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 """3GPP TR39.801 urban microcell (UMi) channel model.""" 6 7 import tensorflow as tf 8 9 from sionna import SPEED_OF_LIGHT 10 from sionna.utils import log10 11 from . import SystemLevelScenario 12 13 14 class UMiScenario(SystemLevelScenario): 15 r""" 16 3GPP TR 38.901 urban microcell (UMi) channel model scenario. 17 18 Parameters 19 ----------- 20 carrier_frequency : float 21 Carrier frequency [Hz] 22 23 o2i_model : str 24 Outdoor to indoor (O2I) pathloss model, used for indoor UTs. 25 Either "low" or "high" (see section 7.4.3 from 38.901 specification) 26 27 ut_array : PanelArray 28 Panel array configuration used by UTs 29 30 bs_array : PanelArray 31 Panel array configuration used by BSs 32 33 direction : str 34 Link direction. Either "uplink" or "downlink" 35 36 enable_pathloss : bool 37 If set to `True`, apply pathloss. Otherwise, does not. Defaults to True. 38 39 enable_shadow_fading : bool 40 If set to `True`, apply shadow fading. Otherwise, does not. 41 Defaults to True. 42 43 dtype : tf.DType 44 Defines the datatype for internal calculations and the output 45 dtype. Defaults to `tf.complex64`. 46 """ 47 48 ######################################### 49 # Public methods and properties 50 ######################################### 51 52 def clip_carrier_frequency_lsp(self, fc): 53 r"""Clip the carrier frequency ``fc`` in GHz for LSP calculation 54 55 Input 56 ----- 57 fc : float 58 Carrier frequency [GHz] 59 60 Output 61 ------- 62 : float 63 Clipped carrier frequency, that should be used for LSp computation 64 """ 65 if fc < 2.: 66 fc = tf.cast(2., self._dtype.real_dtype) 67 return fc 68 69 @property 70 def min_2d_in(self): 71 r"""Minimum indoor 2D distance for indoor UTs [m]""" 72 return tf.constant(0.0, self._dtype.real_dtype) 73 74 @property 75 def max_2d_in(self): 76 r"""Maximum indoor 2D distance for indoor UTs [m]""" 77 return tf.constant(25.0, self._dtype.real_dtype) 78 79 @property 80 def los_probability(self): 81 r"""Probability of each UT to be LoS. Used to randomly generate LoS 82 status of outdoor UTs. 83 84 Computed following section 7.4.2 of TR 38.901. 85 86 [batch size, num_ut]""" 87 88 distance_2d_out = self._distance_2d_out 89 los_probability = ( (18./distance_2d_out) + 90 tf.exp(-distance_2d_out/36.0)*(1.-18./distance_2d_out) ) 91 los_probability = tf.where(tf.math.less(distance_2d_out, 18.0), 92 tf.constant(1.0, self._dtype.real_dtype), los_probability) 93 return los_probability 94 95 @property 96 def rays_per_cluster(self): 97 r"""Number of rays per cluster""" 98 return tf.constant(20, tf.int32) 99 100 @property 101 def los_parameter_filepath(self): 102 r""" Path of the configuration file for LoS scenario""" 103 return 'UMi_LoS.json' 104 105 @property 106 def nlos_parameter_filepath(self): 107 r""" Path of the configuration file for NLoS scenario""" 108 return'UMi_NLoS.json' 109 110 @property 111 def o2i_parameter_filepath(self): 112 r""" Path of the configuration file for indoor scenario""" 113 return 'UMi_O2I.json' 114 115 ######################### 116 # Utility methods 117 ######################### 118 119 def _compute_lsp_log_mean_std(self): 120 r"""Computes the mean and standard deviations of LSPs in log-domain""" 121 122 batch_size = self.batch_size 123 num_bs = self.num_bs 124 num_ut = self.num_ut 125 distance_2d = self.distance_2d 126 h_bs = self.h_bs 127 h_bs = tf.expand_dims(h_bs, axis=2) # For broadcasting 128 h_ut = self.h_ut 129 h_ut = tf.expand_dims(h_ut, axis=1) # For broadcasting 130 131 ## Mean 132 # DS 133 log_mean_ds = self.get_param("muDS") 134 # ASD 135 log_mean_asd = self.get_param("muASD") 136 # ASA 137 log_mean_asa = self.get_param("muASA") 138 # SF. Has zero-mean. 139 log_mean_sf = tf.zeros([batch_size, num_bs, num_ut], 140 self._dtype.real_dtype) 141 # K. Given in dB in the 3GPP tables, hence the division by 10 142 log_mean_k = self.get_param("muK")/10.0 143 # ZSA 144 log_mean_zsa = self.get_param("muZSA") 145 # ZSD 146 log_mean_zsd_los = tf.math.maximum( 147 tf.constant(-0.21, self._dtype.real_dtype), 148 -14.8*(distance_2d/1000.0) + 0.01*tf.abs(h_ut-h_bs)+0.83) 149 log_mean_zsd_nlos = tf.math.maximum( 150 tf.constant(-0.5, self._dtype.real_dtype), 151 -3.1*(distance_2d/1000.0) + 0.01*tf.maximum(h_ut-h_bs,0.0)+0.2) 152 log_mean_zsd = tf.where(self.los, log_mean_zsd_los, log_mean_zsd_nlos) 153 154 lsp_log_mean = tf.stack([log_mean_ds, 155 log_mean_asd, 156 log_mean_asa, 157 log_mean_sf, 158 log_mean_k, 159 log_mean_zsa, 160 log_mean_zsd], axis=3) 161 162 ## STD 163 # DS 164 log_std_ds = self.get_param("sigmaDS") 165 # ASD 166 log_std_asd = self.get_param("sigmaASD") 167 # ASA 168 log_std_asa = self.get_param("sigmaASA") 169 # SF. Given in dB in the 3GPP tables, hence the division by 10 170 # O2I and NLoS cases just require the use of a predefined value 171 log_std_sf = self.get_param("sigmaSF")/10.0 172 # K. Given in dB in the 3GPP tables, hence the division by 10. 173 log_std_k = self.get_param("sigmaK")/10.0 174 # ZSA 175 log_std_zsa = self.get_param("sigmaZSA") 176 # ZSD 177 log_std_zsd = self.get_param("sigmaZSD") 178 179 lsp_log_std = tf.stack([log_std_ds, 180 log_std_asd, 181 log_std_asa, 182 log_std_sf, 183 log_std_k, 184 log_std_zsa, 185 log_std_zsd], axis=3) 186 187 self._lsp_log_mean = lsp_log_mean 188 self._lsp_log_std = lsp_log_std 189 190 # ZOD offset 191 zod_offset = -tf.math.pow(tf.constant(10.0, self._dtype.real_dtype), 192 -1.5*log10(tf.maximum(tf.constant(10.0, self._dtype.real_dtype), 193 distance_2d))+3.3) 194 zod_offset = tf.where(self.los,tf.constant(0.0, self._dtype.real_dtype), 195 zod_offset) 196 self._zod_offset = zod_offset 197 198 def _compute_pathloss_basic(self): 199 r"""Computes the basic component of the pathloss [dB]""" 200 201 distance_2d = self.distance_2d 202 distance_3d = self.distance_3d 203 fc = self.carrier_frequency # Carrier frequency (Hz) 204 h_bs = self.h_bs 205 h_bs = tf.expand_dims(h_bs, axis=2) # For broadcasting 206 h_ut = self.h_ut 207 h_ut = tf.expand_dims(h_ut, axis=1) # For broadcasting 208 209 # Beak point distance 210 h_e = 1.0 211 h_bs_prime = h_bs - h_e 212 h_ut_prime = h_ut - h_e 213 distance_breakpoint = 4*h_bs_prime*h_ut_prime*fc/SPEED_OF_LIGHT 214 215 ## Basic path loss for LoS 216 217 pl_1 = 32.4 + 21.0*log10(distance_3d) + 20.0*log10(fc/1e9) 218 pl_2 = (32.4 + 40.0*log10(distance_3d) + 20.0*log10(fc/1e9) 219 - 9.5*log10(tf.square(distance_breakpoint)+tf.square(h_bs-h_ut))) 220 pl_los = tf.where(tf.math.less(distance_2d, distance_breakpoint), 221 pl_1, pl_2) 222 223 ## Basic pathloss for NLoS and O2I 224 225 pl_3 = (35.3*log10(distance_3d) + 22.4 + 21.3*log10(fc/1e9) 226 - 0.3*(h_ut-1.5)) 227 pl_nlos = tf.math.maximum(pl_los, pl_3) 228 229 ## Set the basic pathloss according to UT state 230 231 # Expand to allow broadcasting with the BS dimension 232 # LoS 233 pl_b = tf.where(self.los, pl_los, pl_nlos) 234 235 self._pl_b = pl_b