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

antenna_array.py (9203B)


      1 #
      2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      3 # SPDX-License-Identifier: Apache-2.0
      4 #
      5 """
      6 Implements classes and methods related to antenna arrays
      7 """
      8 import tensorflow as tf
      9 import numpy as np
     10 import matplotlib.pyplot as plt
     11 from matplotlib.markers import MarkerStyle
     12 from .antenna import Antenna
     13 from . import scene
     14 from .utils import rotate
     15 
     16 class AntennaArray():
     17     # pylint: disable=line-too-long
     18     r"""
     19     Class implementing an antenna array
     20 
     21     An antenna array is composed of identical antennas that are placed
     22     at different positions. The ``positions`` parameter can be assigned
     23     to a TensorFlow variable or tensor.
     24 
     25     .. code-block:: Python
     26 
     27         array = AntennaArray(antenna=Antenna("tr38901", "V"),
     28                              positions=tf.Variable([[0,0,0], [0, 1, 1]]))
     29 
     30     Parameters
     31     ----------
     32     antenna : :class:`~sionna.rt.Antenna`
     33         Antenna instance
     34 
     35     positions : [array_size, 3], array_like
     36         Array of relative positions :math:`(x,y,z)` [m] of each
     37         antenna (dual-polarized antennas are counted as a single antenna
     38         and share the same position).
     39         The absolute position of the antennas is obtained by
     40         adding the position of the :class:`~sionna.rt.Transmitter`
     41         or :class:`~sionna.rt.Receiver` using it.
     42 
     43     dtype : tf.complex64 or tf.complex128
     44         Data type used for all computations.
     45         Defaults to `tf.complex64`.
     46     """
     47     def __init__(self, antenna, positions, dtype=tf.complex64):
     48         super().__init__()
     49 
     50         if dtype not in (tf.complex64, tf.complex128):
     51             raise ValueError("`dtype` must be tf.complex64 or tf.complex128`")
     52         self._rdtype = dtype.real_dtype
     53         self.antenna = antenna
     54         self.positions = positions
     55 
     56     @property
     57     def antenna(self):
     58         """
     59         :class:`~sionna.rt.Antenna` : Get/set the antenna
     60         """
     61         return self._antenna
     62 
     63     @antenna.setter
     64     def antenna(self, antenna):
     65         if not isinstance(antenna, Antenna):
     66             raise TypeError("``antenna`` must be an instance of Antenna.")
     67         self._antenna = antenna
     68 
     69     @property
     70     def positions(self):
     71         """
     72         [array_size, 3], `tf.float` : Get/set  array of relative positions
     73         :math:`(x,y,z)` [m] of each antenna (dual-polarized antennas are
     74         counted as a single antenna and share the same position).
     75         """
     76         return self._positions
     77 
     78     @positions.setter
     79     def positions(self, positions):
     80         if isinstance(positions, tf.Variable):
     81             if positions.dtype != self._rdtype:
     82                 raise TypeError(f"`positions` must have dtype={self._rdtype}")
     83             else:
     84                 self._positions = positions
     85         else:
     86             self._positions = tf.cast(positions, self._rdtype)
     87 
     88     @property
     89     def num_ant(self):
     90         """
     91         int (read-only) : Number of linearly polarized antennas in the array.
     92             Dual-polarized antennas are counted as two linearly polarized
     93             antennas.
     94         """
     95         return self._positions.shape[0]*len(self._antenna.patterns)
     96 
     97     @property
     98     def array_size(self):
     99         """
    100         int (read-only) : Number of antennas in the array.
    101             Dual-polarized antennas are counted as a single antenna.
    102         """
    103         return self._positions.shape[0]
    104 
    105     def rotated_positions(self, orientation):
    106         r"""
    107         Get the antenna positions rotated according to ``orientation``
    108 
    109         Input
    110         ------
    111         orientation : [3], tf.float
    112             Orientation :math:`(\alpha, \beta, \gamma)` [rad] specified
    113             through three angles corresponding to a 3D rotation
    114             as defined in :eq:`rotation`.
    115 
    116         Output
    117         -------
    118         : [array_size, 3]
    119             Rotated positions
    120         """
    121         # [array_size, 3]
    122         rot_p = rotate(self.positions, orientation)
    123         return rot_p
    124 
    125 class PlanarArray(AntennaArray):
    126     # pylint: disable=line-too-long
    127     r"""
    128     Class implementing a planar antenna array
    129 
    130     The antennas are regularly spaced, located in the y-z plane, and
    131     numbered column-first from the top-left to bottom-right corner.
    132 
    133     Parameters
    134     ----------
    135     num_rows : int
    136         Number of rows
    137 
    138     num_cols : int
    139         Number of columns
    140 
    141     vertical_spacing : float
    142         Vertical antenna spacing [multiples of wavelength].
    143 
    144     horizontal_spacing : float
    145         Horizontal antenna spacing [multiples of wavelength].
    146 
    147     pattern : str, callable, or length-2 sequence of callables
    148         Antenna pattern. Either one of
    149         ["iso", "dipole", "hw_dipole", "tr38901"],
    150         or a callable, or a length-2 sequence of callables defining
    151         antenna patterns. In the latter case, the antennas are dual
    152         polarized and each callable defines the antenna pattern
    153         in one of the two orthogonal polarization directions.
    154         An antenna pattern is a callable that takes as inputs vectors of
    155         zenith and azimuth angles of the same length and returns for each
    156         pair the corresponding zenith and azimuth patterns. See :eq:`C` for
    157         more detail.
    158 
    159     polarization : str or None
    160         Type of polarization. For single polarization, must be "V" (vertical)
    161         or "H" (horizontal). For dual polarization, must be "VH" or "cross".
    162         Only needed if ``pattern`` is a string.
    163 
    164     polarization_model: int, one of [1,2]
    165         Polarization model to be used. Options `1` and `2`
    166         refer to :func:`~sionna.rt.antenna.polarization_model_1`
    167         and :func:`~sionna.rt.antenna.polarization_model_2`,
    168         respectively.
    169         Defaults to `2`.
    170 
    171     dtype : tf.complex64 or tf.complex128
    172         Datatype used for all computations.
    173         Defaults to `tf.complex64`.
    174 
    175     Example
    176     -------
    177     .. code-block:: Python
    178 
    179         array = PlanarArray(8,4, 0.5, 0.5, "tr38901", "VH")
    180         array.show()
    181 
    182     .. figure:: ../figures/antenna_array.png
    183         :align: center
    184         :scale: 100%
    185     """
    186     def __init__(self,
    187                  num_rows,
    188                  num_cols,
    189                  vertical_spacing,
    190                  horizontal_spacing,
    191                  pattern,
    192                  polarization=None,
    193                  polarization_model=2,
    194                  dtype=tf.complex64):
    195 
    196         if dtype not in (tf.complex64, tf.complex128):
    197             raise ValueError("`dtype` must be tf.complex64 or tf.complex128`")
    198 
    199         # Create list of antennas
    200         array_size = num_rows*num_cols
    201         antenna = Antenna(pattern, polarization, polarization_model, dtype)
    202 
    203         # Compute antenna positions
    204         d_v = vertical_spacing
    205         d_h = horizontal_spacing
    206         positions =  np.zeros([array_size, 3])
    207 
    208         for i in range(num_rows):
    209             for j in range(num_cols):
    210                 positions[i + j*num_rows] = [0,
    211                                              j*d_h,
    212                                              -i*d_v]
    213 
    214         # Center the panel around the origin
    215         offset = [0,
    216                   -(num_cols-1)*d_h/2,
    217                   (num_rows-1)*d_v/2]
    218         positions += offset
    219         super().__init__(antenna, positions, dtype)
    220         self._positions_set = False
    221 
    222     @property
    223     def positions(self):
    224         """
    225         [array_size, 3], `tf.float` : Get/set  array of relative positions
    226         :math:`(x,y,z)` [m] of each antenna (dual-polarized antennas are
    227         counted as a single antenna and share the same position).
    228         """
    229         if not self._positions_set:
    230             # Scale positions by wavelength
    231             if hasattr(scene.Scene(), "wavelength"):
    232                 wavelength = scene.Scene().wavelength
    233             else:
    234                 wavelength = tf.cast(1, self._rdtype)
    235             return self._positions*wavelength
    236         else:
    237             # Return provided positions
    238             return self._positions
    239 
    240     @positions.setter
    241     def positions(self, positions):
    242         if isinstance(positions, tf.Variable):
    243             if positions.dtype != self._rdtype:
    244                 raise TypeError(f"`positions` must have dtype={self._rdtype}")
    245             else:
    246                 self._positions = positions
    247         else:
    248             self._positions = tf.cast(positions, self._rdtype)
    249         self._positions_set = True
    250 
    251     def show(self):
    252         r"""show()
    253 
    254         Visualizes the antenna array
    255 
    256         Antennas are depicted by markers that are annotated with the antenna
    257         number. The marker is not related to the polarization of an antenna.
    258 
    259         Output
    260         ------
    261         : :class:`matplotlib.pyplot.Figure`
    262             Figure depicting the antenna array
    263         """
    264         fig = plt.figure()
    265         plt.plot(self.positions[:,1], self.positions[:,2],
    266                  marker=MarkerStyle("+").get_marker(), markeredgecolor='red',
    267                  markerfacecolor='red', markersize="10", linestyle="None",
    268                  markeredgewidth="1")
    269         for i, p in enumerate(self.positions):
    270             fig.axes[0].annotate(i+1, (p[1], p[2]))
    271         plt.xlabel("y (m)")
    272         plt.ylabel("z (m)")
    273         plt.title("Planar Array Layout")
    274         return fig