radio_device.py (6237B)
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 implementing a radio device, which can be either a transmitter or a 7 receiver. 8 """ 9 10 import tensorflow as tf 11 12 from .object import Object 13 from .utils import normalize, theta_phi_from_unit_vec 14 from sionna.constants import PI 15 16 17 class RadioDevice(Object): 18 # pylint: disable=line-too-long 19 r"""RadioDevice(name, position, orientation=[0.,0.,0.], look_at=None, dtype=tf.complex64) 20 21 Class defining a generic radio device. 22 23 :class:`~sionna.rt.Transmitter`, :class:`~sionna.rt.Receiver`, 24 and :class:`~sionna.rt.RIS` 25 26 inherit from this class and should be used. 27 28 Parameters 29 ---------- 30 name : str 31 Name 32 33 position : [3], float 34 Position :math:`(x,y,z)` as three-dimensional vector 35 36 orientation : [3], float 37 Orientation :math:`(\alpha, \beta, \gamma)` specified 38 through three angles corresponding to a 3D rotation 39 as defined in :eq:`rotation`. 40 This parameter is ignored if ``look_at`` is not `None`. 41 Defaults to [0,0,0]. 42 43 look_at : [3], float | :class:`~sionna.rt.Transmitter` | :class:`~sionna.rt.Receiver` | :class:`~sionna.rt.RIS` | :class:`~sionna.rt.Camera` | None 44 A position or the instance of a :class:`~sionna.rt.Transmitter`, 45 :class:`~sionna.rt.Receiver`, :class:`~sionna.rt.RIS`, or :class:`~sionna.rt.Camera` to look at. 46 If set to `None`, then ``orientation`` is used to orientate the device. 47 48 color : [3], float 49 Defines the RGB (red, green, blue) ``color`` parameter for the device as displayed in the previewer and renderer. 50 Each RGB component must have a value within the range :math:`\in [0,1]`. 51 52 dtype : tf.complex 53 Datatype to be used in internal calculations. 54 Defaults to `tf.complex64`. 55 """ 56 57 def __init__(self, 58 name, 59 position=None, 60 orientation=(0.,0.,0.), 61 look_at=None, 62 color=(0,0,0), 63 dtype=tf.complex64, 64 **kwargs): 65 66 if dtype not in (tf.complex64, tf.complex128): 67 raise ValueError("`dtype` must be tf.complex64 or tf.complex128`") 68 self._dtype = dtype 69 self._rdtype = dtype.real_dtype 70 self.color = color 71 72 # Position and orientation are set through this call 73 super().__init__(name=name, 74 position=position, 75 orientation=orientation, 76 look_at=look_at, 77 **kwargs) 78 79 @property 80 def position(self): 81 """ 82 [3], tf.float : Get/set the position 83 """ 84 return self._position 85 86 @position.setter 87 def position(self, v): 88 if isinstance(v, tf.Variable): 89 if v.dtype != self._rdtype: 90 msg = f"`position` must have dtype={self._rdtype}" 91 raise TypeError(msg) 92 else: 93 self._position = v 94 else: 95 self._position = tf.cast(v, dtype=self._rdtype) 96 97 @property 98 def orientation(self): 99 """ 100 [3], tf.float : Get/set the orientation 101 """ 102 return self._orientation 103 104 @orientation.setter 105 def orientation(self, v): 106 if isinstance(v, tf.Variable): 107 if v.dtype != self._rdtype: 108 msg = f"`orientation` must have dtype={self._rdtype}" 109 raise TypeError(msg) 110 else: 111 self._orientation = v 112 else: 113 self._orientation = tf.cast(v, dtype=self._rdtype) 114 115 def look_at(self, target): 116 # pylint: disable=line-too-long 117 r""" 118 Sets the orientation so that the x-axis points toward a 119 position, radio device, RIS, or camera. 120 121 Given a point :math:`\mathbf{x}\in\mathbb{R}^3` with spherical angles 122 :math:`\theta` and :math:`\varphi`, the orientation of the radio device 123 will be set equal to :math:`(\varphi, \frac{\pi}{2}-\theta, 0.0)`. 124 125 Input 126 ----- 127 target : [3], float | :class:`~sionna.rt.Transmitter` | :class:`~sionna.rt.Receiver` | :class:`~sionna.rt.RIS` | :class:`~sionna.rt.Camera` | str 128 A position or the name or instance of a 129 :class:`~sionna.rt.Transmitter`, :class:`~sionna.rt.Receiver`, 130 :class:`~sionna.rt.RIS`, or 131 :class:`~sionna.rt.Camera` in the scene to look at. 132 """ 133 # Get position to look at 134 if isinstance(target, str): 135 obj = self.scene.get(target) 136 if not isinstance(obj, Object): 137 raise ValueError(f"No camera, device, or object named '{target}' found.") 138 else: 139 target = obj.position 140 elif isinstance(target, Object): 141 target = target.position 142 else: 143 target = tf.cast(target, dtype=self._rdtype) 144 if not target.shape[0]==3: 145 raise ValueError("`target` must be a three-element vector)") 146 147 # Compute angles relative to LCS 148 x = target - self.position 149 x, _ = normalize(x) 150 theta, phi = theta_phi_from_unit_vec(x) 151 alpha = phi # Rotation around z-axis 152 beta = theta-PI/2 # Rotation around y-axis 153 gamma = 0.0 # Rotation around x-axis 154 self.orientation = (alpha, beta, gamma) 155 156 @property 157 def color(self): 158 r""" 159 [3], float : Get/set the the RGB (red, green, blue) color for the device as displayed in the previewer and renderer. 160 Each RGB component must have a value within the range :math:`\in [0,1]`. 161 """ 162 return self._color 163 164 @color.setter 165 def color(self, new_color): 166 new_color = tf.cast(new_color, dtype=self._rdtype) 167 if not (tf.rank(new_color) == 1 and new_color.shape[0] == 3): 168 msg = "Color must be shaped as [r,g,b] (rank=1 and shape=[3])" 169 raise ValueError(msg) 170 if tf.reduce_any(new_color < 0.) or tf.reduce_any(new_color > 1.): 171 msg = "Color components must be in the range (0,1)" 172 raise ValueError(msg) 173 self._color = new_color