# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Set, Tuple
from typing_extensions import override
from hermespy.core import DeserializationProcess, SerializationProcess
from ..channel import ChannelSampleHook, LinkState
from ..consistent import ConsistentGenerator, ConsistentRealization, ConsistentUniform
from .delay import DelayChannelBase, DelayChannelRealization, DelayChannelSample
__author__ = "Jan Adler"
__copyright__ = "Copyright 2024, Barkhausen Institut gGmbH"
__credits__ = ["Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.5.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"
[docs]
class RandomDelayChannelRealization(DelayChannelRealization):
"""Realization of a random delay channel.
Generated from :class:`RandomDelayChannel's<RandomDelayChannel>` :meth:`_realize<RandomDelayChannel._realize>` routine.
"""
__consistent_realization: ConsistentRealization
__delay_variable: ConsistentUniform
__delay: float | Tuple[float, float]
def __init__(
self,
consistent_realization: ConsistentRealization,
delay_variable: ConsistentUniform,
delay: float | Tuple[float, float],
model_propagation_loss: bool,
sample_hooks: Set[ChannelSampleHook[DelayChannelSample]],
gain: float,
) -> None:
# Initialize base class
DelayChannelRealization.__init__(self, model_propagation_loss, sample_hooks, gain)
# Store attributes
self.__consistent_realization = consistent_realization
self.__delay_variable = delay_variable
self.__delay = delay
@property
def delay(self) -> float | Tuple[float, float]:
"""Assumed propagation delay in seconds.
If set to a scalar floating point, the delay is assumed to be constant.
If set to a tuple of two floats, the tuple values indicate the mininum and maxium values of a uniform distribution, respectively.
Raises:
ValueError: If the delay is set to a negative value.
ValueError: If the delay is set to a tuple of two values where the first value is greater than the second value.
"""
return self.__delay
def _sample(self, state: LinkState) -> DelayChannelSample:
if isinstance(self.__delay, float):
delay = self.__delay
else:
# Sample the consistent realization
consistent_sample = self.__consistent_realization.sample(
state.transmitter.position, state.receiver.position
)
# Realize the delay
delay = float(
self.__delay[0]
+ (self.__delay[1] - self.__delay[0])
* self.__delay_variable.sample(consistent_sample)
)
# Generate a sample
return DelayChannelSample(delay, self.model_propagation_loss, self.gain, state)
[docs]
@override
def serialize(self, process: SerializationProcess) -> None:
process.serialize_object(self.__consistent_realization, "consistent_realization")
process.serialize_object(self.__delay_variable, "delay_variable")
process.serialize_range(self.__delay, "delay")
DelayChannelRealization.serialize(self, process)
[docs]
@classmethod
@override
def Deserialize(cls, process: DeserializationProcess) -> RandomDelayChannelRealization:
return RandomDelayChannelRealization(
process.deserialize_object("consistent_realization", ConsistentRealization),
process.deserialize_object("delay_variable", ConsistentUniform),
process.deserialize_range("delay"),
sample_hooks=set(),
**DelayChannelRealization._DeserializeParameters(process), # type: ignore[arg-type]
)
[docs]
class RandomDelayChannel(DelayChannelBase[RandomDelayChannelRealization]):
"""Delay channel assuming random propagation delays."""
__DEFAULT_DECORRELATION_DISTANCE = float("inf")
__delay: float | Tuple[float, float]
def __init__(
self,
delay: float | Tuple[float, float],
decorrelation_distance: float = __DEFAULT_DECORRELATION_DISTANCE,
model_propagation_loss: bool = True,
gain: float = DelayChannelBase._DEFAULT_GAIN,
seed: int | None = None,
) -> None:
"""
Args:
delay:
Assumed propagation delay in seconds.
If a scalar floating point, the delay is assumed to be constant.
If a tuple of two floats, the tuple values indicate the mininum and maxium values of a uniform distribution, respectively.
decorrelation_distance:
Distance in meters at which the channel decorrelates.
By default, the channel is assumed to be static in space.
model_propagation_loss:
Should free space propagation loss be modeled?
Enabled by default.
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.
"""
# Initialize base class
DelayChannelBase.__init__(self, model_propagation_loss, gain, seed)
# Store attributes
self.delay = delay
self.decorrelation_distance = decorrelation_distance
self.__consistent_generator = ConsistentGenerator(self)
self.__delay_variable = self.__consistent_generator.uniform()
@property
def delay(self) -> float | Tuple[float, float]:
"""Assumed propagation delay in seconds.
If set to a scalar floating point, the delay is assumed to be constant.
If set to a tuple of two floats, the tuple values indicate the mininum and maxium values of a uniform distribution, respectively.
Raises:
ValueError: If the delay is set to a negative value.
ValueError: If the delay is set to a tuple of two values where the first value is greater than the second value.
"""
return self.__delay
@delay.setter
def delay(self, value: float | Tuple[float, float]) -> None:
if isinstance(value, float):
if value < 0.0:
raise ValueError(f"Delay must be greater or equal to zero (not {value})")
elif isinstance(value, tuple):
if len(value) != 2:
raise ValueError("Delay limit tuple must contain two entries")
if any(v < 0.0 for v in value):
raise ValueError(
f"Delay must be greater or equal to zero (not {value[0]} and {value[1]})"
)
if value[0] > value[1]:
raise ValueError("First tuple entry must be smaller or equal to second tuple entry")
else:
raise ValueError("Unsupported value type")
self.__delay = value
@property
def decorrelation_distance(self) -> float:
"""Distance in meters at which the channel decorrelates.
Raises:
ValueError: If the decorrelation distance is set to a negative value.
"""
return self.__decorrelation_distance
@decorrelation_distance.setter
def decorrelation_distance(self, value: float) -> None:
if value < 0.0:
raise ValueError(
f"Decorrelation distance must be greater or equal to zero (not {value})"
)
self.__decorrelation_distance = value
[docs]
def _realize(self) -> RandomDelayChannelRealization:
# Realize the consistent generator
consistent_realization = self.__consistent_generator.realize(self.decorrelation_distance)
# Return the realization
return RandomDelayChannelRealization(
consistent_realization,
self.__delay_variable,
self.__delay,
self.model_propagation_loss,
self.sample_hooks,
self.gain,
)
[docs]
@override
def serialize(self, process: SerializationProcess) -> None:
process.serialize_object(self.__delay_variable, "delay_variable")
process.serialize_range(self.delay, "delay")
process.serialize_floating(self.__decorrelation_distance, "decorrelation_distance")
DelayChannelBase.serialize(self, process)
[docs]
@classmethod
@override
def Deserialize(cls, process: DeserializationProcess) -> RandomDelayChannel:
return RandomDelayChannel(
process.deserialize_range("delay"),
process.deserialize_floating(
"decorrelation_distance", cls.__DEFAULT_DECORRELATION_DISTANCE
),
**DelayChannelBase._DeserializeParameters(process), # type: ignore[arg-type]
)