#
# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Miscellaneous utilities"""
from contextlib import contextmanager
import drjit as dr
import mitsuba as mi
from .complex import cpx_sqrt
[docs]
def complex_sqrt(x : mi.Complex2f) -> mi.Complex2f:
r"""
Computes the square root of a complex number :math:`x`
The following formula is implemented to compute the square roots of complex
numbers:
https://en.wikipedia.org/wiki/Square_root#Algebraic_formula
:param x: Complex number
"""
return mi.Complex2f(*cpx_sqrt((dr.real(x), dr.imag(x))))
[docs]
def isclose(
a : mi.Float,
b : mi.Float,
rtol : mi.Float = 1e-5,
atol : mi.Float = 1e-8
) -> mi.Bool:
# pylint: disable=line-too-long
r"""
Returns an array of boolean in which an element is set to `True` if the
corresponding entries in ``a`` and ``b`` are equal within a tolerance
More precisely, this function returns `True` for the :math:`i^{th}` element
if:
.. math::
|\texttt{a}[i] - \texttt{b}[i]| < \texttt{atol} + \texttt{rtol} \cdot \texttt{b}[i]
:param a: First input array to compare
:param b: Second input array to compare
:param rtol: Relative error threshold
:param atol: Absolute error threshold
"""
close = dr.abs(a-b) < atol + dr.abs(b)*rtol
close &= ~dr.isinf(b)
close &= ~(dr.isnan(a) | dr.isnan(b))
return close
[docs]
def log10(x : mi.Float) -> mi.Float:
r"""
Evaluates the base-10 logarithm
:param x: Input value
"""
return dr.log(x)/dr.log(10.)
[docs]
def sinc(x : mi.Float) -> mi.Float:
r"""
Evaluates the normalized sinc function
The sinc function is defined as :math:`\sin(\pi x)/(\pi x)`
for any :math:`x \neq 0` and equals :math:`0` for :math:`x=0`.
"""
x = dr.pi*x
return dr.select(x==0, 1, dr.sin(x)*dr.rcp(x))
[docs]
def watt_to_dbm(x : mi.Float) -> mi.Float:
r"""
Converts Watt to dBm
Implements the following formula:
.. math::
P_{dBm} = 30 + 10 \log_{10}(P_W)
:param x: Power [W]
"""
return log10(x)*10. + 30.
[docs]
def dbm_to_watt(x : mi.Float) -> mi.Float:
r"""
Converts dBm to Watt
Implements the following formula:
.. math::
P_W = 10^{\frac{P_{dBm}-30}{10}}
:param x: Power [dBm]
"""
return dr.power(10., (x - 30.) / 10.)
def spectrum_to_matrix_4f(s : mi.Spectrum) -> mi.Matrix4f:
r"""
Builds a :class:`mi.Matrix4f` from a :class:`mi.Spectrum` object
:param s: Mitsuba Spectrum object
"""
m = mi.Matrix4f(s.array.x.x.x, s.array.x.y.x, s.array.x.z.x, s.array.x.w.x,
s.array.y.x.x, s.array.y.y.x, s.array.y.z.x, s.array.y.w.x,
s.array.z.x.x, s.array.z.y.x, s.array.z.z.x, s.array.z.w.x,
s.array.w.x.x, s.array.w.y.x, s.array.w.z.x, s.array.w.w.x)
return m
@contextmanager
def scoped_set_log_level(level: mi.LogLevel):
r"""
Context manager for running Mitsuba with a set log level
:param level: Log level to use within the context
"""
logger = mi.Thread.thread().logger()
previous = logger.log_level()
logger.set_log_level(level)
try:
yield
finally:
logger.set_log_level(previous)
[docs]
def sigmoid(x : mi.Float) -> mi.Float:
r"""
Evaluates the sigmoid of ``x``
:param x: Input value
"""
# Clip to avoid extreme exponential values
x = dr.clip(x, -80, 80)
y = dr.select(x >= 0.,
1.*dr.rcp(1. + dr.exp(-x)),
dr.exp(x)*dr.rcp(1. + dr.exp(x)))
return y
[docs]
def subcarrier_frequencies(num_subcarriers: int,
subcarrier_spacing: float) -> mi.Float:
# pylint: disable=line-too-long
r"""
Compute the baseband frequencies of ``num_subcarrier`` subcarriers spaced by
``subcarrier_spacing``, i.e.,
>>> # If num_subcarrier is even:
>>> frequencies = [-num_subcarrier/2, ..., 0, ..., num_subcarrier/2-1] * subcarrier_spacing
>>>
>>> # If num_subcarrier is odd:
>>> frequencies = [-(num_subcarrier-1)/2, ..., 0, ..., (num_subcarrier-1)/2] * subcarrier_spacing
:param num_subcarriers: Number of subcarriers
:param subcarrier_spacing: Subcarrier spacing [Hz]
:return: Baseband frequencies of subcarriers
"""
if num_subcarriers % 2 == 0:
start=int(-num_subcarriers/2)
limit=int(num_subcarriers/2)
else:
start=int(-(num_subcarriers-1)/2)
limit=int((num_subcarriers-1)/2+1)
frequencies = dr.arange(mi.Float, start=start, stop=limit)
frequencies *= subcarrier_spacing
return frequencies
def map_angle_to_canonical_range(x):
r"""
Maps an angle to the canonical range :math:`[0, 2\pi)`
:param x: Input angle
"""
return x - dr.two_pi * dr.floor(x*dr.rcp(dr.two_pi))