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

radio_material.py (12955B)


      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 a radio material.
      7 A radio material provides the EM radio properties for a specific material.
      8 """
      9 
     10 import tensorflow as tf
     11 
     12 from . import scene
     13 from sionna.constants import DIELECTRIC_PERMITTIVITY_VACUUM, PI
     14 from .scattering_pattern import ScatteringPattern, LambertianPattern
     15 
     16 class RadioMaterial:
     17     # pylint: disable=line-too-long
     18     r"""
     19     Class implementing a radio material
     20 
     21     A radio material is defined by its relative permittivity
     22     :math:`\varepsilon_r` and conductivity :math:`\sigma` (see :eq:`eta`),
     23     as well as optional parameters related to diffuse scattering, such as the
     24     scattering coefficient :math:`S`, cross-polarization discrimination
     25     coefficient :math:`K_x`, and scattering pattern :math:`f_\text{s}(\hat{\mathbf{k}}_\text{i}, \hat{\mathbf{k}}_\text{s})`.
     26 
     27     We assume non-ionized and non-magnetic materials, and therefore the
     28     permeability :math:`\mu` of the material is assumed to be equal
     29     to the permeability of vacuum i.e., :math:`\mu_r=1.0`.
     30 
     31     For frequency-dependent materials, it is possible to
     32     specify a callback function ``frequency_update_callback`` that computes
     33     the material properties :math:`(\varepsilon_r, \sigma)` from the
     34     frequency. If a callback function is specified, the material properties
     35     cannot be set and the values specified at instantiation are ignored.
     36     The callback should return `-1` for both the relative permittivity and
     37     the conductivity if these are not defined for the given carrier frequency.
     38 
     39     The material properties can be assigned to a TensorFlow variable or
     40     tensor. In the latter case, the tensor could be the output of a callable,
     41     such as a Keras layer implementing a neural network. In the former case, it
     42     could be set to a trainable variable:
     43 
     44     .. code-block:: Python
     45 
     46         mat = RadioMaterial("my_mat")
     47         mat.conductivity = tf.Variable(0.0, dtype=tf.float32)
     48 
     49     Parameters
     50     -----------
     51     name : str
     52         Unique name of the material
     53 
     54     relative_permittivity : float | `None`
     55         Relative permittivity of the material.
     56         Must be larger or equal to 1.
     57         Defaults to 1. Ignored if ``frequency_update_callback``
     58         is provided.
     59 
     60     conductivity : float | `None`
     61         Conductivity of the material [S/m].
     62         Must be non-negative.
     63         Defaults to 0.
     64         Ignored if ``frequency_update_callback``
     65         is provided.
     66 
     67     scattering_coefficient : float
     68         Scattering coefficient :math:`S\in[0,1]` as defined in
     69         :eq:`scattering_coefficient`.
     70         Defaults to 0.
     71 
     72     xpd_coefficient : float
     73         Cross-polarization discrimination coefficient :math:`K_x\in[0,1]` as
     74         defined in :eq:`xpd`.
     75         Only relevant if ``scattering_coefficient``>0.
     76         Defaults to 0.
     77 
     78     scattering_pattern : ScatteringPattern
     79         :class:`~sionna.rt.ScatteringPattern` to be applied.
     80         Only relevant if ``scattering_coefficient``>0.
     81         Defaults to `None`, which implies a :class:`~sionna.rt.LambertianPattern`.
     82 
     83     frequency_update_callback : callable | `None`
     84         An optional callable object used to obtain the material parameters
     85         from the scene's :attr:`~sionna.rt.Scene.frequency`.
     86         This callable must take as input the frequency [Hz] and
     87         must return the material properties as a tuple:
     88 
     89         ``(relative_permittivity, conductivity)``.
     90 
     91         If set to `None`, the material properties are constant and equal
     92         to ``relative_permittivity`` and ``conductivity``.
     93         Defaults to `None`.
     94 
     95     dtype : tf.complex64 or tf.complex128
     96         Datatype.
     97         Defaults to `tf.complex64`.
     98     """
     99 
    100     def __init__(self,
    101                  name,
    102                  relative_permittivity=1.0,
    103                  conductivity=0.0,
    104                  scattering_coefficient=0.0,
    105                  xpd_coefficient=0.0,
    106                  scattering_pattern=None,
    107                  frequency_update_callback=None,
    108                  dtype=tf.complex64):
    109 
    110         if not isinstance(name, str):
    111             raise TypeError("`name` must be a string")
    112         self._name = name
    113 
    114         if dtype not in (tf.complex64, tf.complex128):
    115             msg = "`dtype` must be `tf.complex64` or `tf.complex128`"
    116             raise ValueError(msg)
    117         self._dtype = dtype
    118         self._rdtype = dtype.real_dtype
    119 
    120         if scattering_pattern is None:
    121             scattering_pattern = LambertianPattern(dtype=dtype)
    122 
    123         self.scattering_pattern = scattering_pattern
    124         self.scattering_coefficient = scattering_coefficient
    125         self.xpd_coefficient = xpd_coefficient
    126 
    127         if frequency_update_callback is None:
    128             self.relative_permittivity = relative_permittivity
    129             self.conductivity = conductivity
    130 
    131         # Save the callback for when the frequency is updated
    132         # or if the RadioMaterial is added to a scene
    133         self._frequency_update_callback = frequency_update_callback
    134 
    135         # When loading a scene, the custom materials (i.e., the materials not
    136         # baked-in Sionna but defined by the user) are not defined yet.
    137         # If when loading a scene a non-defined material is encountered,
    138         # then a "placeholder" material is created which is used until the
    139         # material is defined by the user.
    140         # Note that propagation simulation cannot be done if placeholders are
    141         # used.
    142         self._is_placeholder = False # Is this material a placeholder
    143 
    144         # Set of objects identifiers that use this material
    145         self._objects_using = set()
    146 
    147 
    148     @property
    149     def name(self):
    150         """
    151         str (read-only) : Name of the radio material
    152         """
    153         return self._name
    154 
    155     @property
    156     def relative_permittivity(self):
    157         r"""
    158         tf.float : Get/set the relative permittivity
    159             :math:`\varepsilon_r` :eq:`eta`
    160         """
    161         return self._relative_permittivity
    162 
    163     @relative_permittivity.setter
    164     def relative_permittivity(self, v):
    165         if isinstance(v, tf.Variable):
    166             if v.dtype != self._rdtype:
    167                 msg = f"`relative_permittivity` must have dtype={self._rdtype}"
    168                 raise TypeError(msg)
    169             else:
    170                 self._relative_permittivity = v
    171         else:
    172             self._relative_permittivity = tf.cast(v, self._rdtype)
    173 
    174     @property
    175     def relative_permeability(self):
    176         r"""
    177         tf.float (read-only) : Relative permeability
    178             :math:`\mu_r` :eq:`mu`.
    179             Defaults to 1.
    180         """
    181         return tf.cast(1., self._rdtype)
    182 
    183     @property
    184     def conductivity(self):
    185         r"""
    186         tf.float: Get/set the conductivity
    187             :math:`\sigma` [S/m] :eq:`eta`
    188         """
    189         return self._conductivity
    190 
    191     @conductivity.setter
    192     def conductivity(self, v):
    193         if isinstance(v, tf.Variable):
    194             if v.dtype != self._rdtype:
    195                 msg = f"`conductivity` must have dtype={self._rdtype}"
    196                 raise TypeError(msg)
    197             else:
    198                 self._conductivity = v
    199         else:
    200             self._conductivity = tf.cast(v, self._rdtype)
    201 
    202     @property
    203     def scattering_coefficient(self):
    204         r"""
    205         tf.float: Get/set the scattering coefficient
    206             :math:`S\in[0,1]` :eq:`scattering_coefficient`.
    207         """
    208         return self._scattering_coefficient
    209 
    210     @scattering_coefficient.setter
    211     def scattering_coefficient(self, v):
    212         if isinstance(v, tf.Variable):
    213             if v.dtype != self._rdtype:
    214                 msg=f"`scattering_coefficient` must have dtype={self._rdtype}"
    215                 raise TypeError(msg)
    216             else:
    217                 self._scattering_coefficient = v
    218         else:
    219             self._scattering_coefficient = tf.cast(v, self._rdtype)
    220 
    221     @property
    222     def xpd_coefficient(self):
    223         r"""
    224         tf.float: Get/set the cross-polarization discrimination coefficient
    225             :math:`K_x\in[0,1]` :eq:`xpd`.
    226         """
    227         return self._xpd_coefficient
    228 
    229     @xpd_coefficient.setter
    230     def xpd_coefficient(self, v):
    231         if isinstance(v, tf.Variable):
    232             if v.dtype != self._rdtype:
    233                 msg=f"`xpd_coefficient` must have dtype={self._rdtype}"
    234                 raise TypeError(msg)
    235             else:
    236                 self._xpd_coefficient = v
    237         else:
    238             self._xpd_coefficient = tf.cast(v, self._rdtype)
    239 
    240     @property
    241     def scattering_pattern(self):
    242         r"""
    243         ScatteringPattern: Get/set the ScatteringPattern.
    244         """
    245         return self._scattering_pattern
    246 
    247     @scattering_pattern.setter
    248     def scattering_pattern(self, v):
    249         if not isinstance(v, ScatteringPattern) and v is not None:
    250             raise ValueError("Not a valid instanc of ScatteringPattern")
    251         self._scattering_pattern = v
    252 
    253     @property
    254     def complex_relative_permittivity(self):
    255         r"""
    256         tf.complex (read-only) : Complex relative permittivity
    257             :math:`\eta` :eq:`eta`
    258         """
    259         epsilon_0 = DIELECTRIC_PERMITTIVITY_VACUUM
    260         eta_prime = self.relative_permittivity
    261         sigma = self.conductivity
    262         frequency = scene.Scene().frequency
    263         omega = tf.cast(2.*PI*frequency, self._rdtype)
    264         return tf.complex(eta_prime,
    265                           -tf.math.divide_no_nan(sigma, epsilon_0*omega))
    266 
    267     @property
    268     def frequency_update_callback(self):
    269         """
    270         callable : Get/set frequency update callback function
    271         """
    272         return self._frequency_update_callback
    273 
    274     @frequency_update_callback.setter
    275     def frequency_update_callback(self, value):
    276         self._frequency_update_callback = value
    277         self.frequency_update()
    278 
    279     @property
    280     def well_defined(self):
    281         """bool : Get if the material is well-defined"""
    282         # pylint: disable=chained-comparison
    283         return ((self._conductivity >= 0.)
    284              and (self.relative_permittivity >= 1.)
    285              and (0. <= self.scattering_coefficient <= 1.)
    286              and (0. <= self.xpd_coefficient <= 1.)
    287              and (0. <= self.scattering_pattern.lambda_ <= 1.))
    288 
    289     @property
    290     def use_counter(self):
    291         """
    292         int : Number of scene objects using this material
    293         """
    294         return len(self._objects_using)
    295 
    296     @property
    297     def is_used(self):
    298         """bool : Indicator if the material is used by at least one object of
    299         the scene"""
    300         return self.use_counter > 0
    301 
    302     @property
    303     def using_objects(self):
    304         """
    305         [num_using_objects], tf.int : Identifiers of the objects using this
    306         material
    307         """
    308         tf_objects_using = tf.cast(tuple(self._objects_using), tf.int32)
    309         return tf_objects_using
    310 
    311     ##############################################
    312     # Internal methods.
    313     # Should not be documented.
    314     ##############################################
    315 
    316     def frequency_update(self):
    317         # pylint: disable=line-too-long
    318         r"""Callback for when the frequency is updated
    319         """
    320         if self._frequency_update_callback is None:
    321             return
    322 
    323         parameters = self._frequency_update_callback(scene.Scene().frequency)
    324         relative_permittivity, conductivity = parameters
    325         self.relative_permittivity = relative_permittivity
    326         self.conductivity = conductivity
    327 
    328     def add_object_using(self, object_id):
    329         """
    330         Add an object to the set of objects using this material
    331         """
    332         self._objects_using.add(object_id)
    333 
    334     def discard_object_using(self, object_id):
    335         """
    336         Remove an object from the set of objects using this material
    337         """
    338         assert object_id in self._objects_using,\
    339             f"Object with id {object_id} is not in the set of {self.name}"
    340         self._objects_using.discard(object_id)
    341 
    342     @property
    343     def is_placeholder(self):
    344         """
    345         bool : Get/set if this radio material is a placeholder
    346         """
    347         return self._is_placeholder
    348 
    349     @is_placeholder.setter
    350     def is_placeholder(self, v):
    351         self._is_placeholder = v
    352 
    353     def assign(self, rm):
    354         """
    355         Assign new values to the radio material properties from another
    356         radio material ``rm``
    357 
    358         Input
    359         ------
    360         rm : :class:`~sionna.rt.RadioMaterial
    361             Radio material from which to assign the new values
    362         """
    363         if not isinstance(rm, RadioMaterial):
    364             raise TypeError("`rm` is not a RadioMaterial")
    365         self.relative_permittivity = rm.relative_permittivity
    366         self.conductivity = rm.conductivity
    367         self.scattering_coefficient = rm.scattering_coefficient
    368         self.xpd_coefficient = rm.xpd_coefficient
    369         self.scattering_pattern = rm.scattering_pattern
    370         self.frequency_update_callback = rm.frequency_update_callback