Source code for sionna.rt.path_solvers.path_solver


#
# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Paths solver: Compute propagation paths"""

import drjit as dr

from .sb_candidate_generator import SBCandidateGenerator
from .image_method import ImageMethod
from .field_calculator import FieldCalculator
from .paths import Paths
from sionna.rt import Scene


[docs] class PathSolver: # pylint: disable=line-too-long r""" Class implementing a path solver A path solver computes propagation paths between the antennas of all transmitters and receivers in the a scene. For each propagation path :math:`i`, the corresponding channel coefficient :math:`a_i` and delay :math:`\tau_i`, the angles of departure :math:`(\theta_{\text{T},i}, \varphi_{\text{T},i})` and arrival :math:`(\theta_{\text{R},i}, \varphi_{\text{R},i})`, as well as the Doppler shifts :math:`f_{\Delta, i}` are computed. For more detail, see :eq:`H_final`. This path solver currently supports line-of-sigth, specular and diffuse reflection, as well as refraction. Paths can consist of any of these interaction types, in any order. Different propagation phenomena can be individually enabled/disabled. This solver assumes that materials are thin enough that their effect on transmitted rays (i.e., rays that traverse the materials through double refraction) is negligible. Rays are traced without angular deflection, and objects like walls should be modeled as single flat surfaces having an attached radio material that accounts for their :attr:`~sionna.rt.RadioMaterial.thickness`. This approach may be inaccurate for very thick objects. The figure below illustrates this model, where :math:`E_i` is the incident electric field, :math:`E_r` is the reflected field and :math:`E_t` is the transmitted field. The Jones matrices, :math:`\mathbf{R}(d)` and :math:`\mathbf{T}(d)`, represent the effects of reflection and transmission, respectively, and depend on the slab thickness, :math:`d`. .. figure:: ../figures/transmission_model.png :width: 80% :align: center If synthetic arrays are used (``synthetic_array`` is `True`), transmitters and receivers are modelled as if they had a single antenna located at their :attr:`~sionna.rt.RadioDevice.position`. The channel responses for each individual antenna of the arrays are then computed "synthetically" by applying appropriate phase shifts. This reduces the complexity significantly for large arrays. Time evolution of the channel coefficients can be simulated with using :meth:`~sionna.rt.Paths.cir` and :meth:`~sionna.rt.Paths.cfr` methods of the returned :class:`~sionna.rt.Paths` object. Example ------- .. code-block:: python import sionna from sionna.rt import load_scene, Transmitter, Receiver, PlanarArray, PathSolver import mitsuba as mi # Load example scene scene = load_scene(sionna.rt.scene.munich) # Configure antenna array for all transmitters scene.tx_array = PlanarArray(num_rows=8, num_cols=2, vertical_spacing=0.7, horizontal_spacing=0.5, pattern="tr38901", polarization="VH") # Configure antenna array for all receivers scene.rx_array = PlanarArray(num_rows=1, num_cols=1, vertical_spacing=0.5, horizontal_spacing=0.5, pattern="dipole", polarization="cross") # Create transmitter tx = Transmitter(name="tx", position=mi.Point3f(8.5,21,27), orientation=mi.Point3f(0,0,0)) scene.add(tx) # Create a receiver rx = Receiver(name="rx", position=mi.Point3f(45,90,1.5), orientation=mi.Point3f(0,0,0)) scene.add(rx) # TX points towards RX tx.look_at(rx) # Compute paths solver = PathSolver() paths = solver(scene) # Open preview showing paths scene.preview(paths=paths, resolution=[1000,600], clip_at=15.) .. figure:: ../figures/paths_preview.png :align: center """ def __init__(self): # Instantiate the Candidate Generator self._candidate_generator = SBCandidateGenerator() # Instantiate the Image Method solver self._image_method = ImageMethod() # Instantiate the Field Calculator self._field_calculator = FieldCalculator() @property def loop_mode(self): # pylint: disable=line-too-long r"""Get/set the Dr.Jit mode used to evaluate the loops that implement the solver. Should be one of "evaluated" or "symbolic". Symbolic mode (default) is the fastest one but does not support automatic differentiation. For more details, see the `corresponding Dr.Jit documentation <https://drjit.readthedocs.io/en/latest/cflow.html#sym-eval>`_. :type: "evaluated" | "symbolic" """ return self._field_calculator.loop_mode @loop_mode.setter def loop_mode(self, mode): if mode not in ("evaluated", "symbolic"): raise ValueError("Invalid loop mode. Must be either 'evaluated'" " or 'symbolic'") self._image_method.loop_mode = mode self._field_calculator.loop_mode = mode
[docs] def __call__(self, scene : Scene, max_depth : int = 3, max_num_paths_per_src : int = 1000000, samples_per_src : int = 1000000, synthetic_array : bool = True, los : bool = True, specular_reflection : bool = True, diffuse_reflection : bool = False, refraction : bool = True, seed : int = 42) -> Paths: # pylint: disable=line-too-long r""" Executes the solver :param scene: Scene for which to compute paths :param max_depth: Maximum depth :param max_num_paths_per_src: Maximum number of paths per source :param samples_per_src: Number of samples per source :param synthetic_array: If set to `True` (default), then the antenna arrays are applied synthetically :param los: Enable line-of-sight paths :param specular_reflection: Enables specular reflection :param diffuse_reflection: Enables diffuse reflection :param refraction: Enables refraction :param seed: Seed :return: Computed paths """ # Check that the scene is all set for simulations scene.all_set(radio_map=False) # Generates sources positions and orientations src_positions, src_orientations, rel_ant_positions_tx, tx_velocities =\ scene.sources(synthetic_array, True) tgt_positions, tgt_orientations, rel_ant_positions_rx, rx_velocities =\ scene.targets(synthetic_array, True) # Trace paths and compute channel impulse responses src_antenna_patterns = scene.tx_array.antenna_pattern.patterns tgt_antenna_patterns = scene.rx_array.antenna_pattern.patterns dr.make_opaque(src_positions, tgt_positions, src_orientations, tgt_orientations) # Generate candidates paths_buffer = self._candidate_generator( mi_scene=scene.mi_scene, src_positions=src_positions, tgt_positions=tgt_positions, samples_per_src=samples_per_src, max_num_paths_per_src=max_num_paths_per_src, max_depth=max_depth, los=los, specular_reflection=specular_reflection, diffuse_reflection=diffuse_reflection, refraction=refraction, seed=seed ) paths_buffer.schedule() dr.eval() # Shrink the paths buffer to fit the number of paths effectively found paths_buffer.shrink() # Detach the paths geometry to avoid differentiation through the # candidate generator paths_buffer.detach_geometry() # Solve specular chains and suffixes paths_buffer = self._image_method( scene=scene.mi_scene, paths=paths_buffer, src_positions=src_positions, tgt_positions=tgt_positions ) # Compute channel coefficients and delays paths_buffer = self._field_calculator( scene=scene.mi_scene, wavelength=scene.wavelength, paths=paths_buffer, samples_per_src=samples_per_src, src_positions=src_positions, tgt_positions=tgt_positions, src_orientations=src_orientations, tgt_orientations=tgt_orientations, src_antenna_patterns=src_antenna_patterns, tgt_antenna_patterns=tgt_antenna_patterns, specular_reflection=specular_reflection, diffuse_reflection=diffuse_reflection, refraction=refraction ) # Discard invalid paths # It was experimentally found that discarding the invalid paths # before re-organizing them into high-dimensional tensors leads to # significant speedups paths_buffer.discard_invalid() # Build the path object paths = Paths(scene, src_positions, tgt_positions, tx_velocities, rx_velocities, synthetic_array, paths_buffer, rel_ant_positions_tx, rel_ant_positions_rx) return paths