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