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

system_level_scenario.py (27466B)


      1 #
      2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      3 # SPDX-License-Identifier: Apache-2.0
      4 #
      5 """Class used to define a system level 3GPP channel simulation scenario"""
      6 
      7 import json
      8 from importlib_resources import files
      9 import tensorflow as tf
     10 from abc import ABC, abstractmethod
     11 import sionna
     12 from sionna import SPEED_OF_LIGHT, PI
     13 from sionna.utils import log10
     14 from sionna.channel.utils import sample_bernoulli, rad_2_deg, wrap_angle_0_360
     15 from .antenna import PanelArray
     16 
     17 from . import models # pylint: disable=relative-beyond-top-level
     18 
     19 
     20 class SystemLevelScenario(ABC):
     21     r"""
     22     This class is used to set up the scenario for system level 3GPP channel
     23     simulation.
     24 
     25     Scenarios for system level channel simulation, such as UMi, UMa, or RMa,
     26     are defined by implementing this base class.
     27 
     28     Input
     29     ------
     30     carrier_frequency : float
     31         Carrier frequency [Hz]
     32 
     33     o2i_model : str
     34         Outdoor to indoor (O2I) pathloss model, used for indoor UTs.
     35         Either "low" or "high" (see section 7.4.3 from 38.901 specification)
     36 
     37     ut_array : PanelArray
     38         Panel array configuration used by UTs
     39 
     40     bs_array : PanelArray
     41         Panel array configuration used by BSs
     42 
     43     direction : str
     44         Link direction. Either "uplink" or "downlink"
     45 
     46     enable_pathloss : bool
     47         If set to `True`, apply pathloss. Otherwise, does not. Defaults to True.
     48 
     49     enable_shadow_fading : bool
     50         If set to `True`, apply shadow fading. Otherwise, does not.
     51         Defaults to True.
     52 
     53     dtype : tf.DType
     54         Defines the datatype for internal calculations and the output
     55         dtype. Defaults to `tf.complex64`.
     56     """
     57 
     58     def __init__(self, carrier_frequency, o2i_model, ut_array, bs_array,
     59         direction, enable_pathloss=True, enable_shadow_fading=True,
     60         dtype=tf.complex64):
     61 
     62         # Carrier frequency (Hz)
     63         self._carrier_frequency = tf.constant(carrier_frequency,
     64             dtype.real_dtype)
     65 
     66         # Wavelength (m)
     67         self._lambda_0 = tf.constant(SPEED_OF_LIGHT/carrier_frequency,
     68             dtype.real_dtype)
     69 
     70         # O2I model
     71         assert o2i_model in ('low', 'high'), "o2i_model must be 'low' or 'high'"
     72         self._o2i_model = o2i_model
     73 
     74         # UTs and BSs arrays
     75         assert isinstance(ut_array, PanelArray), \
     76             "'ut_array' must be an instance of PanelArray"
     77         assert isinstance(bs_array, PanelArray), \
     78             "'bs_array' must be an instance of PanelArray"
     79         self._ut_array = ut_array
     80         self._bs_array = bs_array
     81 
     82         # data type
     83         assert dtype.is_complex, "'dtype' must be complex type"
     84         self._dtype = dtype
     85 
     86         # Direction
     87         assert direction in ("uplink", "downlink"), \
     88             "'direction' must be 'uplink' or 'downlink'"
     89         self._direction = direction
     90 
     91         # Pathloss and shadow fading
     92         self._enable_pathloss = enable_pathloss
     93         self._enable_shadow_fading = enable_shadow_fading
     94 
     95         # Scenario
     96         self._ut_loc = None
     97         self._bs_loc = None
     98         self._ut_orientations = None
     99         self._bs_orientations = None
    100         self._ut_velocities = None
    101         self._in_state = None
    102         self._requested_los = None
    103 
    104         # Load parameters for this scenario
    105         self._load_params()
    106 
    107     @property
    108     def carrier_frequency(self):
    109         r"""Carrier frequency [Hz]"""
    110         return self._carrier_frequency
    111 
    112     @property
    113     def direction(self):
    114         r"""Direction of communication. Either "uplink" or "downlink"."""
    115         return self._direction
    116 
    117     @property
    118     def pathloss_enabled(self):
    119         r"""`True` is pathloss is enabled. `False` otherwise."""
    120         return self._enable_pathloss
    121 
    122     @property
    123     def shadow_fading_enabled(self):
    124         r"""`True` is shadow fading is enabled. `False` otherwise."""
    125         return self._enable_shadow_fading
    126 
    127     @property
    128     def lambda_0(self):
    129         r"""Wavelength [m]"""
    130         return self._lambda_0
    131 
    132     @property
    133     def batch_size(self):
    134         """Batch size"""
    135         return tf.shape(self._ut_loc)[0]
    136 
    137     @property
    138     def num_ut(self):
    139         """Number of UTs."""
    140         return tf.shape(self._ut_loc)[1]
    141 
    142     @property
    143     def num_bs(self):
    144         """
    145         Number of BSs.
    146         """
    147         return tf.shape(self._bs_loc)[1]
    148 
    149     @property
    150     def h_ut(self):
    151         r"""Heigh of UTs [m]. [batch size, number of UTs]"""
    152         return self._ut_loc[:,:,2]
    153 
    154     @property
    155     def h_bs(self):
    156         r"""Heigh of BSs [m].[batch size, number of BSs]"""
    157         return self._bs_loc[:,:,2]
    158 
    159     @property
    160     def ut_loc(self):
    161         r"""Locations of UTs [m]. [batch size, number of UTs, 3]"""
    162         return self._ut_loc
    163 
    164     @property
    165     def bs_loc(self):
    166         r"""Locations of BSs [m]. [batch size, number of BSs, 3]"""
    167         return self._bs_loc
    168 
    169     @property
    170     def ut_orientations(self):
    171         r"""Orientations of UTs [radian]. [batch size, number of UTs, 3]"""
    172         return self._ut_orientations
    173 
    174     @property
    175     def bs_orientations(self):
    176         r"""Orientations of BSs [radian]. [batch size, number of BSs, 3]"""
    177         return self._bs_orientations
    178 
    179     @property
    180     def ut_velocities(self):
    181         r"""UTs velocities [m/s]. [batch size, number of UTs, 3]"""
    182         return self._ut_velocities
    183 
    184     @property
    185     def ut_array(self):
    186         r"""PanelArray used by UTs."""
    187         return self._ut_array
    188 
    189     @property
    190     def bs_array(self):
    191         r"""PanelArray used by BSs."""
    192         return self._bs_array
    193 
    194     @property
    195     def indoor(self):
    196         r"""
    197         Indoor state of UTs. `True` is indoor, `False` otherwise.
    198         [batch size, number of UTs]"""
    199         return self._in_state
    200 
    201     @property
    202     def los(self):
    203         r"""LoS state of BS-UT links. `True` if LoS, `False` otherwise.
    204         [batch size, number of BSs, number of UTs]"""
    205         return self._los
    206 
    207     @property
    208     def distance_2d(self):
    209         r"""
    210         Distance between each UT and each BS in the X-Y plan [m].
    211         [batch size, number of BSs, number of UTs]"""
    212         return self._distance_2d
    213 
    214     @property
    215     def distance_2d_in(self):
    216         r"""Indoor distance between each UT and BS in the X-Y plan [m], i.e.,
    217         part of the total distance that corresponds to indoor propagation in the
    218         X-Y plan.
    219         Set to 0 for UTs located ourdoor.
    220         [batch size, number of BSs, number of UTs]"""
    221         return self._distance_2d_in
    222 
    223     @property
    224     def distance_2d_out(self):
    225         r"""Outdoor distance between each UT and BS in the X-Y plan [m], i.e.,
    226         part of the total distance that corresponds to outdoor propagation in
    227         the X-Y plan.
    228         Equals to ``distance_2d`` for UTs located outdoor.
    229         [batch size, number of BSs, number of UTs]"""
    230         return self._distance_2d_out
    231 
    232     @property
    233     def distance_3d(self):
    234         r"""
    235         Distance between each UT and each BS [m].
    236         [batch size, number of BSs, number of UTs]"""
    237         return self._distance_2d
    238 
    239     @property
    240     def distance_3d_in(self):
    241         r"""Indoor distance between each UT and BS [m], i.e.,
    242         part of the total distance that corresponds to indoor propagation.
    243         Set to 0 for UTs located ourdoor.
    244         [batch size, number of BSs, number of UTs]"""
    245         return self._distance_3d_in
    246 
    247     @property
    248     def distance_3d_out(self):
    249         r"""Outdoor distance between each UT and BS [m], i.e.,
    250         part of the total distance that corresponds to outdoor propagation.
    251         Equals to ``distance_3d`` for UTs located outdoor.
    252         [batch size, number of BSs, number of UTs]"""
    253         return self._distance_3d_out
    254 
    255     @property
    256     def matrix_ut_distance_2d(self):
    257         r"""Distance between all pairs for UTs in the X-Y plan [m].
    258         [batch size, number of UTs, number of UTs]"""
    259         return self._matrix_ut_distance_2d
    260 
    261     @property
    262     def los_aod(self):
    263         r"""LoS azimuth angle of departure of each BS-UT link [deg].
    264         [batch size, number of BSs, number of UTs]"""
    265         return self._los_aod
    266 
    267     @property
    268     def los_aoa(self):
    269         r"""LoS azimuth angle of arrival of each BS-UT link [deg].
    270         [batch size, number of BSs, number of UTs]"""
    271         return self._los_aoa
    272 
    273     @property
    274     def los_zod(self):
    275         r"""LoS zenith angle of departure of each BS-UT link [deg].
    276         [batch size, number of BSs, number of UTs]"""
    277         return self._los_zod
    278 
    279     @property
    280     def los_zoa(self):
    281         r"""LoS zenith angle of arrival of each BS-UT link [deg].
    282         [batch size, number of BSs, number of UTs]"""
    283         return self._los_zoa
    284 
    285     @property
    286     @abstractmethod
    287     def los_probability(self):
    288         r"""Probability of each UT to be LoS. Used to randomly generate LoS
    289         status of outdoor UTs. [batch size, number of UTs]"""
    290         pass
    291 
    292     @property
    293     @abstractmethod
    294     def min_2d_in(self):
    295         r"""Minimum indoor 2D distance for indoor UTs [m]"""
    296         pass
    297 
    298     @property
    299     @abstractmethod
    300     def max_2d_in(self):
    301         r"""Maximum indoor 2D distance for indoor UTs [m]"""
    302         pass
    303 
    304     @property
    305     def lsp_log_mean(self):
    306         r"""
    307         Mean of LSPs in the log domain.
    308         [batch size, number of BSs, number of UTs, 7].
    309         The last dimension corresponds to the LSPs, in the following order:
    310         DS - ASD - ASA - SF - K - ZSA - ZSD - XPR"""
    311         return self._lsp_log_mean
    312 
    313     @property
    314     def lsp_log_std(self):
    315         r"""
    316         STD of LSPs in the log domain.
    317         [batch size, number of BSs, number of UTs, 7].
    318         The last dimension corresponds to the LSPs, in the following order:
    319         DS - ASD - ASA - SF - K - ZSA - ZSD - XPR"""
    320         return self._lsp_log_std
    321 
    322     @property
    323     @abstractmethod
    324     def rays_per_cluster(self):
    325         r"""Number of rays per cluster"""
    326         pass
    327 
    328     @property
    329     def zod_offset(self):
    330         r"""Zenith angle of departure offset"""
    331         return self._zod_offset
    332 
    333     @property
    334     def num_clusters_los(self):
    335         r"""Number of clusters for LoS scenario"""
    336         return self._params_los["numClusters"]
    337 
    338     @property
    339     def num_clusters_nlos(self):
    340         r"""Number of clusters for NLoS scenario"""
    341         return self._params_nlos["numClusters"]
    342 
    343     @property
    344     def num_clusters_indoor(self):
    345         r"""Number of clusters indoor scenario"""
    346         return self._params_o2i["numClusters"]
    347 
    348     @property
    349     def num_clusters_max(self):
    350         r"""Maximum number of clusters over indoor, LoS, and NLoS scenarios"""
    351         # Different models have different number of clusters
    352         num_clusters_los = self._params_los["numClusters"]
    353         num_clusters_nlos = self._params_nlos["numClusters"]
    354         num_clusters_o2i = self._params_o2i["numClusters"]
    355         num_clusters_max = tf.reduce_max([num_clusters_los, num_clusters_nlos,
    356             num_clusters_o2i])
    357         return num_clusters_max
    358 
    359     @property
    360     def basic_pathloss(self):
    361         r"""Basic pathloss component [dB].
    362         See section 7.4.1 of 38.901 specification.
    363         [batch size, num BS, num UT]"""
    364         return self._pl_b
    365 
    366     def set_topology(self, ut_loc=None, bs_loc=None, ut_orientations=None,
    367         bs_orientations=None, ut_velocities=None, in_state=None, los=None):
    368         r"""
    369         Set the network topology.
    370 
    371         It is possible to set up a different network topology for each batch
    372         example.
    373 
    374         When calling this function, not specifying a parameter leads to the
    375         reuse of the previously given value. Not specifying a value that was not
    376         set at a former call rises an error.
    377 
    378         Input
    379         ------
    380             ut_loc : [batch size, number of UTs, 3], tf.float
    381                 Locations of the UTs [m]
    382 
    383             bs_loc : [batch size, number of BSs, 3], tf.float
    384                 Locations of BSs [m]
    385 
    386             ut_orientations : [batch size, number of UTs, 3], tf.float
    387                 Orientations of the UTs arrays [radian]
    388 
    389             bs_orientations : [batch size, number of BSs, 3], tf.float
    390                 Orientations of the BSs arrays [radian]
    391 
    392             ut_velocities : [batch size, number of UTs, 3], tf.float
    393                 Velocity vectors of UTs [m/s]
    394 
    395             in_state : [batch size, number of UTs], tf.bool
    396                 Indoor/outdoor state of UTs. `True` means indoor and `False`
    397                 means outdoor.
    398 
    399             los : tf.bool or `None`
    400                 If not `None` (default value), all UTs located outdoor are
    401                 forced to be in LoS if ``los`` is set to `True`, or in NLoS
    402                 if it is set to `False`. If set to `None`, the LoS/NLoS states
    403                 of UTs is set following 3GPP specification
    404                 (Section 7.4.2 of TR 38.901).
    405         """
    406 
    407         assert (ut_loc is not None) or (self._ut_loc is not None),\
    408             "`ut_loc` is None and was not previously set"
    409 
    410         assert (bs_loc is not None) or (self._bs_loc is not None),\
    411             "`bs_loc` is None and was not previously set"
    412 
    413         assert (in_state is not None) or (self._in_state is not None),\
    414             "`in_state` is None and was not previously set"
    415 
    416         assert (ut_orientations is not None)\
    417             or (self._ut_orientations is not None),\
    418             "`ut_orientations` is None and was not previously set"
    419 
    420         assert (bs_orientations is not None)\
    421             or (self._bs_orientations is not None),\
    422             "`bs_orientations` is None and was not previously set"
    423 
    424         assert (ut_velocities is not None)\
    425             or (self._ut_velocities is not None),\
    426             "`ut_velocities` is None and was not previously set"
    427 
    428         # Boolean used to keep track of whether or not we need to (re-)compute
    429         # the distances between users, correlation matrices...
    430         # This is required if the UT locations, BS locations, indoor/outdoor
    431         # state of UTs, or LoS/NLoS states of outdoor UTs are updated.
    432         need_for_update = False
    433 
    434         if ut_loc is not None:
    435             self._ut_loc = ut_loc
    436             need_for_update = True
    437 
    438         if bs_loc is not None:
    439             self._bs_loc = bs_loc
    440             need_for_update = True
    441 
    442         if bs_orientations is not None:
    443             self._bs_orientations = bs_orientations
    444 
    445         if ut_orientations is not None:
    446             self._ut_orientations = ut_orientations
    447 
    448         if ut_velocities is not None:
    449             self._ut_velocities = ut_velocities
    450 
    451         if in_state is not None:
    452             self._in_state = in_state
    453             need_for_update = True
    454 
    455         if los is not None:
    456             self._requested_los = los
    457             need_for_update = True
    458 
    459         if need_for_update:
    460             # Update topology-related quantities
    461             self._compute_distance_2d_3d_and_angles()
    462             self._sample_indoor_distance()
    463             self._sample_los()
    464 
    465             # Compute the LSPs means and stds
    466             self._compute_lsp_log_mean_std()
    467 
    468             # Compute the basic path-loss
    469             self._compute_pathloss_basic()
    470 
    471         return need_for_update
    472 
    473     def spatial_correlation_matrix(self, correlation_distance):
    474         r"""Computes and returns a 2D spatial exponential correlation matrix
    475         :math:`C` over the UTs, such that :math:`C`has shape
    476         (number of UTs)x(number of UTs), and
    477 
    478         .. math::
    479             C_{n,m} = \exp{-\frac{d_{n,m}}{D}}
    480 
    481         where :math:`d_{n,m}` is the distance between UT :math:`n` and UT
    482         :math:`m` in the X-Y plan, and :math:`D` the correlation distance.
    483 
    484         Input
    485         ------
    486         correlation_distance : float
    487             Correlation distance, i.e., distance such that the correlation
    488             is :math:`e^{-1} \approx 0.37`
    489 
    490         Output
    491         --------
    492         : [batch size, number of UTs, number of UTs], float
    493             Spatial correlation :math:`C`
    494         """
    495         spatial_correlation_matrix = tf.math.exp(-self.matrix_ut_distance_2d/
    496                                                  correlation_distance)
    497         return spatial_correlation_matrix
    498 
    499 
    500     @property
    501     @abstractmethod
    502     def los_parameter_filepath(self):
    503         r""" Path of the configuration file for LoS scenario"""
    504         pass
    505 
    506     @property
    507     @abstractmethod
    508     def nlos_parameter_filepath(self):
    509         r""" Path of the configuration file for NLoS scenario"""
    510         pass
    511 
    512     @property
    513     @abstractmethod
    514     def o2i_parameter_filepath(self):
    515         r""" Path of the configuration file for indoor scenario"""
    516         pass
    517 
    518     @property
    519     def o2i_model(self):
    520         r"""O2I model used for pathloss computation of indoor UTs. Either "low"
    521         or "high". See section 7.4.3 or TR 38.901."""
    522         return self._o2i_model
    523 
    524     @property
    525     def dtype(self):
    526         r"""Complex datatype used for internal calculation and tensors"""
    527         return self._dtype
    528 
    529     @abstractmethod
    530     def clip_carrier_frequency_lsp(self, fc):
    531         r"""Clip the carrier frequency ``fc`` in GHz for LSP calculation
    532 
    533         Input
    534         -----
    535         fc : float
    536             Carrier frequency [GHz]
    537 
    538         Output
    539         -------
    540         : float
    541             Clipped carrier frequency, that should be used for LSp computation
    542         """
    543         pass
    544 
    545     def get_param(self, parameter_name):
    546         r"""
    547         Given a ``parameter_name`` used in the configuration file, returns a
    548         tensor with shape [batch size, number of BSs, number of UTs] of the
    549         parameter value according to each BS-UT link state (LoS, NLoS, indoor).
    550 
    551         Input
    552         ------
    553         parameter_name : str
    554             Name of the parameter used in the configuration file
    555 
    556         Output
    557         -------
    558         : [batch size, number of BSs, number of UTs], tf.float
    559             Parameter value for each BS-UT link
    560         """
    561 
    562         fc = self._carrier_frequency/1e9
    563         fc = self.clip_carrier_frequency_lsp(fc)
    564 
    565         parameter_tensor = tf.zeros(shape=[self.batch_size,
    566                                             self.num_bs,
    567                                             self.num_ut],
    568                                             dtype=self._dtype.real_dtype)
    569 
    570         # Parameter value
    571         if parameter_name in ('muDS', 'sigmaDS', 'muASD', 'sigmaASD', 'muASA',
    572                              'sigmaASA', 'muZSA', 'sigmaZSA'):
    573 
    574             pa_los = self._params_los[parameter_name + 'a']
    575             pb_los = self._params_los[parameter_name + 'b']
    576             pc_los = self._params_los[parameter_name + 'c']
    577 
    578             pa_nlos = self._params_nlos[parameter_name + 'a']
    579             pb_nlos = self._params_nlos[parameter_name + 'b']
    580             pc_nlos = self._params_nlos[parameter_name + 'c']
    581 
    582             pa_o2i = self._params_o2i[parameter_name + 'a']
    583             pb_o2i = self._params_o2i[parameter_name + 'b']
    584             pc_o2i = self._params_o2i[parameter_name + 'c']
    585 
    586             parameter_value_los = pa_los*log10(pb_los+fc) + pc_los
    587             parameter_value_nlos = pa_nlos*log10(pb_nlos+fc) + pc_nlos
    588             parameter_value_o2i = pa_o2i*log10(pb_o2i+fc) + pc_o2i
    589         elif parameter_name == "cDS":
    590 
    591             pa_los = self._params_los[parameter_name + 'a']
    592             pb_los = self._params_los[parameter_name + 'b']
    593             pc_los = self._params_los[parameter_name + 'c']
    594 
    595             pa_nlos = self._params_nlos[parameter_name + 'a']
    596             pb_nlos = self._params_nlos[parameter_name + 'b']
    597             pc_nlos = self._params_nlos[parameter_name + 'c']
    598 
    599             pa_o2i = self._params_o2i[parameter_name + 'a']
    600             pb_o2i = self._params_o2i[parameter_name + 'b']
    601             pc_o2i = self._params_o2i[parameter_name + 'c']
    602 
    603             parameter_value_los = tf.math.maximum(pa_los,
    604                 pb_los - pc_los*log10(fc))
    605             parameter_value_nlos = tf.math.maximum(pa_nlos,
    606                 pb_nlos - pc_nlos*log10(fc))
    607             parameter_value_o2i = tf.math.maximum(pa_o2i,
    608                 pb_o2i - pc_o2i*log10(fc))
    609         else:
    610             parameter_value_los = self._params_los[parameter_name]
    611             parameter_value_nlos = self._params_nlos[parameter_name]
    612             parameter_value_o2i = self._params_o2i[parameter_name]
    613 
    614         # Expand to allow broadcasting with the BS dimension
    615         indoor = tf.expand_dims(self.indoor, axis=1)
    616         # LoS
    617         parameter_value_los = tf.cast(parameter_value_los,
    618                                         self._dtype.real_dtype)
    619         parameter_tensor = tf.where(self.los, parameter_value_los,
    620             parameter_tensor)
    621         # NLoS
    622         parameter_value_nlos = tf.cast(parameter_value_nlos,
    623                                         self._dtype.real_dtype)
    624         parameter_tensor = tf.where(
    625             tf.logical_and(tf.logical_not(self.los),
    626             tf.logical_not(indoor)), parameter_value_nlos,
    627             parameter_tensor)
    628         # O2I
    629         parameter_value_o2i = tf.cast(parameter_value_o2i,
    630                                         self._dtype.real_dtype)
    631         parameter_tensor = tf.where(indoor, parameter_value_o2i,
    632             parameter_tensor)
    633 
    634         return parameter_tensor
    635 
    636     #####################################################
    637     # Internal utility methods
    638     #####################################################
    639 
    640     def _compute_distance_2d_3d_and_angles(self):
    641         r"""
    642         Computes the following internal values:
    643         * 2D distances for all BS-UT pairs in the X-Y plane
    644         * 3D distances for all BS-UT pairs
    645         * 2D distances for all pairs of UTs in the X-Y plane
    646         * LoS AoA, AoD, ZoA, ZoD for all BS-UT pairs
    647 
    648         This function is called at every update of the topology.
    649         """
    650 
    651         ut_loc = self._ut_loc
    652         ut_loc = tf.expand_dims(ut_loc, axis=1)
    653 
    654         bs_loc = self._bs_loc
    655         bs_loc = tf.expand_dims(bs_loc, axis=2)
    656 
    657         delta_loc_xy = ut_loc[:,:,:,:2] - bs_loc[:,:,:,:2]
    658         delta_loc = ut_loc - bs_loc
    659 
    660         # 2D distances for all BS-UT pairs in the (x-y) plane
    661         distance_2d = tf.sqrt(tf.reduce_sum(tf.square(delta_loc_xy), axis=3))
    662         self._distance_2d = distance_2d
    663 
    664         # 3D distances for all BS-UT pairs
    665         distance_3d = tf.sqrt(tf.reduce_sum(tf.square(delta_loc), axis=3))
    666         self._distance_3d = distance_3d
    667 
    668         # LoS AoA, AoD, ZoA, ZoD
    669         los_aod = tf.atan2(delta_loc[:,:,:,1], delta_loc[:,:,:,0])
    670         los_aoa = los_aod + PI
    671         los_zod = tf.atan2(distance_2d, delta_loc[:,:,:,2])
    672         los_zoa = los_zod - PI
    673         # Angles are converted to degrees and wrapped to (0,360)
    674         self._los_aod = wrap_angle_0_360(rad_2_deg(los_aod))
    675         self._los_aoa = wrap_angle_0_360(rad_2_deg(los_aoa))
    676         self._los_zod = wrap_angle_0_360(rad_2_deg(los_zod))
    677         self._los_zoa = wrap_angle_0_360(rad_2_deg(los_zoa))
    678 
    679         # 2D distances for all pairs of UTs in the (x-y) plane
    680         ut_loc_xy = self._ut_loc[:,:,:2]
    681 
    682         ut_loc_xy_expanded_1 = tf.expand_dims(ut_loc_xy, axis=1)
    683         ut_loc_xy_expanded_2 = tf.expand_dims(ut_loc_xy, axis=2)
    684 
    685         delta_loc_xy = ut_loc_xy_expanded_1 - ut_loc_xy_expanded_2
    686 
    687         matrix_ut_distance_2d = tf.sqrt(tf.reduce_sum(tf.square(delta_loc_xy),
    688                                                        axis=3))
    689         self._matrix_ut_distance_2d = matrix_ut_distance_2d
    690 
    691     def _sample_los(self):
    692         r"""Set the LoS state of each UT randomly, following the procedure
    693         described in section 7.4.2 of TR 38.901.
    694         LoS state of each UT is randomly assigned according to a Bernoulli
    695         distribution, which probability depends on the channel model.
    696         """
    697         if self._requested_los is None:
    698             los_probability = self.los_probability
    699             los = sample_bernoulli([self.batch_size, self.num_bs,
    700                                         self.num_ut], los_probability,
    701                                         self._dtype.real_dtype)
    702         else:
    703             los = tf.fill([self.batch_size, self.num_bs, self.num_ut],
    704                             self._requested_los)
    705 
    706         self._los = tf.logical_and(los,
    707             tf.logical_not(tf.expand_dims(self._in_state, axis=1)))
    708 
    709     def _sample_indoor_distance(self):
    710         r"""Sample 2D indoor distances for indoor devices, according to section
    711         7.4.3.1 of TR 38.901.
    712         """
    713 
    714         indoor = self.indoor
    715         indoor = tf.expand_dims(indoor, axis=1) # For broadcasting with BS dim
    716         indoor_mask = tf.where(indoor, tf.constant(1.0, self._dtype.real_dtype),
    717             tf.constant(0.0, self._dtype.real_dtype))
    718 
    719         # Sample the indoor 2D distances for each BS-UT link
    720         self._distance_2d_in = sionna.config.tf_rng.uniform(
    721             shape=[self.batch_size, self.num_bs, self.num_ut],
    722             minval=self.min_2d_in,
    723             maxval=self.max_2d_in,
    724             dtype=self._dtype.real_dtype) * indoor_mask
    725         # Compute the outdoor 2D distances
    726         self._distance_2d_out = self.distance_2d - self._distance_2d_in
    727         # Compute the indoor 3D distances
    728         self._distance_3d_in = ((self._distance_2d_in/self.distance_2d)
    729             *self.distance_3d)
    730         # Compute the outdoor 3D distances
    731         self._distance_3d_out = self.distance_3d - self._distance_3d_in
    732 
    733     def _load_params(self):
    734         r"""Load the configuration files corresponding to the 3 possible states
    735         of UTs: LoS, NLoS, and O2I"""
    736 
    737         source = files(models).joinpath(self.o2i_parameter_filepath)
    738         # pylint: disable=unspecified-encoding
    739         with open(source) as f:
    740             self._params_o2i = json.load(f)
    741 
    742         for param_name in self._params_o2i :
    743             v = self._params_o2i[param_name]
    744             if isinstance(v, float):
    745                 self._params_o2i[param_name] = tf.constant(v,
    746                                                     self._dtype.real_dtype)
    747             elif isinstance(v, int):
    748                 self._params_o2i[param_name] = tf.constant(v, tf.int32)
    749 
    750         source = files(models).joinpath(self.los_parameter_filepath)
    751         # pylint: disable=unspecified-encoding
    752         with open(source) as f:
    753             self._params_los = json.load(f)
    754 
    755         for param_name in self._params_los :
    756             v = self._params_los[param_name]
    757             if isinstance(v, float):
    758                 self._params_los[param_name] = tf.constant(v,
    759                                                     self._dtype.real_dtype)
    760             elif isinstance(v, int):
    761                 self._params_los[param_name] = tf.constant(v, tf.int32)
    762 
    763         source = files(models).joinpath(self.nlos_parameter_filepath)
    764         # pylint: disable=unspecified-encoding
    765         with open(source) as f:
    766             self._params_nlos = json.load(f)
    767 
    768         for param_name in self._params_nlos :
    769             v = self._params_nlos[param_name]
    770             if isinstance(v, float):
    771                 self._params_nlos[param_name] = tf.constant(v,
    772                                                         self._dtype.real_dtype)
    773             elif isinstance(v, int):
    774                 self._params_nlos[param_name] = tf.constant(v, tf.int32)
    775 
    776     @abstractmethod
    777     def _compute_lsp_log_mean_std(self):
    778         r"""Computes the mean and standard deviations of LSPs in log-domain"""
    779         pass
    780 
    781     @abstractmethod
    782     def _compute_pathloss_basic(self):
    783         r"""Computes the basic component of the pathloss [dB]"""
    784         pass