# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Generic, Set, TypeVar
import numpy as np
from h5py import Group
from scipy.constants import speed_of_light
from hermespy.core import AntennaMode, ChannelStateInformation, ChannelStateFormat, SignalBlock
from hermespy.tools import amplitude_path_loss
from ..channel import (
Channel,
ChannelSample,
LinkState,
ChannelSampleHook,
ChannelRealization,
InterpolationMode,
)
__author__ = "Jan Adler"
__copyright__ = "Copyright 2024, Barkhausen Institut gGmbH"
__credits__ = ["Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.3.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"
DCRT = TypeVar("DCRT", bound="DelayChannelRealization")
"""Type of delay channel realization"""
[docs]
class DelayChannelSample(ChannelSample):
"""Sample of a delay channel."""
def __init__(
self, delay: float, model_propagation_loss: bool, gain: float, state: LinkState
) -> None:
# Initialize base class
ChannelSample.__init__(self, state)
# Store attributes
self.__delay = delay
self.__model_propagation_loss = model_propagation_loss
self.__gain = gain
@property
def delay(self) -> float:
"""Propagation delay in seconds."""
return self.__delay
@property
def model_propagation_loss(self) -> bool:
"""Should free space propagation loss be modeled?"""
return self.__model_propagation_loss
@property
def gain(self) -> float:
"""Linear power gain factor a signal experiences when being propagated over this realization."""
return self.__gain
@property
def expected_energy_scale(self) -> float:
return self.__path_gain(self.carrier_frequency)
def __spatial_response(self) -> np.ndarray:
receiver_position = self.receiver_state.position
transmitter_position = self.transmitter_state.position
if np.all(receiver_position == transmitter_position):
return np.ones(
(
self.receiver_state.antennas.num_receive_antennas,
self.transmitter_state.antennas.num_transmit_antennas,
),
dtype=np.complex_,
)
transmit_response = self.transmitter_state.antennas.cartesian_array_response(
self.carrier_frequency, self.receiver_state.position, "global", AntennaMode.TX
)
receive_response = self.receiver_state.antennas.cartesian_array_response(
self.carrier_frequency, self.transmitter_state.position, "global", AntennaMode.RX
)
return receive_response @ transmit_response.T
def __path_gain(self, carrier_frequency: float) -> float:
"""Compute the realization's linear path gain factor.
carrier_frequency (float):
Central frequency of the propagation in :math:`\\mathrm{Hz}`.
Returns: Linear path gain scaling the amplitude of a propagated signal.
"""
gain_factor = self.gain
if self.model_propagation_loss:
if carrier_frequency == 0.0:
raise RuntimeError(
"Transmitting device's carrier frequency may not be zero, disable propagation path loss modeling"
)
if self.delay > 0:
gain_factor *= amplitude_path_loss(carrier_frequency, self.delay * speed_of_light)
return gain_factor
def _propagate(self, signal: SignalBlock, interpolation: InterpolationMode) -> SignalBlock:
delay_samples = round(self.delay * self.bandwidth)
spatial_response = self.__spatial_response()
gain_factor = self.__path_gain(self.carrier_frequency)
propagated_samples = np.append(
np.zeros(
(self.receiver_state.antennas.num_receive_antennas, delay_samples), np.complex_
),
gain_factor * spatial_response @ signal,
)
propagated_signal = SignalBlock(propagated_samples, signal._offset)
return propagated_signal
[docs]
def state(
self,
num_samples: int,
max_num_taps: int,
interpolation_mode: InterpolationMode = InterpolationMode.NEAREST,
) -> ChannelStateInformation:
delay_samples = round(self.delay * self.bandwidth)
spatial_response = self.__spatial_response()
gain_factor = self.__path_gain(self.carrier_frequency)
cir = np.zeros(
(
self.receiver_state.antennas.num_receive_antennas,
self.transmitter_state.antennas.num_transmit_antennas,
num_samples,
min(1 + delay_samples, max_num_taps),
),
dtype=np.complex_,
)
if delay_samples < max_num_taps:
cir[:, :, :, delay_samples] = gain_factor * spatial_response[:, :, np.newaxis]
state = ChannelStateInformation(ChannelStateFormat.IMPULSE_RESPONSE, cir)
return state
[docs]
class DelayChannelRealization(ChannelRealization[DelayChannelSample]):
"""Base class for delay channel realizations."""
__model_propagation_loss: bool
def __init__(
self,
model_propagation_loss: bool,
sample_hooks: Set[ChannelSampleHook[DelayChannelSample]],
gain: float,
) -> None:
"""
Args:
model_propagation_loss (bool):
Should free space propagation loss be modeled?
gain (float):
Linear power gain factor a signal experiences when being propagated over this realization.
"""
# Initialize base class
ChannelRealization.__init__(self, sample_hooks, gain)
# Initialize class attributes
self.__model_propagation_loss = model_propagation_loss
@property
def model_propagation_loss(self) -> bool:
"""Should free-space propagation losses be modeled?"""
return self.__model_propagation_loss
def _reciprocal_sample(
self, sample: DelayChannelSample, state: LinkState
) -> DelayChannelSample:
return DelayChannelSample(sample.delay, self.model_propagation_loss, self.gain, state)
[docs]
def to_HDF(self, group: Group) -> None:
group.attrs["model_propagation_loss"] = self.model_propagation_loss
group.attrs["gain"] = self.gain
[docs]
class DelayChannelBase(Generic[DCRT], Channel[DCRT, DelayChannelSample]):
"""Base of delay channel models."""
__model_propagation_loss: bool
def __init__(self, model_propagation_loss: bool = True, gain: float = 1.0, **kwargs) -> None:
"""
Args:
model_propagation_loss (bool, optional):
Should free space propagation loss be modeled?
Enabled by default.
gain (float, optional):
Linear power gain factor a signal experiences when being propagated over this realization.
:math:`1.0` by default.
\**kawrgs:
:class:`Channel` base class initialization arguments.
"""
# Initialize base class
Channel.__init__(self, gain, **kwargs)
# Initialize class attributes
self.__model_propagation_loss = model_propagation_loss
@property
def model_propagation_loss(self) -> bool:
"""Should free space propagation loss be modeled?"""
return self.__model_propagation_loss
@model_propagation_loss.setter
def model_propagation_loss(self, value: bool) -> None:
self.__model_propagation_loss = value