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.py (27234B)


      1 #
      2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      3 # SPDX-License-Identifier: Apache-2.0
      4 #
      5 """3GPP TR 38.901 antenna modeling"""
      6 
      7 import tensorflow as tf
      8 from tensorflow import sin, cos, sqrt
      9 
     10 import numpy as np
     11 
     12 import matplotlib.pyplot as plt
     13 from matplotlib.markers import MarkerStyle
     14 
     15 from sionna import SPEED_OF_LIGHT, PI
     16 from sionna.utils import log10
     17 
     18 
     19 class AntennaElement:
     20     """Antenna element following the [TR38901]_ specification
     21 
     22     Parameters
     23     ----------
     24 
     25     pattern : str
     26         Radiation pattern. Should be "omni" or "38.901".
     27 
     28     slant_angle : float
     29         Polarization slant angle [radian]
     30 
     31     dtype : tf.DType
     32         Complex datatype to use for internal processing and output.
     33         Defaults to `tf.complex64`.
     34     """
     35 
     36     def __init__(self,
     37                  pattern,
     38                  slant_angle=0.0,
     39                  dtype=tf.complex64,
     40                 ):
     41 
     42         assert pattern in ["omni", "38.901"], \
     43             "The radiation_pattern must be one of [\"omni\", \"38.901\"]."
     44         assert dtype.is_complex, "'dtype' must be complex type"
     45 
     46         self._pattern = pattern
     47         self._slant_angle = tf.constant(slant_angle, dtype=dtype.real_dtype)
     48 
     49         # Selected the radiation field correspding to the requested pattern
     50         if pattern == "omni":
     51             self._radiation_pattern = self._radiation_pattern_omni
     52         else:
     53             self._radiation_pattern = self._radiation_pattern_38901
     54 
     55         self._dtype = dtype
     56 
     57     def field(self, theta, phi):
     58         """
     59         Field pattern in the vertical and horizontal polarization (7.3-4/5)
     60 
     61         Inputs
     62         -------
     63         theta:
     64             Zenith angle wrapped within (0,pi) [radian]
     65 
     66         phi:
     67             Azimuth angle wrapped within (-pi, pi) [radian]
     68         """
     69         a = sqrt(self._radiation_pattern(theta, phi))
     70         f_theta = a * cos(self._slant_angle)
     71         f_phi   = a * sin(self._slant_angle)
     72         return (f_theta, f_phi)
     73 
     74     def show(self):
     75         """
     76         Shows the field pattern of an antenna element
     77         """
     78         theta = tf.linspace(0.0, PI, 361)
     79         phi = tf.linspace(-PI, PI, 361)
     80         a_v = 10*log10(self._radiation_pattern(theta, tf.zeros_like(theta) ))
     81         a_h = 10*log10(self._radiation_pattern(PI/2*tf.ones_like(phi) , phi))
     82 
     83         fig = plt.figure()
     84         plt.polar(theta, a_v)
     85         fig.axes[0].set_theta_zero_location("N")
     86         fig.axes[0].set_theta_direction(-1)
     87         plt.title(r"Vertical cut of the radiation pattern ($\phi = 0 $) ")
     88         plt.legend([f"{self._pattern}"])
     89 
     90         fig = plt.figure()
     91         plt.polar(phi, a_h)
     92         fig.axes[0].set_theta_zero_location("E")
     93         plt.title(r"Horizontal cut of the radiation pattern ($\theta = \pi/2$)")
     94         plt.legend([f"{self._pattern}"])
     95 
     96         theta = tf.linspace(0.0, PI, 50)
     97         phi = tf.linspace(-PI, PI, 50)
     98         phi_grid, theta_grid = tf.meshgrid(phi, theta)
     99         a = self._radiation_pattern(theta_grid, phi_grid)
    100         x = a * sin(theta_grid) * cos(phi_grid)
    101         y = a * sin(theta_grid) * sin(phi_grid)
    102         z = a * cos(theta_grid)
    103         fig = plt.figure()
    104         ax = fig.add_subplot(1,1,1, projection='3d')
    105         ax.plot_surface(x, y, z, rstride=1, cstride=1,
    106                         linewidth=0, antialiased=False, alpha=0.5)
    107         ax.view_init(elev=30., azim=-45)
    108         plt.xlabel("x")
    109         plt.ylabel("y")
    110         ax.set_zlabel("z")
    111         plt.title(f"Radiation power pattern ({self._pattern})")
    112 
    113 
    114     ###############################
    115     # Utility functions
    116     ###############################
    117 
    118     # pylint: disable=unused-argument
    119     def _radiation_pattern_omni(self, theta, phi):
    120         """
    121         Radiation pattern of an omnidirectional 3D radiation pattern
    122 
    123         Inputs
    124         -------
    125         theta:
    126             Zenith angle
    127 
    128         phi:
    129             Azimuth angle
    130         """
    131         return tf.ones_like(theta)
    132 
    133     def _radiation_pattern_38901(self, theta, phi):
    134         """
    135         Radiation pattern from TR38901 (Table 7.3-1)
    136 
    137         Inputs
    138         -------
    139         theta:
    140             Zenith angle wrapped within (0,pi) [radian]
    141 
    142         phi:
    143             Azimuth angle wrapped within (-pi, pi) [radian]
    144         """
    145         theta_3db = phi_3db = 65/180*PI
    146         a_max = sla_v = 30
    147         g_e_max = 8
    148         a_v = -tf.minimum(12*((theta-PI/2)/theta_3db)**2, sla_v)
    149         a_h = -tf.minimum(12*(phi/phi_3db)**2, a_max)
    150         a_db = -tf.minimum(-(a_v + a_h), a_max) + g_e_max
    151         return 10**(a_db/10)
    152 
    153     def _compute_gain(self):
    154         """
    155         Compute antenna gain and directivity through numerical integration
    156         """
    157         # Create angular meshgrid
    158         theta = tf.linspace(0.0, PI, 181)
    159         phi = tf.linspace(-PI, PI, 361)
    160         phi_grid, theta_grid = tf.meshgrid(phi, theta)
    161 
    162         # Compute field strength over the grid
    163         f_theta, f_phi =  self.field(theta_grid, phi_grid)
    164         u = f_theta**2 + f_phi**2
    165         gain_db = 10*log10(tf.reduce_max(u))
    166 
    167         # Numerical integration of the field components
    168         dtheta = theta[1]-theta[0]
    169         dphi = phi[1]-phi[0]
    170         po = tf.reduce_sum(u*sin(theta_grid)*dtheta*dphi)
    171 
    172         # Compute directivity
    173         u_bar = po/(4*PI) # Equivalent isotropic radiator
    174         d = u/u_bar # Directivity grid
    175         directivity_db = 10*log10(tf.reduce_max(d))
    176         return (gain_db, directivity_db)
    177 
    178 
    179 class AntennaPanel:
    180     """Antenna panel following the [TR38901]_ specification
    181 
    182     Parameters
    183     -----------
    184 
    185     num_rows : int
    186         Number of rows forming the panel
    187 
    188     num_cols : int
    189         Number of columns forming the panel
    190 
    191     polarization : str
    192         Polarization. Should be "single" or "dual"
    193 
    194     vertical_spacing : float
    195         Vertical antenna element spacing [multiples of wavelength]
    196 
    197     horizontal_spacing : float
    198         Horizontal antenna element spacing [multiples of wavelength]
    199 
    200     dtype : tf.DType
    201         Complex datatype to use for internal processing and output.
    202         Defaults to `tf.complex64`.
    203     """
    204 
    205     def __init__(self,
    206                  num_rows,
    207                  num_cols,
    208                  polarization,
    209                  vertical_spacing,
    210                  horizontal_spacing,
    211                  dtype=tf.complex64):
    212 
    213         assert dtype.is_complex, "'dtype' must be complex type"
    214         assert polarization in ('single', 'dual'), \
    215             "polarization must be either 'single' or 'dual'"
    216 
    217         self._num_rows = tf.constant(num_rows, tf.int32)
    218         self._num_cols = tf.constant(num_cols, tf.int32)
    219         self._polarization = polarization
    220         self._horizontal_spacing = tf.constant(horizontal_spacing,
    221                                                 dtype.real_dtype)
    222         self._vertical_spacing = tf.constant(vertical_spacing, dtype.real_dtype)
    223         self._dtype = dtype.real_dtype
    224 
    225         # Place the antenna elements of the first polarization direction
    226         # on the y-z-plane
    227         p = 1 if polarization == 'single' else 2
    228         ant_pos = np.zeros([num_rows*num_cols*p, 3])
    229         for i in range(num_rows):
    230             for j in range(num_cols):
    231                 ant_pos[i +j*num_rows] = [  0,
    232                                             j*horizontal_spacing,
    233                                             -i*vertical_spacing]
    234 
    235         # Center the panel around the origin
    236         offset = [  0,
    237                     -(num_cols-1)*horizontal_spacing/2,
    238                     (num_rows-1)*vertical_spacing/2]
    239         ant_pos += offset
    240 
    241         # Create the antenna elements of the second polarization direction
    242         if polarization == 'dual':
    243             ant_pos[num_rows*num_cols:] = ant_pos[:num_rows*num_cols]
    244         self._ant_pos = tf.constant(ant_pos, self._dtype.real_dtype)
    245 
    246     @property
    247     def ant_pos(self):
    248         """Antenna positions in the local coordinate system"""
    249         return self._ant_pos
    250 
    251     @property
    252     def num_rows(self):
    253         """Number of rows"""
    254         return self._num_rows
    255 
    256     def num_cols(self):
    257         """Number of columns"""
    258         return self._num_cols
    259 
    260     @property
    261     def porlarization(self):
    262         """Polarization ("single" or "dual")"""
    263         return self._polarization
    264 
    265     @property
    266     def vertical_spacing(self):
    267         """Vertical spacing between elements [multiple of wavelength]"""
    268         return self._vertical_spacing
    269 
    270     @property
    271     def horizontal_spacing(self):
    272         """Vertical spacing between elements [multiple of wavelength]"""
    273         return self._horizontal_spacing
    274 
    275     def show(self):
    276         """Shows the panel geometry"""
    277         fig = plt.figure()
    278         pos = self._ant_pos[:self._num_rows*self._num_cols]
    279         plt.plot(pos[:,1], pos[:,2], marker = "|", markeredgecolor='red',
    280             markersize="20", linestyle="None", markeredgewidth="2")
    281         for i, p in enumerate(pos):
    282             fig.axes[0].annotate(i+1, (p[1], p[2]))
    283         if self._polarization == 'dual':
    284             pos = self._ant_pos[self._num_rows*self._num_cols:]
    285             plt.plot(pos[:,1], pos[:,2], marker = "_", markeredgecolor='black',
    286                 markersize="20", linestyle="None", markeredgewidth="1")
    287         plt.xlabel(r"y ($\lambda_0$)")
    288         plt.ylabel(r"z ($\lambda_0$)")
    289         plt.title("Antenna Panel")
    290         plt.legend(["Polarization 1", "Polarization 2"], loc="upper right")
    291 
    292 
    293 class PanelArray:
    294     # pylint: disable=line-too-long
    295     r"""PanelArray(num_rows_per_panel, num_cols_per_panel, polarization, polarization_type, antenna_pattern, carrier_frequency, num_rows=1, num_cols=1, panel_vertical_spacing=None, panel_horizontal_spacing=None, element_vertical_spacing=None, element_horizontal_spacing=None, dtype=tf.complex64)
    296 
    297     Antenna panel array following the [TR38901]_ specification.
    298 
    299     This class is used to create models of the panel arrays used by the
    300     transmitters and receivers and that need to be specified when using the
    301     :ref:`CDL <cdl>`, :ref:`UMi <umi>`, :ref:`UMa <uma>`, and :ref:`RMa <rma>`
    302     models.
    303 
    304     Example
    305     --------
    306 
    307     >>> array = PanelArray(num_rows_per_panel = 4,
    308     ...                    num_cols_per_panel = 4,
    309     ...                    polarization = 'dual',
    310     ...                    polarization_type = 'VH',
    311     ...                    antenna_pattern = '38.901',
    312     ...                    carrier_frequency = 3.5e9,
    313     ...                    num_cols = 2,
    314     ...                    panel_horizontal_spacing = 3.)
    315     >>> array.show()
    316 
    317     .. image:: ../figures/panel_array.png
    318 
    319     Parameters
    320     ----------
    321 
    322     num_rows_per_panel : int
    323         Number of rows of elements per panel
    324 
    325     num_cols_per_panel : int
    326         Number of columns of elements per panel
    327 
    328     polarization : str
    329         Polarization, either "single" or "dual"
    330 
    331     polarization_type : str
    332         Type of polarization. For single polarization, must be "V" or "H".
    333         For dual polarization, must be "VH" or "cross".
    334 
    335     antenna_pattern : str
    336         Element radiation pattern, either "omni" or "38.901"
    337 
    338     carrier_frequency : float
    339         Carrier frequency [Hz]
    340 
    341     num_rows : int
    342         Number of rows of panels. Defaults to 1.
    343 
    344     num_cols : int
    345         Number of columns of panels. Defaults to 1.
    346 
    347     panel_vertical_spacing : `None` or float
    348         Vertical spacing of panels [multiples of wavelength].
    349         Must be greater than the panel width.
    350         If set to `None` (default value), it is set to the panel width + 0.5.
    351 
    352     panel_horizontal_spacing : `None` or float
    353         Horizontal spacing of panels [in multiples of wavelength].
    354         Must be greater than the panel height.
    355         If set to `None` (default value), it is set to the panel height + 0.5.
    356 
    357     element_vertical_spacing : `None` or float
    358         Element vertical spacing [multiple of wavelength].
    359         Defaults to 0.5 if set to `None`.
    360 
    361     element_horizontal_spacing : `None` or float
    362         Element horizontal spacing [multiple of wavelength].
    363         Defaults to 0.5 if set to `None`.
    364 
    365     dtype : Complex tf.DType
    366         Defines the datatype for internal calculations and the output
    367         dtype. Defaults to `tf.complex64`.
    368     """
    369 
    370     def __init__(self,  num_rows_per_panel,
    371                         num_cols_per_panel,
    372                         polarization,
    373                         polarization_type,
    374                         antenna_pattern,
    375                         carrier_frequency,
    376                         num_rows=1,
    377                         num_cols=1,
    378                         panel_vertical_spacing=None,
    379                         panel_horizontal_spacing=None,
    380                         element_vertical_spacing=None,
    381                         element_horizontal_spacing=None,
    382                         dtype=tf.complex64):
    383 
    384         assert dtype.is_complex, "'dtype' must be complex type"
    385 
    386         assert polarization in ('single', 'dual'), \
    387             "polarization must be either 'single' or 'dual'"
    388 
    389         # Setting default values for antenna and panel spacings if not
    390         # specified by the user
    391         # Default spacing for antenna elements is half a wavelength
    392         if element_vertical_spacing is None:
    393             element_vertical_spacing = 0.5
    394         if element_horizontal_spacing is None:
    395             element_horizontal_spacing = 0.5
    396         # Default values of panel spacing is the pannel size + 0.5
    397         if panel_vertical_spacing is None:
    398             panel_vertical_spacing = (num_rows_per_panel-1)\
    399                 *element_vertical_spacing+0.5
    400         if panel_horizontal_spacing is None:
    401             panel_horizontal_spacing = (num_cols_per_panel-1)\
    402                 *element_horizontal_spacing+0.5
    403 
    404         # Check that panel spacing is larger than panel dimensions
    405         assert panel_horizontal_spacing > (num_cols_per_panel-1)\
    406             *element_horizontal_spacing,\
    407                 "Pannel horizontal spacing must be larger than the panel width"
    408         assert panel_vertical_spacing > (num_rows_per_panel-1)\
    409             *element_vertical_spacing,\
    410             "Pannel vertical spacing must be larger than panel height"
    411 
    412         self._num_rows = tf.constant(num_rows, tf.int32)
    413         self._num_cols = tf.constant(num_cols, tf.int32)
    414         self._num_rows_per_panel = tf.constant(num_rows_per_panel, tf.int32)
    415         self._num_cols_per_panel = tf.constant(num_cols_per_panel, tf.int32)
    416         self._polarization = polarization
    417         self._polarization_type = polarization_type
    418         self._panel_vertical_spacing = tf.constant(panel_vertical_spacing,
    419                                             dtype.real_dtype)
    420         self._panel_horizontal_spacing = tf.constant(panel_horizontal_spacing,
    421                                             dtype.real_dtype)
    422         self._element_vertical_spacing = tf.constant(element_vertical_spacing,
    423                                             dtype.real_dtype)
    424         self._element_horizontal_spacing=tf.constant(element_horizontal_spacing,
    425                             dtype.real_dtype)
    426         self._dtype = dtype
    427 
    428         self._num_panels = tf.constant(num_cols*num_rows, tf.int32)
    429 
    430         p = 1 if polarization == 'single' else 2
    431         self._num_panel_ant = tf.constant(  num_cols_per_panel*
    432                                             num_rows_per_panel*p,
    433                                             tf.int32)
    434         # Total number of antenna elements
    435         self._num_ant = self._num_panels * self._num_panel_ant
    436 
    437         # Wavelength (m)
    438         self._lambda_0 = tf.constant(SPEED_OF_LIGHT / carrier_frequency,
    439                                     dtype.real_dtype)
    440 
    441         # Create one antenna element for each polarization direction
    442         # polarization must be one of {"V", "H", "VH", "cross"}
    443         if polarization == 'single':
    444             assert polarization_type in ["V", "H"],\
    445                 "For single polarization, polarization_type must be 'V' or 'H'"
    446             slant_angle = 0 if polarization_type == "V" else PI/2
    447             self._ant_pol1 = AntennaElement(antenna_pattern, slant_angle,
    448                 self._dtype)
    449         else:
    450             assert polarization_type in ["VH", "cross"],\
    451             "For dual polarization, polarization_type must be 'VH' or 'cross'"
    452             slant_angle = 0 if polarization_type == "VH" else -PI/4
    453             self._ant_pol1 = AntennaElement(antenna_pattern, slant_angle,
    454                 self._dtype)
    455             self._ant_pol2 = AntennaElement(antenna_pattern, slant_angle+PI/2,
    456                 self._dtype)
    457 
    458         # Compose array from panels
    459         ant_pos = np.zeros([self._num_ant, 3])
    460         panel = AntennaPanel(num_rows_per_panel, num_cols_per_panel,
    461             polarization, element_vertical_spacing, element_horizontal_spacing,
    462             dtype)
    463         pos = panel.ant_pos
    464         count = 0
    465         num_panel_ant = self._num_panel_ant
    466         for j in range(num_cols):
    467             for i in range(num_rows):
    468                 offset = [  0,
    469                             j*panel_horizontal_spacing,
    470                             -i*panel_vertical_spacing]
    471                 new_pos = pos + offset
    472                 ant_pos[count*num_panel_ant:(count+1)*num_panel_ant] = new_pos
    473                 count += 1
    474 
    475         # Center the entire panel array around the orgin of the y-z plane
    476         offset = [  0,
    477                     -(num_cols-1)*panel_horizontal_spacing/2,
    478                     (num_rows-1)*panel_vertical_spacing/2]
    479         ant_pos += offset
    480 
    481         # Scale antenna element positions by the wavelength
    482         ant_pos *= self._lambda_0
    483         self._ant_pos = tf.constant(ant_pos, dtype.real_dtype)
    484 
    485         # Compute indices of antennas for polarization directions
    486         ind = np.arange(0, self._num_ant)
    487         ind = np.reshape(ind, [self._num_panels*p, -1])
    488         self._ant_ind_pol1 = tf.constant(np.reshape(ind[::p], [-1]), tf.int32)
    489         if polarization == 'single':
    490             self._ant_ind_pol2 = tf.constant(np.array([]), tf.int32)
    491         else:
    492             self._ant_ind_pol2 = tf.constant(np.reshape(
    493                 ind[1:self._num_panels*p:2], [-1]), tf.int32)
    494 
    495         # Get positions of antenna elements for each polarization direction
    496         self._ant_pos_pol1 = tf.gather(self._ant_pos, self._ant_ind_pol1,
    497                                         axis=0)
    498         self._ant_pos_pol2 = tf.gather(self._ant_pos, self._ant_ind_pol2,
    499                                         axis=0)
    500 
    501     @property
    502     def num_rows(self):
    503         """Number of rows of panels"""
    504         return self._num_rows
    505 
    506     @property
    507     def num_cols(self):
    508         """Number of columns of panels"""
    509         return self._num_cols
    510 
    511     @property
    512     def num_rows_per_panel(self):
    513         """Number of rows of elements per panel"""
    514         return self._num_rows_per_panel
    515 
    516     @property
    517     def num_cols_per_panel(self):
    518         """Number of columns of elements per panel"""
    519         return self._num_cols_per_panel
    520 
    521     @property
    522     def polarization(self):
    523         """Polarization ("single" or "dual")"""
    524         return self._polarization
    525 
    526     @property
    527     def polarization_type(self):
    528         """Polarization type. "V" or "H" for single polarization.
    529         "VH" or "cross" for dual polarization."""
    530         return self._polarization_type
    531 
    532     @property
    533     def panel_vertical_spacing(self):
    534         """Vertical spacing between the panels [multiple of wavelength]"""
    535         return self._panel_vertical_spacing
    536 
    537     @property
    538     def panel_horizontal_spacing(self):
    539         """Horizontal spacing between the panels [multiple of wavelength]"""
    540         return self._panel_horizontal_spacing
    541 
    542     @property
    543     def element_vertical_spacing(self):
    544         """Vertical spacing between the antenna elements within a panel
    545         [multiple of wavelength]"""
    546         return self._element_vertical_spacing
    547 
    548     @property
    549     def element_horizontal_spacing(self):
    550         """Horizontal spacing between the antenna elements within a panel
    551         [multiple of wavelength]"""
    552         return self._element_horizontal_spacing
    553 
    554     @property
    555     def num_panels(self):
    556         """Number of panels"""
    557         return self._num_panels
    558 
    559     @property
    560     def num_panels_ant(self):
    561         """Number of antenna elements per panel"""
    562         return self._num_panel_ant
    563 
    564     @property
    565     def num_ant(self):
    566         """Total number of antenna elements"""
    567         return self._num_ant
    568 
    569     @property
    570     def ant_pol1(self):
    571         """Field of an antenna element with the first polarization direction"""
    572         return self._ant_pol1
    573 
    574     @property
    575     def ant_pol2(self):
    576         """Field of an antenna element with the second polarization direction.
    577         Only defined with dual polarization."""
    578         assert self._polarization == 'dual',\
    579             "This property is not defined with single polarization"
    580         return self._ant_pol2
    581 
    582     @property
    583     def ant_pos(self):
    584         """Positions of the antennas"""
    585         return self._ant_pos
    586 
    587     @property
    588     def ant_ind_pol1(self):
    589         """Indices of antenna elements with the first polarization direction"""
    590         return self._ant_ind_pol1
    591 
    592     @property
    593     def ant_ind_pol2(self):
    594         """Indices of antenna elements with the second polarization direction.
    595         Only defined with dual polarization."""
    596         assert self._polarization == 'dual',\
    597             "This property is not defined with single polarization"
    598         return self._ant_ind_pol2
    599 
    600     @property
    601     def ant_pos_pol1(self):
    602         """Positions of the antenna elements with the first polarization
    603         direction"""
    604         return self._ant_pos_pol1
    605 
    606     @property
    607     def ant_pos_pol2(self):
    608         """Positions of antenna elements with the second polarization direction.
    609         Only defined with dual polarization."""
    610         assert self._polarization == 'dual',\
    611             "This property is not defined with single polarization"
    612         return self._ant_pos_pol2
    613 
    614     def show(self):
    615         """Show the panel array geometry"""
    616         if self._polarization == 'single':
    617             if self._polarization_type == 'H':
    618                 marker_p1 = MarkerStyle("_").get_marker()
    619             else:
    620                 marker_p1 = MarkerStyle("|")
    621         else: # 'dual'
    622             if self._polarization_type == 'cross':
    623                 marker_p1 = (2, 0, -45)
    624                 marker_p2 = (2, 0, 45)
    625             else:
    626                 marker_p1 = MarkerStyle("_").get_marker()
    627                 marker_p2 = MarkerStyle("|").get_marker()
    628 
    629         fig = plt.figure()
    630         pos_pol1 = self._ant_pos_pol1
    631         plt.plot(pos_pol1[:,1], pos_pol1[:,2],
    632             marker=marker_p1, markeredgecolor='red',
    633             markersize="20", linestyle="None", markeredgewidth="2")
    634         for i, p in enumerate(pos_pol1):
    635             fig.axes[0].annotate(self._ant_ind_pol1[i].numpy()+1, (p[1], p[2]))
    636         if self._polarization == 'dual':
    637             pos_pol2 = self._ant_pos_pol2
    638             plt.plot(pos_pol2[:,1], pos_pol2[:,2],
    639                 marker=marker_p2, markeredgecolor='black', # pylint: disable=possibly-used-before-assignment
    640                 markersize="20", linestyle="None", markeredgewidth="1")
    641         plt.xlabel("y (m)")
    642         plt.ylabel("z (m)")
    643         plt.title("Panel Array")
    644         plt.legend(["Polarization 1", "Polarization 2"], loc="upper right")
    645 
    646     def show_element_radiation_pattern(self):
    647         """Show the radiation field of antenna elements forming the panel"""
    648         self._ant_pol1.show()
    649 
    650 class Antenna(PanelArray):
    651     # pylint: disable=line-too-long
    652     r"""Antenna(polarization, polarization_type, antenna_pattern, carrier_frequency, dtype=tf.complex64)
    653 
    654     Single antenna following the [TR38901]_ specification.
    655 
    656     This class is a special case of :class:`~sionna.channel.tr38901.PanelArray`,
    657     and can be used in lieu of it.
    658 
    659     Parameters
    660     ----------
    661     polarization : str
    662         Polarization, either "single" or "dual"
    663 
    664     polarization_type : str
    665         Type of polarization. For single polarization, must be "V" or "H".
    666         For dual polarization, must be "VH" or "cross".
    667 
    668     antenna_pattern : str
    669         Element radiation pattern, either "omni" or "38.901"
    670 
    671     carrier_frequency : float
    672         Carrier frequency [Hz]
    673 
    674     dtype : Complex tf.DType
    675         Defines the datatype for internal calculations and the output
    676         dtype. Defaults to `tf.complex64`.
    677     """
    678 
    679     def __init__(self,  polarization,
    680                         polarization_type,
    681                         antenna_pattern,
    682                         carrier_frequency,
    683                         dtype=tf.complex64):
    684 
    685         super().__init__(num_rows_per_panel=1,
    686                          num_cols_per_panel=1,
    687                          polarization=polarization,
    688                          polarization_type=polarization_type,
    689                          antenna_pattern=antenna_pattern,
    690                          carrier_frequency=carrier_frequency,
    691                          dtype=dtype)
    692 
    693 class AntennaArray(PanelArray):
    694     # pylint: disable=line-too-long
    695     r"""AntennaArray(num_rows, num_cols, polarization, polarization_type, antenna_pattern, carrier_frequency, vertical_spacing, horizontal_spacing, dtype=tf.complex64)
    696 
    697     Antenna array following the [TR38901]_ specification.
    698 
    699     This class is a special case of :class:`~sionna.channel.tr38901.PanelArray`,
    700     and can used in lieu of it.
    701 
    702     Parameters
    703     ----------
    704     num_rows : int
    705         Number of rows of elements
    706 
    707     num_cols : int
    708         Number of columns of elements
    709 
    710     polarization : str
    711         Polarization, either "single" or "dual"
    712 
    713     polarization_type : str
    714         Type of polarization. For single polarization, must be "V" or "H".
    715         For dual polarization, must be "VH" or "cross".
    716 
    717     antenna_pattern : str
    718         Element radiation pattern, either "omni" or "38.901"
    719 
    720     carrier_frequency : float
    721         Carrier frequency [Hz]
    722 
    723     vertical_spacing : `None` or float
    724         Element vertical spacing [multiple of wavelength].
    725         Defaults to 0.5 if set to `None`.
    726 
    727     horizontal_spacing : `None` or float
    728         Element horizontal spacing [multiple of wavelength].
    729         Defaults to 0.5 if set to `None`.
    730 
    731     dtype : Complex tf.DType
    732         Defines the datatype for internal calculations and the output
    733         dtype. Defaults to `tf.complex64`.
    734     """
    735 
    736     def __init__(self,  num_rows,
    737                         num_cols,
    738                         polarization,
    739                         polarization_type,
    740                         antenna_pattern,
    741                         carrier_frequency,
    742                         vertical_spacing=None,
    743                         horizontal_spacing=None,
    744                         dtype=tf.complex64):
    745 
    746         super().__init__(num_rows_per_panel=num_rows,
    747                          num_cols_per_panel=num_cols,
    748                          polarization=polarization,
    749                          polarization_type=polarization_type,
    750                          antenna_pattern=antenna_pattern,
    751                          carrier_frequency=carrier_frequency,
    752                          element_vertical_spacing=vertical_spacing,
    753                          element_horizontal_spacing=horizontal_spacing,
    754                          dtype=dtype)