cdl.py (29184B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 """Clustered delay line (CDL) channel model from 3GPP TR38.901 specification""" 6 7 8 import json 9 from importlib_resources import files 10 import tensorflow as tf 11 from tensorflow import cos, sin 12 import numpy as np 13 14 from sionna.channel.utils import deg_2_rad 15 from sionna.channel import ChannelModel 16 from sionna import PI 17 from sionna import config 18 from sionna.utils.tensors import insert_dims 19 from . import Topology, ChannelCoefficientsGenerator 20 from . import Rays 21 22 from . import models # pylint: disable=relative-beyond-top-level 23 24 class CDL(ChannelModel): 25 # pylint: disable=line-too-long 26 r"""CDL(model, delay_spread, carrier_frequency, ut_array, bs_array, direction, min_speed=0., max_speed=None, dtype=tf.complex64) 27 28 Clustered delay line (CDL) channel model from the 3GPP [TR38901]_ specification. 29 30 The power delay profiles (PDPs) are normalized to have a total energy of one. 31 32 If a minimum speed and a maximum speed are specified such that the 33 maximum speed is greater than the minimum speed, then UTs speeds are 34 randomly and uniformly sampled from the specified interval for each link 35 and each batch example. 36 37 The CDL model only works for systems with a single transmitter and a single 38 receiver. The transmitter and receiver can be equipped with multiple 39 antennas. 40 41 Example 42 -------- 43 44 The following code snippet shows how to setup a CDL channel model assuming 45 an OFDM waveform: 46 47 >>> # Panel array configuration for the transmitter and receiver 48 >>> bs_array = PanelArray(num_rows_per_panel = 4, 49 ... num_cols_per_panel = 4, 50 ... polarization = 'dual', 51 ... polarization_type = 'cross', 52 ... antenna_pattern = '38.901', 53 ... carrier_frequency = 3.5e9) 54 >>> ut_array = PanelArray(num_rows_per_panel = 1, 55 ... num_cols_per_panel = 1, 56 ... polarization = 'single', 57 ... polarization_type = 'V', 58 ... antenna_pattern = 'omni', 59 ... carrier_frequency = 3.5e9) 60 >>> # CDL channel model 61 >>> cdl = CDL(model = "A", 62 >>> delay_spread = 300e-9, 63 ... carrier_frequency = 3.5e9, 64 ... ut_array = ut_array, 65 ... bs_array = bs_array, 66 ... direction = 'uplink') 67 >>> channel = OFDMChannel(channel_model = cdl, 68 ... resource_grid = rg) 69 70 where ``rg`` is an instance of :class:`~sionna.ofdm.ResourceGrid`. 71 72 Notes 73 ------ 74 75 The following tables from [TR38901]_ provide typical values for the delay 76 spread. 77 78 +--------------------------+-------------------+ 79 | Model | Delay spread [ns] | 80 +==========================+===================+ 81 | Very short delay spread | :math:`10` | 82 +--------------------------+-------------------+ 83 | Short short delay spread | :math:`10` | 84 +--------------------------+-------------------+ 85 | Nominal delay spread | :math:`100` | 86 +--------------------------+-------------------+ 87 | Long delay spread | :math:`300` | 88 +--------------------------+-------------------+ 89 | Very long delay spread | :math:`1000` | 90 +--------------------------+-------------------+ 91 92 +-----------------------------------------------+------+------+----------+-----+----+-----+ 93 | Delay spread [ns] | Frequency [GHz] | 94 + +------+------+----+-----+-----+----+-----+ 95 | | 2 | 6 | 15 | 28 | 39 | 60 | 70 | 96 +========================+======================+======+======+====+=====+=====+====+=====+ 97 | Indoor office | Short delay profile | 20 | 16 | 16 | 16 | 16 | 16 | 16 | 98 | +----------------------+------+------+----+-----+-----+----+-----+ 99 | | Normal delay profile | 39 | 30 | 24 | 20 | 18 | 16 | 16 | 100 | +----------------------+------+------+----+-----+-----+----+-----+ 101 | | Long delay profile | 59 | 53 | 47 | 43 | 41 | 38 | 37 | 102 +------------------------+----------------------+------+------+----+-----+-----+----+-----+ 103 | UMi Street-canyon | Short delay profile | 65 | 45 | 37 | 32 | 30 | 27 | 26 | 104 | +----------------------+------+------+----+-----+-----+----+-----+ 105 | | Normal delay profile | 129 | 93 | 76 | 66 | 61 | 55 | 53 | 106 | +----------------------+------+------+----+-----+-----+----+-----+ 107 | | Long delay profile | 634 | 316 | 307| 301 | 297 | 293| 291 | 108 +------------------------+----------------------+------+------+----+-----+-----+----+-----+ 109 | UMa | Short delay profile | 93 | 93 | 85 | 80 | 78 | 75 | 74 | 110 | +----------------------+------+------+----+-----+-----+----+-----+ 111 | | Normal delay profile | 363 | 363 | 302| 266 | 249 |228 | 221 | 112 | +----------------------+------+------+----+-----+-----+----+-----+ 113 | | Long delay profile | 1148 | 1148 | 955| 841 | 786 | 720| 698 | 114 +------------------------+----------------------+------+------+----+-----+-----+----+-----+ 115 | RMa / RMa O2I | Short delay profile | 32 | 32 | N/A| N/A | N/A | N/A| N/A | 116 | +----------------------+------+------+----+-----+-----+----+-----+ 117 | | Normal delay profile | 37 | 37 | N/A| N/A | N/A | N/A| N/A | 118 | +----------------------+------+------+----+-----+-----+----+-----+ 119 | | Long delay profile | 153 | 153 | N/A| N/A | N/A | N/A| N/A | 120 +------------------------+----------------------+------+------+----+-----+-----+----+-----+ 121 | UMi / UMa O2I | Normal delay profile | 242 | 122 | +----------------------+-----------------------------------------+ 123 | | Long delay profile | 616 | 124 +------------------------+----------------------+-----------------------------------------+ 125 126 Parameters 127 ----------- 128 129 model : str 130 CDL model to use. Must be one of "A", "B", "C", "D" or "E". 131 132 delay_spread : float 133 RMS delay spread [s]. 134 135 carrier_frequency : float 136 Carrier frequency [Hz]. 137 138 ut_array : PanelArray 139 Panel array used by the UTs. All UTs share the same antenna array 140 configuration. 141 142 bs_array : PanelArray 143 Panel array used by the Bs. All BSs share the same antenna array 144 configuration. 145 146 direction : str 147 Link direction. Must be either "uplink" or "downlink". 148 149 ut_orientation : `None` or Tensor of shape [3], tf.float 150 Orientation of the UT. If set to `None`, [:math:`\pi`, 0, 0] is used. 151 Defaults to `None`. 152 153 bs_orientation : `None` or Tensor of shape [3], tf.float 154 Orientation of the BS. If set to `None`, [0, 0, 0] is used. 155 Defaults to `None`. 156 157 min_speed : float 158 Minimum speed [m/s]. Defaults to 0. 159 160 max_speed : None or float 161 Maximum speed [m/s]. If set to `None`, 162 then ``max_speed`` takes the same value as ``min_speed``. 163 Defaults to `None`. 164 165 dtype : Complex tf.DType 166 Defines the datatype for internal calculations and the output 167 dtype. Defaults to `tf.complex64`. 168 169 Input 170 ----- 171 172 batch_size : int 173 Batch size 174 175 num_time_steps : int 176 Number of time steps 177 178 sampling_frequency : float 179 Sampling frequency [Hz] 180 181 Output 182 ------- 183 a : [batch size, num_rx = 1, num_rx_ant, num_tx = 1, num_tx_ant, num_paths, num_time_steps], tf.complex 184 Path coefficients 185 186 tau : [batch size, num_rx = 1, num_tx = 1, num_paths], tf.float 187 Path delays [s] 188 189 """ 190 191 # Number of rays per cluster is set to 20 for CDL 192 NUM_RAYS = 20 193 194 def __init__( self, 195 model, 196 delay_spread, 197 carrier_frequency, 198 ut_array, 199 bs_array, 200 direction, 201 ut_orientation=None, 202 bs_orientation=None, 203 min_speed=0., 204 max_speed=None, 205 dtype=tf.complex64): 206 207 assert dtype.is_complex, "dtype must be a complex datatype" 208 self._dtype = dtype 209 real_dtype = dtype.real_dtype 210 self._real_dtype = real_dtype 211 212 assert direction in('uplink', 'downlink'), "Invalid link direction" 213 self._direction = direction 214 215 # If no orientation is defined by the user, set to default values 216 # that make sense 217 if ut_orientation is None: 218 ut_orientation = tf.constant([PI, 0.0, 0.0], real_dtype) 219 if bs_orientation is None: 220 bs_orientation = tf.zeros([3], real_dtype) 221 222 # Setting which from UT or BS is the transmitter and which is the 223 # receiver according to the link direction 224 if self._direction == 'downlink': 225 self._moving_end = 'rx' 226 self._tx_array = bs_array 227 self._rx_array = ut_array 228 self._tx_orientation = bs_orientation 229 self._rx_orientation = ut_orientation 230 elif self._direction == 'uplink': 231 self._moving_end = 'tx' 232 self._tx_array = ut_array 233 self._rx_array = bs_array 234 self._tx_orientation = ut_orientation 235 self._rx_orientation = bs_orientation 236 237 self._carrier_frequency = tf.constant(carrier_frequency, real_dtype) 238 self._delay_spread = tf.constant(delay_spread, real_dtype) 239 self._min_speed = tf.constant(min_speed, real_dtype) 240 if max_speed is None: 241 self._max_speed = self._min_speed 242 else: 243 assert max_speed >= min_speed, \ 244 "min_speed cannot be larger than max_speed" 245 self._max_speed = tf.constant(max_speed, real_dtype) 246 247 # Loading the model parameters 248 assert model in ("A", "B", "C", "D", "E"), "Invalid CDL model" 249 if model == 'A': 250 parameters_fname = "CDL-A.json" 251 elif model == 'B': 252 parameters_fname = "CDL-B.json" 253 elif model == 'C': 254 parameters_fname = "CDL-C.json" 255 elif model == 'D': 256 parameters_fname = "CDL-D.json" 257 else: # 'E' 258 parameters_fname = "CDL-E.json" 259 self._load_parameters(parameters_fname) 260 261 # Channel coefficient generator for sampling channel impulse responses 262 self._cir_sampler = ChannelCoefficientsGenerator(carrier_frequency, 263 self._tx_array, 264 self._rx_array, 265 subclustering=False, 266 dtype=dtype) 267 268 def __call__(self, batch_size, num_time_steps, sampling_frequency): 269 270 ## Topology for generating channel coefficients 271 # Sample random velocities 272 v_r = config.tf_rng.uniform(shape=[batch_size, 1], 273 minval=self._min_speed, 274 maxval=self._max_speed, 275 dtype=self._real_dtype) 276 v_phi = config.tf_rng.uniform(shape=[batch_size, 1], 277 minval=0.0, 278 maxval=2.*PI, 279 dtype=self._real_dtype) 280 v_theta = config.tf_rng.uniform(shape=[batch_size, 1], 281 minval=0.0, 282 maxval=PI, 283 dtype=self._real_dtype) 284 velocities = tf.stack([v_r*cos(v_phi)*sin(v_theta), 285 v_r*sin(v_phi)*sin(v_theta), 286 v_r*cos(v_theta)], axis=-1) 287 los = tf.fill([batch_size, 1, 1], self._los) 288 los_aoa = tf.tile(self._los_aoa, [batch_size, 1, 1]) 289 los_zoa = tf.tile(self._los_zoa, [batch_size, 1, 1]) 290 los_aod = tf.tile(self._los_aod, [batch_size, 1, 1]) 291 los_zod = tf.tile(self._los_zod, [batch_size, 1, 1]) 292 distance_3d = tf.zeros([batch_size, 1, 1], self._real_dtype) 293 tx_orientation = tf.tile(insert_dims(self._tx_orientation, 2, 0), 294 [batch_size, 1, 1]) 295 rx_orientation = tf.tile(insert_dims(self._rx_orientation, 2, 0), 296 [batch_size, 1, 1]) 297 k_factor = tf.tile(self._k_factor, [batch_size, 1, 1]) 298 topology = Topology(velocities=velocities, 299 moving_end=self._moving_end, 300 los_aoa=los_aoa, 301 los_zoa=los_zoa, 302 los_aod=los_aod, 303 los_zod=los_zod, 304 los=los, 305 distance_3d=distance_3d, 306 tx_orientations=tx_orientation, 307 rx_orientations=rx_orientation) 308 309 # Rays used to generate the channel model 310 delays = tf.tile(self._delays*self._delay_spread, [batch_size, 1, 1, 1]) 311 powers = tf.tile(self._powers, [batch_size, 1, 1, 1]) 312 aoa = tf.tile(self._aoa, [batch_size, 1, 1, 1, 1]) 313 aod = tf.tile(self._aod, [batch_size, 1, 1, 1, 1]) 314 zoa = tf.tile(self._zoa, [batch_size, 1, 1, 1, 1]) 315 zod = tf.tile(self._zod, [batch_size, 1, 1, 1, 1]) 316 xpr = tf.tile(self._xpr, [batch_size, 1, 1, 1, 1]) 317 318 # Random coupling 319 aoa, aod, zoa, zod = self._random_coupling(aoa, aod, zoa, zod) 320 321 rays = Rays(delays=delays, 322 powers=powers, 323 aoa=aoa, 324 aod=aod, 325 zoa=zoa, 326 zod=zod, 327 xpr=xpr) 328 329 # Sampling channel impulse responses 330 # pylint: disable=unbalanced-tuple-unpacking 331 h, delays = self._cir_sampler(num_time_steps, sampling_frequency, 332 k_factor, rays, topology) 333 334 # Reshaping to match the expected output 335 h = tf.transpose(h, [0, 2, 4, 1, 5, 3, 6]) 336 delays = tf.transpose(delays, [0, 2, 1, 3]) 337 338 # Stop gadients to avoid useless backpropagation 339 h = tf.stop_gradient(h) 340 delays = tf.stop_gradient(delays) 341 342 return h, delays 343 344 @property 345 def num_clusters(self): 346 r"""Number of paths (:math:`M`)""" 347 return self._num_clusters 348 349 @property 350 def los(self): 351 r"""`True` is this is a LoS model. `False` otherwise.""" 352 return self._los 353 354 @property 355 def k_factor(self): 356 r"""K-factor in linear scale. Only available with LoS models.""" 357 assert self._los, "This property is only available for LoS models" 358 # We return the K-factor for the path with zero-delay, and not for the 359 # entire PDP. 360 return self._k_factor[0,0,0]/self._powers[0,0,0,0] 361 362 @property 363 def delays(self): 364 r"""Path delays [s]""" 365 return self._delays[0,0,0]*self._delay_spread 366 367 @property 368 def powers(self): 369 r"""Path powers in linear scale""" 370 if self.los: 371 k_factor = self._k_factor[0,0,0] 372 nlos_powers = self._powers[0,0,0] 373 # Power of the LoS path 374 p0 = k_factor + nlos_powers[0] 375 returned_powers = tf.tensor_scatter_nd_update(nlos_powers, 376 [[0]], [p0]) 377 returned_powers = returned_powers / (k_factor+1.) 378 else: 379 returned_powers = self._powers[0,0,0] 380 return returned_powers 381 382 @property 383 def delay_spread(self): 384 r"""RMS delay spread [s]""" 385 return self._delay_spread 386 387 @delay_spread.setter 388 def delay_spread(self, value): 389 self._delay_spread = value 390 391 ########################################### 392 # Utility functions 393 ########################################### 394 395 def _load_parameters(self, fname): 396 r"""Load parameters of a CDL model. 397 398 The model parameters are stored as JSON files with the following keys: 399 * los : boolean that indicates if the model is a LoS model 400 * num_clusters : integer corresponding to the number of clusters (paths) 401 * delays : List of path delays in ascending order normalized by the RMS 402 delay spread 403 * powers : List of path powers in dB scale 404 * aod : Paths AoDs [degree] 405 * aoa : Paths AoAs [degree] 406 * zod : Paths ZoDs [degree] 407 * zoa : Paths ZoAs [degree] 408 * cASD : Cluster ASD 409 * cASA : Cluster ASA 410 * cZSD : Cluster ZSD 411 * cZSA : Cluster ZSA 412 * xpr : XPR in dB 413 414 For LoS models, the two first paths have zero delay, and are assumed 415 to correspond to the specular and NLoS component, in this order. 416 417 Input 418 ------ 419 fname : str 420 File from which to load the parameters. 421 422 Output 423 ------ 424 None 425 """ 426 427 # Load the JSON configuration file 428 source = files(models).joinpath(fname) 429 # pylint: disable=unspecified-encoding 430 with open(source) as parameter_file: 431 params = json.load(parameter_file) 432 433 # LoS scenario ? 434 self._los = tf.cast(params['los'], tf.bool) 435 436 # Loading cluster delays and powers 437 self._num_clusters = tf.constant(params['num_clusters'], tf.int32) 438 439 # Loading the rays components, all of shape [num clusters] 440 delays = tf.constant(params['delays'], self._real_dtype) 441 powers = tf.constant(np.power(10.0, np.array(params['powers'])/10.0), 442 self._real_dtype) 443 444 # Normalize powers 445 norm_fact = tf.reduce_sum(powers) 446 powers = powers / norm_fact 447 448 # Loading the angles and angle spreads of arrivals and departure 449 c_aod = tf.constant(params['cASD'], self._real_dtype) 450 aod = tf.constant(params['aod'], self._real_dtype) 451 c_aoa = tf.constant(params['cASA'], self._real_dtype) 452 aoa = tf.constant(params['aoa'], self._real_dtype) 453 c_zod = tf.constant(params['cZSD'], self._real_dtype) 454 zod = tf.constant(params['zod'], self._real_dtype) 455 c_zoa = tf.constant(params['cZSA'], self._real_dtype) 456 zoa = tf.constant(params['zoa'], self._real_dtype) 457 458 # If LoS, compute the model K-factor following 7.7.6 of TR38.901 and 459 # the LoS path angles of arrival and departure. 460 # We remove the specular component from the arrays, as it will be added 461 # separately when computing the channel coefficients 462 if self._los: 463 # Extract the specular component, as it will be added separately by 464 # the CIR generator. 465 los_power = powers[0] 466 powers = powers[1:] 467 delays = delays[1:] 468 los_aod = aod[0] 469 aod = aod[1:] 470 los_aoa = aoa[0] 471 aoa = aoa[1:] 472 los_zod = zod[0] 473 zod = zod[1:] 474 los_zoa = zoa[0] 475 zoa = zoa[1:] 476 477 # The CIR generator scales all NLoS powers by 1/(K+1), 478 # where K = k_factor, and adds to the path with zero delay a 479 # specular component with power K/(K+1). 480 # Note that all the paths are scaled by 1/(K+1), including the ones 481 # with non-zero delays. 482 # We re-normalized the NLoS power paths to ensure total unit energy 483 # after scaling 484 norm_fact = tf.reduce_sum(powers) 485 powers = powers / norm_fact 486 # To ensure that the path with zero delay the ratio between the 487 # specular component and the NLoS component has the same ratio as 488 # in the CDL PDP, we need to set the K-factor to to the value of 489 # the specular component. The ratio between the other paths is 490 # preserved as all paths are scaled by 1/(K+1). 491 # Note that because of the previous normalization of the NLoS paths' 492 # powers, which ensured that their total power is 1, 493 # this is equivalent to defining the K factor as done in 3GPP 494 # specifications (see step 11): 495 # K = (power of specular component)/(total power of the NLoS paths) 496 k_factor = los_power/norm_fact 497 498 los_aod = deg_2_rad(los_aod) 499 los_aoa = deg_2_rad(los_aoa) 500 los_zod = deg_2_rad(los_zod) 501 los_zoa = deg_2_rad(los_zoa) 502 else: 503 # For NLoS models, we need to give value to the K-factor and LoS 504 # angles, but they will not be used. 505 k_factor = tf.ones((), self._real_dtype) 506 507 los_aod = tf.zeros((), self._real_dtype) 508 los_aoa = tf.zeros((), self._real_dtype) 509 los_zod = tf.zeros((), self._real_dtype) 510 los_zoa = tf.zeros((), self._real_dtype) 511 512 # Generate clusters rays and convert angles to radian 513 aod = self._generate_rays(aod, c_aod) # [num clusters, num rays] 514 aod = deg_2_rad(aod) # [num clusters, num rays] 515 aoa = self._generate_rays(aoa, c_aoa) # [num clusters, num rays] 516 aoa = deg_2_rad(aoa) # [num clusters, num rays] 517 zod = self._generate_rays(zod, c_zod) # [num clusters, num rays] 518 zod = deg_2_rad(zod) # [num clusters, num rays] 519 zoa = self._generate_rays(zoa, c_zoa) # [num clusters, num rays] 520 zoa = deg_2_rad(zoa) # [num clusters, num rays] 521 522 # Store LoS power 523 if self._los: 524 self._los_power = los_power 525 526 # Reshape the as expected by the channel impulse response generator 527 self._k_factor = self._reshape_for_cir_computation(k_factor) 528 los_aod = self._reshape_for_cir_computation(los_aod) 529 los_aoa = self._reshape_for_cir_computation(los_aoa) 530 los_zod = self._reshape_for_cir_computation(los_zod) 531 los_zoa = self._reshape_for_cir_computation(los_zoa) 532 self._delays = self._reshape_for_cir_computation(delays) 533 self._powers = self._reshape_for_cir_computation(powers) 534 aod = self._reshape_for_cir_computation(aod) 535 aoa = self._reshape_for_cir_computation(aoa) 536 zod = self._reshape_for_cir_computation(zod) 537 zoa = self._reshape_for_cir_computation(zoa) 538 539 # Setting angles of arrivals and departures according to the link 540 # direction 541 if self._direction == 'downlink': 542 self._los_aoa = los_aoa 543 self._los_zoa = los_zoa 544 self._los_aod = los_aod 545 self._los_zod = los_zod 546 self._aoa = aoa 547 self._zoa = zoa 548 self._aod = aod 549 self._zod = zod 550 elif self._direction == 'uplink': 551 self._los_aoa = los_aod 552 self._los_zoa = los_zod 553 self._los_aod = los_aoa 554 self._los_zod = los_zoa 555 self._aoa = aod 556 self._zoa = zod 557 self._aod = aoa 558 self._zod = zoa 559 560 # XPR 561 xpr = params['xpr'] 562 xpr = np.power(10.0, xpr/10.0) 563 xpr = tf.constant(xpr, self._real_dtype) 564 xpr = tf.fill([self._num_clusters, CDL.NUM_RAYS], xpr) 565 self._xpr = self._reshape_for_cir_computation(xpr) 566 567 def _generate_rays(self, angles, c): 568 r""" 569 Generate rays from ``angles`` (which could be ZoD, ZoA, AoD, or AoA) and 570 the angle spread ``c`` using equation 7.7-0a of TR38.901 specifications 571 572 Input 573 ------- 574 angles : [num cluster], float 575 Tensor of angles with shape `[num_clusters]` 576 577 c : float 578 Angle spread 579 580 Output 581 ------- 582 ray_angles : float 583 A tensor of shape [num clusters, num rays] containing the angle of 584 each ray 585 """ 586 587 # Basis vector of offset angle from table 7.5-3 from specfications 588 # TR38.901 589 basis_vector = tf.constant([0.0447, -0.0447, 590 0.1413, -0.1413, 591 0.2492, -0.2492, 592 0.3715, -0.3715, 593 0.5129, -0.5129, 594 0.6797, -0.6797, 595 0.8844, -0.8844, 596 1.1481, -1.1481, 597 1.5195, -1.5195, 598 2.1551, -2.1551], self._real_dtype) 599 600 # Reshape for broadcasting 601 # [1, num rays = 20] 602 basis_vector = tf.expand_dims(basis_vector, axis=0) 603 # [num clusters, 1] 604 angles = tf.expand_dims(angles, axis=1) 605 606 # Generate rays following 7.7-0a 607 # [num clusters, num rays = 20] 608 ray_angles = angles + c*basis_vector 609 610 return ray_angles 611 612 def _reshape_for_cir_computation(self, array): 613 r""" 614 Add three leading dimensions to array, with shape [1, num_tx, num_rx], 615 to reshape it as expected by the channel impulse response sampler. 616 617 Input 618 ------- 619 array : Any shape, float 620 Array to reshape 621 622 Output 623 ------- 624 reshaped_array : Tensor, float 625 The tensor ``array`` expanded with 3 dimensions for the batch, 626 number of tx, and number of rx. 627 """ 628 629 array_rank = tf.rank(array) 630 tiling = tf.constant([1, 1, 1], tf.int32) 631 if array_rank > 0: 632 tiling = tf.concat([tiling, tf.ones([array_rank],tf.int32)], axis=0) 633 634 array = insert_dims(array, 3, 0) 635 array = tf.tile(array, tiling) 636 637 return array 638 639 def _shuffle_angles(self, angles): 640 # pylint: disable=line-too-long 641 """ 642 Randomly shuffle a tensor carrying azimuth/zenith angles 643 of arrival/departure. 644 645 Input 646 ------ 647 angles : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 648 Angles to shuffle 649 650 Output 651 ------- 652 shuffled_angles : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 653 Shuffled ``angles`` 654 """ 655 656 # Create randomly shuffled indices by arg-sorting samples from a random 657 # normal distribution 658 random_numbers = config.tf_rng.normal(tf.shape(angles)) 659 shuffled_indices = tf.argsort(random_numbers) 660 # Shuffling the angles 661 shuffled_angles = tf.gather(angles,shuffled_indices, batch_dims=4) 662 return shuffled_angles 663 664 def _random_coupling(self, aoa, aod, zoa, zod): 665 # pylint: disable=line-too-long 666 """ 667 Randomly couples the angles within a cluster for both azimuth and 668 elevation. 669 670 Step 8 in TR 38.901 specification. 671 672 Input 673 ------ 674 aoa : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 675 Paths azimuth angles of arrival [degree] (AoA) 676 677 aod : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 678 Paths azimuth angles of departure (AoD) [degree] 679 680 zoa : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 681 Paths zenith angles of arrival [degree] (ZoA) 682 683 zod : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 684 Paths zenith angles of departure [degree] (ZoD) 685 686 Output 687 ------- 688 shuffled_aoa : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 689 Shuffled `aoa` 690 691 shuffled_aod : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 692 Shuffled `aod` 693 694 shuffled_zoa : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 695 Shuffled `zoa` 696 697 shuffled_zod : [batch size, num of BSs, num of UTs, maximum number of clusters, number of rays], tf.float 698 Shuffled `zod` 699 """ 700 shuffled_aoa = self._shuffle_angles(aoa) 701 shuffled_aod = self._shuffle_angles(aod) 702 shuffled_zoa = self._shuffle_angles(zoa) 703 shuffled_zod = self._shuffle_angles(zod) 704 705 return shuffled_aoa, shuffled_aod, shuffled_zoa, shuffled_zod