Source code for sionna.rt.radio_material

#
# SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""
Implements a radio material.
A radio material provides the EM radio properties for a specific material.
"""

import tensorflow as tf

from . import scene
from sionna.constants import DIELECTRIC_PERMITTIVITY_VACUUM, PI
from .scattering_pattern import ScatteringPattern, LambertianPattern

[docs]class RadioMaterial: # pylint: disable=line-too-long r""" Class implementing a radio material A radio material is defined by its relative permittivity :math:`\varepsilon_r` and conductivity :math:`\sigma` (see :eq:`eta`), as well as optional parameters related to diffuse scattering, such as the scattering coefficient :math:`S`, cross-polarization discrimination coefficient :math:`K_x`, and scattering pattern :math:`f_\text{s}(\hat{\mathbf{k}}_\text{i}, \hat{\mathbf{k}}_\text{s})`. We assume non-ionized and non-magnetic materials, and therefore the permeability :math:`\mu` of the material is assumed to be equal to the permeability of vacuum i.e., :math:`\mu_r=1.0`. For frequency-dependent materials, it is possible to specify a callback function ``frequency_update_callback`` that computes the material properties :math:`(\varepsilon_r, \sigma)` from the frequency. If a callback function is specified, the material properties cannot be set and the values specified at instantiation are ignored. The callback should return `-1` for both the relative permittivity and the conductivity if these are not defined for the given carrier frequency. The material properties can be assigned to a TensorFlow variable or tensor. In the latter case, the tensor could be the output of a callable, such as a Keras layer implementing a neural network. In the former case, it could be set to a trainable variable: .. code-block:: Python mat = RadioMaterial("my_mat") mat.conductivity = tf.Variable(0.0, dtype=tf.float32) Parameters ----------- name : str Unique name of the material relative_permittivity : float | `None` Relative permittivity of the material. Must be larger or equal to 1. Defaults to 1. Ignored if ``frequency_update_callback`` is provided. conductivity : float | `None` Conductivity of the material [S/m]. Must be non-negative. Defaults to 0. Ignored if ``frequency_update_callback`` is provided. scattering_coefficient : float Scattering coefficient :math:`S\in[0,1]` as defined in :eq:`scattering_coefficient`. Defaults to 0. xpd_coefficient : float Cross-polarization discrimination coefficient :math:`K_x\in[0,1]` as defined in :eq:`xpd`. Only relevant if ``scattering_coefficient``>0. Defaults to 0. scattering_pattern : ScatteringPattern :class:`~sionna.rt.ScatteringPattern` to be applied. Only relevant if ``scattering_coefficient``>0. Defaults to `None`, which implies a :class:`~sionna.rt.LambertianPattern`. frequency_update_callback : callable | `None` An optional callable object used to obtain the material parameters from the scene's :attr:`~sionna.rt.Scene.frequency`. This callable must take as input the frequency [Hz] and must return the material properties as a tuple: ``(relative_permittivity, conductivity)``. If set to `None`, the material properties are constant and equal to ``relative_permittivity`` and ``conductivity``. Defaults to `None`. dtype : tf.complex64 or tf.complex128 Datatype. Defaults to `tf.complex64`. """ def __init__(self, name, relative_permittivity=1.0, conductivity=0.0, scattering_coefficient=0.0, xpd_coefficient=0.0, scattering_pattern=None, frequency_update_callback=None, dtype=tf.complex64): if not isinstance(name, str): raise TypeError("`name` must be a string") self._name = name if dtype not in (tf.complex64, tf.complex128): msg = "`dtype` must be `tf.complex64` or `tf.complex128`" raise ValueError(msg) self._dtype = dtype self._rdtype = dtype.real_dtype if scattering_pattern is None: scattering_pattern = LambertianPattern(dtype=dtype) self.scattering_pattern = scattering_pattern self.scattering_coefficient = scattering_coefficient self.xpd_coefficient = xpd_coefficient if frequency_update_callback is None: self.relative_permittivity = relative_permittivity self.conductivity = conductivity # Save the callback for when the frequency is updated # or if the RadioMaterial is added to a scene self._frequency_update_callback = frequency_update_callback # When loading a scene, the custom materials (i.e., the materials not # baked-in Sionna but defined by the user) are not defined yet. # If when loading a scene a non-defined material is encountered, # then a "placeholder" material is created which is used until the # material is defined by the user. # Note that propagation simulation cannot be done if placeholders are # used. self._is_placeholder = False # Is this material a placeholder # Set of objects identifiers that use this material self._objects_using = set() @property def name(self): """ str (read-only) : Name of the radio material """ return self._name @property def relative_permittivity(self): r""" tf.float : Get/set the relative permittivity :math:`\varepsilon_r` :eq:`eta` """ return self._relative_permittivity @relative_permittivity.setter def relative_permittivity(self, v): if isinstance(v, tf.Variable): if v.dtype != self._rdtype: msg = f"`relative_permittivity` must have dtype={self._rdtype}" raise TypeError(msg) else: self._relative_permittivity = v else: self._relative_permittivity = tf.cast(v, self._rdtype) @property def relative_permeability(self): r""" tf.float (read-only) : Relative permeability :math:`\mu_r` :eq:`mu`. Defaults to 1. """ return tf.cast(1., self._rdtype) @property def conductivity(self): r""" tf.float: Get/set the conductivity :math:`\sigma` [S/m] :eq:`eta` """ return self._conductivity @conductivity.setter def conductivity(self, v): if isinstance(v, tf.Variable): if v.dtype != self._rdtype: msg = f"`conductivity` must have dtype={self._rdtype}" raise TypeError(msg) else: self._conductivity = v else: self._conductivity = tf.cast(v, self._rdtype) @property def scattering_coefficient(self): r""" tf.float: Get/set the scattering coefficient :math:`S\in[0,1]` :eq:`scattering_coefficient`. """ return self._scattering_coefficient @scattering_coefficient.setter def scattering_coefficient(self, v): if isinstance(v, tf.Variable): if v.dtype != self._rdtype: msg=f"`scattering_coefficient` must have dtype={self._rdtype}" raise TypeError(msg) else: self._scattering_coefficient = v else: self._scattering_coefficient = tf.cast(v, self._rdtype) @property def xpd_coefficient(self): r""" tf.float: Get/set the cross-polarization discrimination coefficient :math:`K_x\in[0,1]` :eq:`xpd`. """ return self._xpd_coefficient @xpd_coefficient.setter def xpd_coefficient(self, v): if isinstance(v, tf.Variable): if v.dtype != self._rdtype: msg=f"`xpd_coefficient` must have dtype={self._rdtype}" raise TypeError(msg) else: self._xpd_coefficient = v else: self._xpd_coefficient = tf.cast(v, self._rdtype) @property def scattering_pattern(self): r""" ScatteringPattern: Get/set the ScatteringPattern. """ return self._scattering_pattern @scattering_pattern.setter def scattering_pattern(self, v): if not isinstance(v, ScatteringPattern) and v is not None: raise ValueError("Not a valid instanc of ScatteringPattern") self._scattering_pattern = v @property def complex_relative_permittivity(self): r""" tf.complex (read-only) : Complex relative permittivity :math:`\eta` :eq:`eta` """ epsilon_0 = DIELECTRIC_PERMITTIVITY_VACUUM eta_prime = self.relative_permittivity sigma = self.conductivity frequency = scene.Scene().frequency omega = tf.cast(2.*PI*frequency, self._rdtype) return tf.complex(eta_prime, -tf.math.divide_no_nan(sigma, epsilon_0*omega)) @property def frequency_update_callback(self): """ callable : Get/set frequency update callback function """ return self._frequency_update_callback @frequency_update_callback.setter def frequency_update_callback(self, value): self._frequency_update_callback = value self.frequency_update() @property def well_defined(self): """bool : Get if the material is well-defined""" # pylint: disable=chained-comparison return ((self._conductivity >= 0.) and (self.relative_permittivity >= 1.) and (0. <= self.scattering_coefficient <= 1.) and (0. <= self.xpd_coefficient <= 1.) and (0. <= self.scattering_pattern.lambda_ <= 1.)) @property def use_counter(self): """ int : Number of scene objects using this material """ return len(self._objects_using) @property def is_used(self): """bool : Indicator if the material is used by at least one object of the scene""" return self.use_counter > 0 @property def using_objects(self): """ [num_using_objects], tf.int : Identifiers of the objects using this material """ tf_objects_using = tf.cast(tuple(self._objects_using), tf.int32) return tf_objects_using ############################################## # Internal methods. # Should not be documented. ############################################## def frequency_update(self): # pylint: disable=line-too-long r"""Callback for when the frequency is updated """ if self._frequency_update_callback is None: return parameters = self._frequency_update_callback(scene.Scene().frequency) relative_permittivity, conductivity = parameters self.relative_permittivity = relative_permittivity self.conductivity = conductivity def add_object_using(self, object_id): """ Add an object to the set of objects using this material """ self._objects_using.add(object_id) def discard_object_using(self, object_id): """ Remove an object from the set of objects using this material """ assert object_id in self._objects_using,\ f"Object with id {object_id} is not in the set of {self.name}" self._objects_using.discard(object_id) @property def is_placeholder(self): """ bool : Get/set if this radio material is a placeholder """ return self._is_placeholder @is_placeholder.setter def is_placeholder(self, v): self._is_placeholder = v def assign(self, rm): """ Assign new values to the radio material properties from another radio material ``rm`` Input ------ rm : :class:`~sionna.rt.RadioMaterial Radio material from which to assign the new values """ if not isinstance(rm, RadioMaterial): raise TypeError("`rm` is not a RadioMaterial") self.relative_permittivity = rm.relative_permittivity self.conductivity = rm.conductivity self.scattering_coefficient = rm.scattering_coefficient self.xpd_coefficient = rm.xpd_coefficient self.scattering_pattern = rm.scattering_pattern self.frequency_update_callback = rm.frequency_update_callback