Source code for hermespy.channel.fading.exponential

# -*- coding: utf-8 -*-

from __future__ import annotations
from typing_extensions import override

import numpy as np

from hermespy.core import DeserializationProcess, SerializationProcess
from .fading import AntennaCorrelation, MultipathFadingChannel
from ..channel import Channel

__author__ = "Tobias Kronauer"
__copyright__ = "Copyright 2024, Barkhausen Institut gGmbH"
__credits__ = ["Tobias Kronauer", "Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.5.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"


[docs] class Exponential(MultipathFadingChannel): """Exponential multipath fading channel model.""" __exponential_truncation: float = 1e-5 __tap_interval: float __rms_delay: float def __init__( self, tap_interval: float, rms_delay: float, correlation_distance: float = MultipathFadingChannel._DEFAULT_DECORRELATION_DISTANCE, num_sinusoids: int = MultipathFadingChannel._DEFAULT_NUM_SINUSOIDS, los_angle: float | None = None, doppler_frequency: float = MultipathFadingChannel._DEFAULT_DOPPLER_FREQUENCY, los_doppler_frequency: float | None = None, antenna_correlation: AntennaCorrelation | None = None, gain: float = MultipathFadingChannel._DEFAULT_GAIN, seed: int | None = None, ) -> None: """ Args: tap_interval: Tap interval in seconds. rms_delay: Root-Mean-Squared delay in seconds. correlation_distance: Distance at which channel samples are considered to be uncorrelated. :math:`\\infty` by default, i.e. the channel is considered to be fully correlated in space. num_sinusoids: Number of sinusoids used to sample the statistical distribution. Denoted by :math:`N` within the respective equations. los_angle: Angle phase of the line of sight component within the statistical distribution. doppler_frequency: Doppler frequency shift of the statistical distribution. Denoted by :math:`\\omega_{\\ell}` within the respective equations. antenna_correlation: Antenna correlation model. By default, the channel assumes ideal correlation, i.e. no cross correlations. gain: Linear power gain factor a signal experiences when being propagated over this realization. :math:`1.0` by default. seed: Seed used to initialize the pseudo-random number generator. Raises: ValueError: On invalid arguments. """ if tap_interval <= 0.0: raise ValueError("Tap interval must be greater than zero") if rms_delay <= 0.0: raise ValueError("Root-Mean-Squared delay must be greater than zero") self.__tap_interval = tap_interval self.__rms_delay = rms_delay rms_norm = rms_delay / tap_interval # Calculate the decay exponent alpha based on an infinite power delay profile, in which case # rms_delay = exp(-alpha/2)/(1-exp(-alpha)), cf. geometric distribution. # Truncate the distributions for paths whose average power is very # small (less than exponential_truncation). alpha = -2 * np.log((-1 + np.sqrt(1 + 4 * rms_norm**2)) / (2 * rms_norm)) max_delay_in_samples = int(-np.ceil(np.log(Exponential.__exponential_truncation) / alpha)) delays = np.arange(max_delay_in_samples + 1) * tap_interval power_profile = np.exp(-alpha * np.arange(max_delay_in_samples + 1)) rice_factors = np.zeros(delays.shape) # Init base class with pre-defined model parameters MultipathFadingChannel.__init__( self, delays, power_profile, rice_factors, correlation_distance, num_sinusoids, los_angle, doppler_frequency, los_doppler_frequency, antenna_correlation, gain, seed, ) @property def tap_interval(self) -> float: """Tap interval. Returns: Tap interval in seconds. """ return self.__tap_interval @property def rms_delay(self) -> float: """Root mean squared channel delay. Returns: Delay in seconds. """ return self.__rms_delay
[docs] @override def serialize(self, process: SerializationProcess) -> None: process.serialize_floating(self.tap_interval, "tap_interval") process.serialize_floating(self.rms_delay, "rms_delay") process.serialize_floating(self.correlation_distance, "correlation_distance") process.serialize_integer(self.num_sinusoids, "num_sinusoids") process.serialize_floating(self.los_angle, "los_angle") process.serialize_floating(self.doppler_frequency, "doppler_frequency") process.serialize_floating(self.los_doppler_frequency, "los_doppler_frequency") if self.antenna_correlation is not None: process.serialize_object(self.antenna_correlation, "antenna_correlation") Channel.serialize(self, process)
[docs] @classmethod @override def Deserialize(cls, process: DeserializationProcess) -> Exponential: return cls( tap_interval=process.deserialize_floating("tap_interval"), rms_delay=process.deserialize_floating("rms_delay"), correlation_distance=process.deserialize_floating( "correlation_distance", cls._DEFAULT_DECORRELATION_DISTANCE ), num_sinusoids=process.deserialize_integer("num_sinusoids", cls._DEFAULT_NUM_SINUSOIDS), los_angle=process.deserialize_floating("los_angle"), doppler_frequency=process.deserialize_floating( "doppler_frequency", cls._DEFAULT_DOPPLER_FREQUENCY ), los_doppler_frequency=process.deserialize_floating("los_doppler_frequency"), antenna_correlation=process.deserialize_object( "antenna_correlation", AntennaCorrelation, None ), **Channel._DeserializeParameters(process), # type: ignore[arg-type] )