anomaly-detection-material-parameters-calibration

Sionna param calibration (research proj)
git clone https://git.ea.contact/anomaly-detection-material-parameters-calibration
Log | Files | Refs | README

uma_scenario.py (10183B)


      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 macrocell (UMa) channel model"""
      6 
      7 import tensorflow as tf
      8 
      9 import sionna
     10 from sionna import SPEED_OF_LIGHT
     11 from sionna.utils import log10
     12 from . import SystemLevelScenario
     13 
     14 class UMaScenario(SystemLevelScenario):
     15     r"""
     16     3GPP TR 38.901 urban macrocell (UMa) 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 < 6.:
     66             fc = tf.cast(6., 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         h_ut = self.h_ut
     89         c = tf.math.pow((h_ut-13.)/10., 1.5)
     90         c = tf.where(tf.math.less(h_ut, 13.0),
     91                         tf.constant(0., self._dtype.real_dtype), c)
     92         c = tf.expand_dims(c, axis=1)
     93 
     94         distance_2d_out = self._distance_2d_out
     95         los_probability = ((18.0/distance_2d_out
     96             + tf.math.exp(-distance_2d_out/63.0)*(1.-18./distance_2d_out))
     97             *(1.+c*5./4.*tf.math.pow(distance_2d_out/100., 3)
     98                 *tf.math.exp(-distance_2d_out/150.0)))
     99 
    100         los_probability = tf.where(tf.math.less(distance_2d_out, 18.0),
    101             tf.constant(1.0, self._dtype.real_dtype), los_probability)
    102         return los_probability
    103 
    104     @property
    105     def rays_per_cluster(self):
    106         r"""Number of rays per cluster"""
    107         return tf.constant(20, tf.int32)
    108 
    109     @property
    110     def los_parameter_filepath(self):
    111         r""" Path of the configuration file for LoS scenario"""
    112         return "UMa_LoS.json"
    113 
    114     @property
    115     def nlos_parameter_filepath(self):
    116         r""" Path of the configuration file for NLoS scenario"""
    117         return"UMa_NLoS.json"
    118 
    119     @property
    120     def o2i_parameter_filepath(self):
    121         r""" Path of the configuration file for indoor scenario"""
    122         return "UMa_O2I.json"
    123 
    124     #########################
    125     # Utility methods
    126     #########################
    127 
    128     def _compute_lsp_log_mean_std(self):
    129         r"""Computes the mean and standard deviations of LSPs in log-domain"""
    130 
    131         batch_size = self.batch_size
    132         num_bs = self.num_bs
    133         num_ut = self.num_ut
    134         distance_2d = self.distance_2d
    135         h_bs = self.h_bs
    136         h_bs = tf.expand_dims(h_bs, axis=2) # For broadcasting
    137         h_ut = self.h_ut
    138         h_ut = tf.expand_dims(h_ut, axis=1) # For broadcasting
    139 
    140         ## Mean
    141         # DS
    142         log_mean_ds = self.get_param("muDS")
    143         # ASD
    144         log_mean_asd = self.get_param("muASD")
    145         # ASA
    146         log_mean_asa = self.get_param("muASA")
    147         # SF.  Has zero-mean.
    148         log_mean_sf = tf.zeros([batch_size, num_bs, num_ut],
    149                                 self._dtype.real_dtype)
    150         # K.  Given in dB in the 3GPP tables, hence the division by 10
    151         log_mean_k = self.get_param("muK")/10.0
    152         # ZSA
    153         log_mean_zsa = self.get_param("muZSA")
    154         # ZSD
    155         log_mean_zsd_los = tf.math.maximum(tf.constant(-0.5,
    156                                                     self._dtype.real_dtype),
    157             -2.1*(distance_2d/1000.0) - 0.01*tf.abs(h_ut-1.5)+0.75)
    158         log_mean_zsd_nlos = tf.math.maximum(tf.constant(-0.5,
    159                                                     self._dtype.real_dtype),
    160             -2.1*(distance_2d/1000.0) - 0.01*tf.abs(h_ut-1.5)+0.9)
    161         log_mean_zsd = tf.where(self.los, log_mean_zsd_los, log_mean_zsd_nlos)
    162 
    163         lsp_log_mean = tf.stack([log_mean_ds,
    164                                 log_mean_asd,
    165                                 log_mean_asa,
    166                                 log_mean_sf,
    167                                 log_mean_k,
    168                                 log_mean_zsa,
    169                                 log_mean_zsd], axis=3)
    170 
    171         ## STD
    172         # DS
    173         log_std_ds = self.get_param("sigmaDS")
    174         # ASD
    175         log_std_asd = self.get_param("sigmaASD")
    176         # ASA
    177         log_std_asa = self.get_param("sigmaASA")
    178         # SF. Given in dB in the 3GPP tables, hence the division by 10
    179         # O2I and NLoS cases just require the use of a predefined value
    180         log_std_sf = self.get_param("sigmaSF")/10.0
    181         # K. Given in dB in the 3GPP tables, hence the division by 10.
    182         log_std_k = self.get_param("sigmaK")/10.0
    183         # ZSA
    184         log_std_zsa = self.get_param("sigmaZSA")
    185         # ZSD
    186         log_std_zsd = self.get_param("sigmaZSD")
    187 
    188         lsp_log_std = tf.stack([log_std_ds,
    189                                log_std_asd,
    190                                log_std_asa,
    191                                log_std_sf,
    192                                log_std_k,
    193                                log_std_zsa,
    194                                log_std_zsd], axis=3)
    195 
    196         self._lsp_log_mean = lsp_log_mean
    197         self._lsp_log_std = lsp_log_std
    198 
    199         # ZOD offset
    200         fc = self.carrier_frequency/1e9
    201         if fc < 6.:
    202             fc = tf.cast(6., self._dtype.real_dtype)
    203         a = 0.208*log10(fc)-0.782
    204         b = tf.constant(25., self._dtype.real_dtype)
    205         c = -0.13*log10(fc)+2.03
    206         e = 7.66*log10(fc)-5.96
    207         zod_offset =(e-tf.math.pow(tf.constant(10., self._dtype.real_dtype),
    208             a*log10(tf.maximum(b, distance_2d)) + c - 0.07*(h_ut-1.5)))
    209         zod_offset = tf.where(self.los,
    210                             tf.constant(0., self._dtype.real_dtype),zod_offset)
    211         self._zod_offset = zod_offset
    212 
    213     def _compute_pathloss_basic(self):
    214         r"""Computes the basic component of the pathloss [dB]"""
    215 
    216         batch_size = self.batch_size
    217         num_bs = self.num_bs
    218         num_ut = self.num_ut
    219         distance_2d = self.distance_2d
    220         distance_3d = self.distance_3d
    221         fc = self.carrier_frequency # Carrier frequency (Hz)
    222         h_bs = self.h_bs
    223         h_bs = tf.expand_dims(h_bs, axis=2) # For broadcasting
    224         h_ut = self.h_ut
    225         h_ut = tf.expand_dims(h_ut, axis=1) # For broadcasting
    226 
    227         # Beak point distance
    228         g = ((5./4.)*tf.math.pow(distance_2d/100., 3.)
    229             *tf.math.exp(-distance_2d/150.0))
    230         g = tf.where(tf.math.less(distance_2d, 18.),
    231             tf.constant(0.0, self._dtype.real_dtype), g)
    232         c = g*tf.math.pow((h_ut-13.)/10., 1.5)
    233         c = tf.where(tf.math.less(h_ut, 13.),
    234             tf.constant(0., self._dtype.real_dtype), c)
    235         p = 1./(1.+c)
    236         r = sionna.config.tf_rng.uniform(shape=[batch_size, num_bs, num_ut],
    237             minval=0.0, maxval=1.0, dtype=self._dtype.real_dtype)
    238         r = tf.where(tf.math.less(r, p),
    239             tf.constant(1.0, self._dtype.real_dtype),
    240             tf.constant(0.0, self._dtype.real_dtype))
    241 
    242         max_value = h_ut- 1.5
    243         # Random uniform integer generation is not supported when maxval and
    244         # are not scalar. The following commented would therefore not work.
    245         # Therefore, for now, we just sample from a continuous
    246         # distribution.
    247         #delta = tf.cast(tf.math.floor((max_value-min_value)/3.), tf.int32)
    248         #s = tf_rng.uniform(shape=[batch_size, num_bs, num_ut],
    249         #    minval=0, maxval=delta+1, dtype=tf.int32)
    250         #s = tf.cast(s, tf.float32)*3.+min_value
    251         s = sionna.config.tf_rng.uniform(shape=[batch_size, num_bs, num_ut],
    252            minval=12., maxval=max_value, dtype=self._dtype.real_dtype)
    253         # Itc could happen that h_ut = 13m, and therefore max_value < 13m
    254         s = tf.where(tf.math.less(s, 12.0),
    255             tf.constant(12.0, self._dtype.real_dtype), s)
    256 
    257         h_e = r + (1.-r)*s
    258         h_bs_prime = h_bs - h_e
    259         h_ut_prime = h_ut - h_e
    260         distance_breakpoint = 4*h_bs_prime*h_ut_prime*fc/SPEED_OF_LIGHT
    261 
    262         ## Basic path loss for LoS
    263 
    264         pl_1 = 28.0 + 22.0*log10(distance_3d) + 20.0*log10(fc/1e9)
    265         pl_2 = (28.0 + 40.0*log10(distance_3d) + 20.0*log10(fc/1e9)
    266          - 9.0*log10(tf.square(distance_breakpoint)+tf.square(h_bs-h_ut)))
    267         pl_los = tf.where(tf.math.less(distance_2d, distance_breakpoint),
    268             pl_1, pl_2)
    269 
    270         ## Basic pathloss for NLoS and O2I
    271 
    272         pl_3 = (13.54 + 39.08*log10(distance_3d) + 20.0*log10(fc/1e9)
    273             - 0.6*(h_ut-1.5))
    274         pl_nlos = tf.math.maximum(pl_los, pl_3)
    275 
    276         ## Set the basic pathloss according to UT state
    277 
    278         # Expand to allow broadcasting with the BS dimension
    279         # LoS
    280         pl_b = tf.where(self.los, pl_los, pl_nlos)
    281 
    282         self._pl_b = pl_b