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

lsp.py (19125B)


      1 #
      2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      3 # SPDX-License-Identifier: Apache-2.0
      4 #
      5 """
      6 Class for sampling large scale parameters (LSPs) and pathloss following the
      7 3GPP TR38.901 specifications and according to a channel simulation scenario.
      8 """
      9 
     10 
     11 import tensorflow as tf
     12 from sionna.utils import log10
     13 from sionna.utils import matrix_sqrt
     14 from sionna import config
     15 
     16 class LSP:
     17     r"""
     18     Class for conveniently storing LSPs
     19 
     20     Parameters
     21     -----------
     22 
     23     ds : [batch size, num tx, num rx], tf.float
     24         RMS delay spread [s]
     25 
     26     asd : [batch size, num tx, num rx], tf.float
     27         azimuth angle spread of departure [deg]
     28 
     29     asa : [batch size, num tx, num rx], tf.float
     30         azimuth angle spread of arrival [deg]
     31 
     32     sf : [batch size, num tx, num rx], tf.float
     33         shadow fading
     34 
     35     k_factor : [batch size, num tx, num rx], tf.float
     36         Rician K-factor. Only used for LoS.
     37 
     38     zsa : [batch size, num tx, num rx], tf.float
     39         Zenith angle spread of arrival [deg]
     40 
     41     zsd: [batch size, num tx, num rx], tf.float
     42         Zenith angle spread of departure [deg]
     43     """
     44 
     45     def __init__(self, ds, asd, asa, sf, k_factor, zsa, zsd):
     46         self.ds = ds
     47         self.asd = asd
     48         self.asa = asa
     49         self.sf = sf
     50         self.k_factor = k_factor
     51         self.zsa = zsa
     52         self.zsd = zsd
     53 
     54 class LSPGenerator:
     55     """
     56     Sample large scale parameters (LSP) and pathloss given a channel scenario,
     57     e.g., UMa, UMi, RMa.
     58 
     59     This class implements steps 1 to 4 of the TR 38.901 specifications
     60     (section 7.5), as well as path-loss generation (Section 7.4.1) with O2I
     61     low- and high- loss models (Section 7.4.3).
     62 
     63     Note that a global scenario is set for the entire batches when instantiating
     64     this class (UMa, UMi, or RMa). However, each UT-BS link can have its
     65     specific state (LoS, NLoS, or indoor).
     66 
     67     The batch size is set by the ``scenario`` given as argument when
     68     constructing the class.
     69 
     70     Parameters
     71     ----------
     72     scenario : :class:`~sionna.channel.tr38901.SystemLevelScenario``
     73         Scenario used to generate LSPs
     74 
     75     Input
     76     -----
     77     None
     78 
     79     Output
     80     ------
     81     An `LSP` instance storing realization of LSPs.
     82     """
     83 
     84     def __init__(self, scenario):
     85         self._scenario = scenario
     86 
     87     def sample_pathloss(self):
     88         """
     89         Generate pathlosses [dB] for each BS-UT link.
     90 
     91         Input
     92         ------
     93         None
     94 
     95         Output
     96         -------
     97             A tensor with shape [batch size, number of BSs, number of UTs] of
     98                 pathloss [dB] for each BS-UT link
     99         """
    100 
    101         # Pre-computed basic pathloss
    102         pl_b = self._scenario.basic_pathloss
    103 
    104         ## O2I penetration
    105         if self._scenario.o2i_model == 'low':
    106             pl_o2i = self._o2i_low_loss()
    107         else: # 'high'
    108             pl_o2i = self._o2i_high_loss()
    109 
    110         ## Total path loss, including O2I penetration
    111         pl = pl_b + pl_o2i
    112 
    113         return pl
    114 
    115     def __call__(self):
    116 
    117         # LSPs are assumed to follow a log-normal distribution.
    118         # They are generated in the log-domain (where they follow a normal
    119         # distribution), where they are correlated as indicated in TR38901
    120         # specification (Section 7.5, step 4)
    121 
    122         s = config.tf_rng.normal(shape=[self._scenario.batch_size,
    123                                         self._scenario.num_bs,
    124                                         self._scenario.num_ut, 7],
    125                                  dtype=self._scenario.dtype.real_dtype)
    126 
    127         ## Applyting cross-LSP correlation
    128         s = tf.expand_dims(s, axis=4)
    129         s = self._cross_lsp_correlation_matrix_sqrt@s
    130         s = tf.squeeze(s, axis=4)
    131 
    132         ## Applying spatial correlation
    133         s = tf.expand_dims(tf.transpose(s, [0, 1, 3, 2]), axis=3)
    134         s = tf.matmul(s, self._spatial_lsp_correlation_matrix_sqrt,
    135                       transpose_b=True)
    136         s = tf.transpose(tf.squeeze(s, axis=3), [0, 1, 3, 2])
    137 
    138         ## Scaling and transposing LSPs to the right mean and variance
    139         lsp_log_mean = self._scenario.lsp_log_mean
    140         lsp_log_std = self._scenario.lsp_log_std
    141         lsp_log = lsp_log_std*s + lsp_log_mean
    142 
    143         ## Mapping to linear domain
    144         lsp = tf.math.pow(tf.constant(10., self._scenario.dtype.real_dtype),
    145             lsp_log)
    146 
    147         # Limit the RMS azimuth arrival (ASA) and azimuth departure (ASD)
    148         # spread values to 104 degrees
    149         # Limit the RMS zenith arrival (ZSA) and zenith departure (ZSD)
    150         # spread values to 52 degrees
    151         lsp = LSP(  ds        = lsp[:,:,:,0],
    152                     asd       = tf.math.minimum(lsp[:,:,:,1], 104.0),
    153                     asa       = tf.math.minimum(lsp[:,:,:,2], 104.0),
    154                     sf        = lsp[:,:,:,3],
    155                     k_factor  = lsp[:,:,:,4],
    156                     zsa       = tf.math.minimum(lsp[:,:,:,5], 52.0),
    157                     zsd       = tf.math.minimum(lsp[:,:,:,6], 52.0)
    158                     )
    159 
    160         return lsp
    161 
    162     def topology_updated_callback(self):
    163         """
    164         Updates internal quantities. Must be called at every update of the
    165         scenario that changes the state of UTs or their locations.
    166 
    167         Input
    168         ------
    169         None
    170 
    171         Output
    172         ------
    173         None
    174         """
    175 
    176         # Pre-computing these quantities avoid unnecessary calculations at every
    177         # generation of new LSPs
    178 
    179         # Compute cross-LSP correlation matrix
    180         self._compute_cross_lsp_correlation_matrix()
    181 
    182         # Compute LSP spatial correlation matrix
    183         self._compute_lsp_spatial_correlation_sqrt()
    184 
    185     ########################################
    186     # Internal utility methods
    187     ########################################
    188 
    189     def _compute_cross_lsp_correlation_matrix(self):
    190         """
    191         Compute and store as attribute the square-root of the  cross-LSPs
    192         correlation matrices for each BS-UT link, and then the corresponding
    193         matrix square root for filtering.
    194 
    195         The resulting tensor is of shape
    196         [batch size, number of BSs, number of UTs, 7, 7)
    197         7 being the number of LSPs to correlate.
    198 
    199         Input
    200         ------
    201         None
    202 
    203         Output
    204         -------
    205         None
    206         """
    207 
    208         # The following 7 LSPs are correlated:
    209         # DS, ASA, ASD, SF, K, ZSA, ZSD
    210         # We create the correlation matrix initialized to the identity matrix
    211         cross_lsp_corr_mat = tf.eye(7, 7,batch_shape=[self._scenario.batch_size,
    212             self._scenario.num_bs, self._scenario.num_ut],
    213             dtype=self._scenario.dtype.real_dtype)
    214 
    215         # Tensors of bool indicating the state of UT-BS links
    216         # Indoor
    217         indoor_bool = tf.tile(tf.expand_dims(self._scenario.indoor, axis=1),
    218             [1, self._scenario.num_bs, 1])
    219         # LoS
    220         los_bool = self._scenario.los
    221         # NLoS (outdoor)
    222         nlos_bool = tf.logical_and(tf.logical_not(self._scenario.los),
    223             tf.logical_not(indoor_bool))
    224         # Expand to allow broadcasting with the BS dimension
    225         indoor_bool = tf.expand_dims(tf.expand_dims(indoor_bool, axis=3),axis=4)
    226         los_bool = tf.expand_dims(tf.expand_dims(los_bool, axis=3),axis=4)
    227         nlos_bool = tf.expand_dims(tf.expand_dims(nlos_bool, axis=3),axis=4)
    228 
    229         # Internal function that adds to the correlation matrix ``mat``
    230         # ``cross_lsp_corr_mat`` the parameter ``parameter_name`` at location
    231         # (m,n)
    232         def _add_param(mat, parameter_name, m, n):
    233             # Mask to put the parameters in the right spot of the 7x7
    234             # correlation matrix
    235             mask = tf.scatter_nd([[m,n],[n,m]],
    236                 tf.constant([1.0, 1.0], self._scenario.dtype.real_dtype), [7,7])
    237             mask = tf.reshape(mask, [1,1,1,7,7])
    238             # Get the parameter value according to the link scenario
    239             update = self._scenario.get_param(parameter_name)
    240             update = tf.expand_dims(tf.expand_dims(update, axis=3), axis=4)
    241             # Add update
    242             mat = mat + update*mask
    243             return mat
    244 
    245         # Fill off-diagonal elements of the correlation matrices
    246         # ASD vs DS
    247         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASDvsDS', 0, 1)
    248         # ASA vs DS
    249         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASAvsDS', 0, 2)
    250         # ASA vs SF
    251         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASAvsSF', 3, 2)
    252         # ASD vs SF
    253         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASDvsSF', 3, 1)
    254         # DS vs SF
    255         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrDSvsSF', 3, 0)
    256         # ASD vs ASA
    257         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASDvsASA', 1,2)
    258         # ASD vs K
    259         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASDvsK', 1, 4)
    260         # ASA vs K
    261         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrASAvsK', 2, 4)
    262         # DS vs K
    263         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrDSvsK', 0, 4)
    264         # SF vs K
    265         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrSFvsK', 3, 4)
    266         # ZSD vs SF
    267         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSDvsSF', 3, 6)
    268         # ZSA vs SF
    269         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSAvsSF', 3, 5)
    270         # ZSD vs K
    271         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSDvsK', 6, 4)
    272         # ZSA vs K
    273         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSAvsK', 5, 4)
    274         # ZSD vs DS
    275         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSDvsDS', 6, 0)
    276         # ZSA vs DS
    277         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSAvsDS', 5, 0)
    278         # ZSD vs ASD
    279         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSDvsASD', 6,1)
    280         # ZSA vs ASD
    281         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSAvsASD', 5,1)
    282         # ZSD vs ASA
    283         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSDvsASA', 6,2)
    284         # ZSA vs ASA
    285         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSAvsASA', 5,2)
    286         # ZSD vs ZSA
    287         cross_lsp_corr_mat = _add_param(cross_lsp_corr_mat, 'corrZSDvsZSA', 5,6)
    288 
    289         # Compute and store the square root of the cross-LSP correlation
    290         # matrix
    291         self._cross_lsp_correlation_matrix_sqrt = matrix_sqrt(
    292                 cross_lsp_corr_mat)
    293 
    294     def _compute_lsp_spatial_correlation_sqrt(self):
    295         """
    296         Compute the square root of the spatial correlation matrices of LSPs.
    297 
    298         The LSPs are correlated accross users according to the distance between
    299         the users. Each LSP is spatially correlated according to a different
    300         spatial correlation matrix.
    301 
    302         The links involving different BSs are not correlated.
    303         UTs in different state (LoS, NLoS, O2I) are not assumed to be
    304         correlated.
    305 
    306         The correlation of the LSPs X of two UTs in the same state related to
    307         the links of these UTs to a same BS is
    308 
    309         .. math::
    310             C(X_1,X_2) = exp(-d/D_X)
    311 
    312         where :math:`d` is the distance between the UTs in the X-Y plane (2D
    313         distance) and D_X the correlation distance of LSP X.
    314 
    315         The resulting tensor if of shape
    316         [batch size, number of BSs, 7, number of UTs, number of UTs)
    317         7 being the number of LSPs.
    318 
    319         Input
    320         ------
    321         None
    322 
    323         Output
    324         -------
    325         None
    326         """
    327 
    328         # Tensors of bool indicating which pair of UTs to correlate.
    329         # Pairs of UTs that are correlated are those that share the same state
    330         # (indoor, LoS, or NLoS).
    331         # Indoor
    332         indoor = tf.tile(tf.expand_dims(self._scenario.indoor, axis=1),
    333                          [1, self._scenario.num_bs, 1])
    334         # LoS
    335         los_ut = self._scenario.los
    336         los_pair_bool = tf.logical_and(tf.expand_dims(los_ut, axis=3),
    337                                        tf.expand_dims(los_ut, axis=2))
    338         # NLoS
    339         nlos_ut = tf.logical_and(tf.logical_not(self._scenario.los),
    340                                  tf.logical_not(indoor))
    341         nlos_pair_bool = tf.logical_and(tf.expand_dims(nlos_ut, axis=3),
    342                                         tf.expand_dims(nlos_ut, axis=2))
    343         # O2I
    344         o2i_pair_bool = tf.logical_and(tf.expand_dims(indoor, axis=3),
    345                                        tf.expand_dims(indoor, axis=2))
    346 
    347         # Stacking the correlation matrix
    348         # One correlation matrix per LSP
    349         filtering_matrices = []
    350         distance_scaling_matrices = []
    351         for parameter_name in ('corrDistDS', 'corrDistASD', 'corrDistASA',
    352             'corrDistSF', 'corrDistK', 'corrDistZSA', 'corrDistZSD'):
    353             # Matrix used for filtering and scaling the 2D distances
    354             # For each pair of UTs, the entry is set to 0 if the UTs are in
    355             # different states, -1/(correlation distance) otherwise.
    356             # The correlation distance is different for each LSP.
    357             filtering_matrix = tf.eye(self._scenario.num_ut,
    358                 self._scenario.num_ut, batch_shape=[self._scenario.batch_size,
    359                 self._scenario.num_bs], dtype=self._scenario.dtype.real_dtype)
    360             distance_scaling_matrix = self._scenario.get_param(parameter_name)
    361             distance_scaling_matrix = tf.tile(tf.expand_dims(
    362                 distance_scaling_matrix, axis=3),
    363                 [1, 1, 1, self._scenario.num_ut])
    364             distance_scaling_matrix = -1./distance_scaling_matrix
    365             # LoS
    366             filtering_matrix = tf.where(los_pair_bool,
    367                 tf.constant(1.0, self._scenario.dtype.real_dtype),
    368                     filtering_matrix)
    369             # NLoS
    370             filtering_matrix = tf.where(nlos_pair_bool,
    371                 tf.constant(1.0, self._scenario.dtype.real_dtype),
    372                     filtering_matrix)
    373             # indoor
    374             filtering_matrix = tf.where(o2i_pair_bool,
    375                 tf.constant(1.0, self._scenario.dtype.real_dtype),
    376                     filtering_matrix)
    377             # Stacking
    378             filtering_matrices.append(filtering_matrix)
    379             distance_scaling_matrices.append(distance_scaling_matrix)
    380         filtering_matrices = tf.stack(filtering_matrices, axis=2)
    381         distance_scaling_matrices = tf.stack(distance_scaling_matrices, axis=2)
    382 
    383         ut_dist_2d = self._scenario.matrix_ut_distance_2d
    384         # Adding a dimension for broadcasting with BS
    385         ut_dist_2d = tf.expand_dims(tf.expand_dims(ut_dist_2d, axis=1), axis=2)
    386 
    387         # Correlation matrix
    388         spatial_lsp_correlation = (tf.math.exp(
    389             ut_dist_2d*distance_scaling_matrices)*filtering_matrices)
    390 
    391         # Compute and store the square root of the spatial correlation matrix
    392         self._spatial_lsp_correlation_matrix_sqrt = matrix_sqrt(
    393                 spatial_lsp_correlation)
    394 
    395     def _o2i_low_loss(self):
    396         """
    397         Compute for each BS-UT link the pathloss due to the O2I penetration loss
    398         in dB with the low-loss model.
    399         See section 7.4.3.1 of 38.901 specification.
    400 
    401         UTs located outdoor (LoS and NLoS) get O2I pathloss of 0dB.
    402 
    403         Input
    404         -----
    405         None
    406 
    407         Output
    408         -------
    409             Tensor with shape
    410             [batch size, number of BSs, number of UTs]
    411             containing the O2I penetration low-loss in dB for each BS-UT link
    412         """
    413 
    414         fc = self._scenario.carrier_frequency/1e9 # Carrier frequency (GHz)
    415         batch_size = self._scenario.batch_size
    416         num_ut = self._scenario.num_ut
    417         num_bs = self._scenario.num_bs
    418 
    419         # Material penetration losses
    420         # fc must be in GHz
    421         l_glass = 2. + 0.2*fc
    422         l_concrete = 5. + 4.*fc
    423 
    424         # Path loss through external wall
    425         pl_tw = 5.0 - 10.*log10(0.3*tf.math.pow(tf.constant(10.,
    426             self._scenario.dtype.real_dtype), -l_glass/10.0) + 0.7*tf.math.pow(
    427                 tf.constant(10., self._scenario.dtype.real_dtype),
    428                     -l_concrete/10.0))
    429 
    430         # Filtering-out the O2I pathloss for UTs located outdoor
    431         indoor_mask = tf.where(self._scenario.indoor, tf.constant(1.0,
    432             self._scenario.dtype.real_dtype), tf.zeros([batch_size, num_ut],
    433             self._scenario.dtype.real_dtype))
    434         indoor_mask = tf.expand_dims(indoor_mask, axis=1)
    435         pl_tw = pl_tw*indoor_mask
    436 
    437         # Pathloss due to indoor propagation
    438         # The indoor 2D distance for outdoor UTs is 0
    439         pl_in = 0.5*self._scenario.distance_2d_in
    440 
    441         # Random path loss component
    442         # Gaussian distributed with standard deviation 4.4 in dB
    443         pl_rnd = config.tf_rng.normal(shape=[batch_size, num_bs, num_ut],
    444                                       mean=0.0,
    445                                       stddev=4.4,
    446                                       dtype=self._scenario.dtype.real_dtype)
    447         pl_rnd = pl_rnd*indoor_mask
    448 
    449         return pl_tw + pl_in + pl_rnd
    450 
    451     def _o2i_high_loss(self):
    452         """
    453         Compute for each BS-UT link the pathloss due to the O2I penetration loss
    454         in dB with the high-loss model.
    455         See section 7.4.3.1 of 38.901 specification.
    456 
    457         UTs located outdoor (LoS and NLoS) get O2I pathloss of 0dB.
    458 
    459         Input
    460         -----
    461         None
    462 
    463         Output
    464         -------
    465             Tensor with shape
    466             [batch size, number of BSs, number of UTs]
    467             containing the O2I penetration low-loss in dB for each BS-UT link
    468         """
    469 
    470         fc = self._scenario.carrier_frequency/1e9 # Carrier frequency (GHz)
    471         batch_size = self._scenario.batch_size
    472         num_ut = self._scenario.num_ut
    473         num_bs = self._scenario.num_bs
    474 
    475         # Material penetration losses
    476         # fc must be in GHz
    477         l_iirglass = 23. + 0.3*fc
    478         l_concrete = 5. + 4.*fc
    479 
    480         # Path loss through external wall
    481         pl_tw = 5.0 - 10.*log10(0.7*tf.math.pow(tf.constant(10.,
    482             self._scenario.dtype.real_dtype), -l_iirglass/10.0)
    483                 + 0.3*tf.math.pow(tf.constant(10.,
    484                 self._scenario.dtype.real_dtype), -l_concrete/10.0))
    485 
    486         # Filtering-out the O2I pathloss for outdoor UTs
    487         indoor_mask = tf.where(self._scenario.indoor, 1.0,
    488             tf.zeros([batch_size, num_ut], self._scenario.dtype.real_dtype))
    489         indoor_mask = tf.expand_dims(indoor_mask, axis=1)
    490         pl_tw = pl_tw*indoor_mask
    491 
    492         # Pathloss due to indoor propagation
    493         # The indoor 2D distance for outdoor UTs is 0
    494         pl_in = 0.5*self._scenario.distance_2d_in
    495 
    496         # Random path loss component
    497         # Gaussian distributed with standard deviation 6.5 in dB for the
    498         # high loss model
    499         pl_rnd = config.tf_rng.normal(shape=[batch_size, num_bs, num_ut],
    500                                       mean=0.0,
    501                                       stddev=6.5,
    502                                       dtype=self._scenario.dtype.real_dtype)
    503         pl_rnd = pl_rnd*indoor_mask
    504 
    505         return pl_tw + pl_in + pl_rnd