# -*- coding: utf-8 -*-
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Dict, Generic, Optional, Tuple, Type, TypeVar, TYPE_CHECKING
from h5py import Group
from hermespy.core import (
Device,
DeviceOutput,
RandomNode,
SerializableEnum,
Signal,
ChannelStateInformation,
Serializable,
)
if TYPE_CHECKING:
from hermespy.simulation import SimulatedDevice, SimulationScenario # pragma: no cover
__author__ = "Andre Noll Barreto"
__copyright__ = "Copyright 2023, Barkhausen Institut gGmbH"
__credits__ = ["Andre Noll Barreto", "Tobias Kronauer", "Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.2.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"
[docs]
class InterpolationMode(SerializableEnum):
"""Interpolation behaviour for sampling and resampling routines.
Considering a complex time series
.. math::
\\mathbf{s} = \\left[s_{0}, s_{1},\\,\\dotsc, \\, s_{M-1} \\right]^{\\mathsf{T}} \\in \\mathbb{C}^{M} \\quad \\text{with} \\quad s_{m} = s(\\frac{m}{f_{\\mathrm{s}}})
sampled at rate :math:`f_{\\mathrm{s}}`, so that each sample
represents a discrete sample of a time-continuous underlying function :math:`s(t)`.
Given only the time-discrete sample vector :math:`\\mathbf{s}`,
resampling refers to
.. math::
\\hat{s}(\\tau) = \\mathscr{F} \\left\\lbrace \\mathbf{s}, \\tau \\right\\rbrace
estimating a sample of the original time-continuous function at time :math:`\\tau` given only the discrete-time sample vector :math:`\\mathbf{s}`.
"""
NEAREST = 0
"""Interpolate to the nearest sampling instance.
.. math::
\\hat{s}(\\tau) = s_{\\lfloor \\tau f_{\\mathrm{s}} \\rfloor}
Very fast, but not very accurate.
"""
SINC = 1
"""Interpolate using sinc kernels.
Also known as the Whittaker-Kotel'nikov-Shannon interpolation formula :footcite:p:`2002:meijering`.
.. math::
\\hat{s}(\\tau) = \\sum_{m=0}^{M-1} s_{m} \\operatorname{sinc} \\left( \\tau f_{\\mathrm{s}} - m \\right)
Perfect for bandlimited signals, not very fast.
"""
CRT = TypeVar("CRT", bound="ChannelRealization")
"""Type of channel realization"""
CT = TypeVar("CT", bound="Channel")
"""Type of channel"""
[docs]
class ChannelRealization(object):
"""Realization of a wireless channel channel model.
Channel realizations represent the channel state during signal propagation.
They are generated by the :meth:`realize()<Channel.realize>` method of :class:`.Channel` instances.
"""
__alpha_device: Device
__beta_device: Device
__interpolation_mode: InterpolationMode
def __init__(
self,
alpha_device: Device,
beta_device: Device,
gain: float,
interpolation_mode: InterpolationMode = InterpolationMode.NEAREST,
) -> None:
"""
Args:
alpha_device (Device):
First device linked by the :class:`.Channel` instance that generated this realization.
beta_device (Device):
Second device linked by the :class:`.Channel` instance that generated this realization.
gain (float):
Linear power gain factor a signal experiences when being propagated over this realization.
interpolation_mode (InterpolationMode, optional):
Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate.
"""
# Initialize class attributes
self.__alpha_device = alpha_device
self.__beta_device = beta_device
self.__gain = gain
self.__interpolation_mode = interpolation_mode
@property
def alpha_device(self) -> Device:
"""First device linked by the :class:`.Channel` instance that generated this realization."""
return self.__alpha_device
@property
def beta_device(self) -> Device:
"""Second device linked by the :class:`.Channel` instance that generated this realization."""
return self.__beta_device
@property
def gain(self) -> float:
"""Linear power gain factor a signal experiences when being propagated over this realization."""
return self.__gain
@property
def interpolation_mode(self) -> InterpolationMode:
"""Default interpolation mode.
Referred to by the :meth:`propagate<.propagate>` and :meth:`state<.state>` routines if no interpolation mode is specified.
"""
return self.__interpolation_mode
[docs]
@abstractmethod
def _propagate(
self,
signal: Signal,
transmitter: Device,
receiver: Device,
interpolation: InterpolationMode,
) -> Signal:
"""Propagate radio-frequency band signals over a channel instance.
Abstract subroutine of :meth:`propagate()<ChannelRealization.propagate>`.
Args:
signal (Signal):
The signal to be propagated.
transmitter (Device):
Device transmitting the `signal` to be propagated over this realization.
receiver (Device):
Device receiving the propagated `signal` after propagation.
interpolation (InterpolationMode):
Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate.
Returns: The propagated signal.
"""
... # pragma: no cover
[docs]
def propagate(
self: CRT,
signal: DeviceOutput | Signal,
transmitter: Device | None = None,
receiver: Device | None = None,
interpolation_mode: InterpolationMode | None = None,
) -> ChannelPropagation[CRT]:
"""Propagate a signal model over this realization.
Let
.. math::
\\mathbf{X} = \\left[ \\mathbf{x}^{(0)}, \\mathbf{x}^{(1)},\\, \\dots,\\, \\mathbf{x}^{(M_\\mathrm{Tx} - 1)} \\right] \\in \\mathbb{C}^{N_\\mathrm{Tx} \\times M_\\mathrm{Tx}}
be the `signal` transmitted by `transmitter` and
.. math::
\\mathbf{Y} = \\left[ \\mathbf{y}^{(0)}, \\mathbf{y}^{(1)},\\, \\dots,\\, \\mathbf{x}^{(M_\\mathrm{Rx} - 1)} \\right] \\in \\mathbb{C}^{N_\\mathrm{Rx} \\times M_\\mathrm{Rx}}
the reception of `receiver`, this method implements the channel propagation equation
.. math::
\\mathbf{y}^{(m)} = \\sum_{\\tau = 0}^{m} \\mathbf{H}^{(m, \\tau)} \mathbf{x}^{(m-\\tau)} \\ \\text{.}
It wraps :meth:`._propagate`, applies the channel :attr:`.gain` and returns a :class:`ChannelPropagation<hermespy.channel.channel.ChannelPropagation>` instance.
If not specified, the transmitter and receiver are assumed to be the devices linked by the channel instance that generated this realization,
meaning the transmitter is :attr:`alpha_device<.alpha_device>` and receiver is :attr:`beta_device<.beta_device>`.
Args:
signal (DeviceOutput | Signal):
Signal model to be propagated.
transmitter (Device, optional):
Device transmitting the `signal` to be propagated over this realization.
If not specified :attr:`alpha_device<.alpha_device>` will be assumed.
receiver (Device, optional):
Device receiving the propagated `signal` after propagation.
If not specified :attr:`beta_device<.beta_device>` will be assumed.
interpolation_mode (InterpolationMode, optional):
Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate.
If not specified, the realization's default :attr:`.interpolation_mode` will be assumed.
Returns: All information generated by the propagation.
"""
# Infer parameters
_transmitter = self.alpha_device if transmitter is None else transmitter
_receiver = self.beta_device if receiver is None else receiver
_interpolation_mode = (
self.__interpolation_mode if interpolation_mode is None else interpolation_mode
)
# Convert signal argument to signal model
if isinstance(signal, DeviceOutput):
_signal = signal.mixed_signal
elif isinstance(signal, Signal):
_signal = signal
else:
raise ValueError("Signal is of unsupported type")
# Assert that the signal's number of streams matches the number of antennas of the transmitter
if _signal.num_streams != _transmitter.antennas.num_transmit_antennas:
raise ValueError(
f"Number of signal streams to be propagated does not match the number of transmitter antennas ({_signal.num_streams} != {_transmitter.antennas.num_transmit_antennas}))"
)
# Propagate signal
propagated_signal = self._propagate(_signal, _transmitter, _receiver, _interpolation_mode)
# Apply channel gain
propagated_signal.samples *= self.gain**0.5
# Return resulting channel propagation
propagation = ChannelPropagation[CRT](
self, propagated_signal, _transmitter, _receiver, _interpolation_mode
)
return propagation
[docs]
@abstractmethod
def state(
self,
transmitter: Device,
receiver: Device,
delay: float,
sampling_rate: float,
num_samples: int,
max_num_taps: int,
) -> ChannelStateInformation:
"""Generate the discrete channel state information from this channel realization.
Denoted by
.. math::
\\mathbf{H}^{(m, \\tau)} \\in \\mathbb{C}^{N_{\\mathrm{Rx}} \\times N_{\\mathrm{Tx}}}
within the respective equations.
Args:
transmitter (Device):
Device transmitting the signal for which the channel state information is generated.
receiver (Device):
Device receiving the signal for which the channel state information is generated.
delay (float):
Delay in seconds at which the state information should be generated.
sampling_rate (float):
Sampling rate of the state information's discrete samples in :math:`\\mathrm{Hz}`.
num_samples (int):
Number of discrete time-domain samples of the chanel state information.
max_num_taps (int):
Maximum number of delay taps considered per discrete time-domain sample.
Returns: The channel state information representing this channel realization.
"""
... # pragma: no cover
[docs]
def to_HDF(self, group: Group) -> None:
"""Serialize the object state to HDF5.
Dumps the object's state and additional information to a HDF5 group.
Args:
group (h5py.Group):
The HDF5 group to which the object is serialized.
"""
group.attrs["gain"] = self.gain
group.attrs["interpolation_mode"] = self.interpolation_mode.value
@classmethod
def _parameters_from_HDF(cls: Type[ChannelRealization], group: Group) -> Dict[str, Any]:
"""Deserialize the object's parameters from HDF5.
Intended to be used as a subroutine of :meth:`From_HDF`.
Returns: The object's parmeters as a keyword argument dictionary.
"""
return {
"gain": group.attrs.get("gain", 1.0),
"interpolation_mode": InterpolationMode(group.attrs.get("interpolation_mode", 0)),
}
[docs]
@classmethod
def From_HDF(cls: Type[CRT], group: Group, alpha_device: Device, beta_device: Device) -> CRT:
"""De-Serialized the object state from HDF5.
Recalls the object's state from a HDF5 group.
Args:
group (h5py.Group):
The HDF5 group from which the object state is recalled.
alpha_device (Device):
First device linked by the :class:`.Channel` instance that generated this realization.
beta_device (Device):
Second device linked by the :class:`.Channel` instance that generated this realization.
Returns: The object initialized from the HDF5 group state.
"""
return cls(alpha_device, beta_device, **cls._parameters_from_HDF(group))
[docs]
class DirectiveChannelRealization(Generic[CRT]):
"""Channel realization with specified directivity.
Generated by :attr:`realization<ChannelPropagation.realization>` property of :class:`ChannelPropagations<ChannelPropagation>`.
"""
__transmitter: Device
__receiver: Device
__realization: CRT
def __init__(self, transmitter: Device, receiver: Device, realization: CRT) -> None:
"""
Args:
transmitter (Device):
Device transmitting into the realized channel model.
Initializes the :attr:`.transmitter` attribute.
receiver (Device):
Device receiving from the realized channel model.
Initializes the :attr:`.receiver` attribute.
realization (CRT):
Realization of the channel linked by `transmitter` and `receiver`.
Initializes the :attr:`.realization` attribute.
"""
# Initialize class attributes
self.__transmitter = transmitter
self.__receiver = receiver
self.__realization = realization
@property
def transmitter(self) -> Device:
"""Device transmitting into the realized channel model."""
return self.__transmitter
@property
def receiver(self) -> Device:
"""Device receiving from the realized channel model."""
return self.__receiver
@property
def realization(self) -> CRT:
"""Wrapped non-directive channel realization."""
return self.__realization
[docs]
def propagate(
self, signal: DeviceOutput | Signal, interpolation_mode: InterpolationMode | None = None
) -> ChannelPropagation[CRT]:
"""Propagate a signal model over this realization.
Let
.. math::
\\mathbf{X} = \\left[ \\mathbf{x}^{(0)}, \\mathbf{x}^{(1)},\\, \\dots,\\, \\mathbf{x}^{(M_\\mathrm{Tx} - 1)} \\right] \\in \\mathbb{C}^{N_\\mathrm{Tx} \\times M_\\mathrm{Tx}}
be the `signal` transmitted by :attr:`.transmitter` and
.. math::
\\mathbf{Y} = \\left[ \\mathbf{y}^{(0)}, \\mathbf{y}^{(1)},\\, \\dots,\\, \\mathbf{x}^{(M_\\mathrm{Rx} - 1)} \\right] \\in \\mathbb{C}^{N_\\mathrm{Rx} \\times M_\\mathrm{Rx}}
the reception of :attr:`.receiver`, this method implements the channel propagation equation
.. math::
\\mathbf{y}^{(m)} = \\sum_{\\tau = 0}^{M_\\mathrm{Rx} - M_\\mathrm{Tx}} \\mathbf{H}^{(m, \\tau)} \mathbf{x}^{(m-\\tau)} \\ \\text{.}
Args:
signal (DeviceOutput | Signal):
Signal model to be propagated.
interpolation_mode (InterpolationMode, optional):
Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate.
If not specified, the realization's initialization value will be assumed.
Returns: All information generated by the propagation.
"""
return self.__realization.propagate(
signal, self.__transmitter, self.__receiver, interpolation_mode
)
[docs]
def state(
self, delay: float, sampling_rate: float, num_samples: int, max_num_taps: int
) -> ChannelStateInformation:
"""Generate the discrete channel state information from this channel realization.
Denoted by
.. math::
\\mathbf{H}^{(m, \\tau)} \\in \\mathbb{C}^{N_{\\mathrm{Rx}} \\times N_{\\mathrm{Tx}}}
within the respective equations.
Args:
delay (float):
Delay in seconds at which the state information should be generated.
sampling_rate (float):
Sampling rate of the state information's discrete samples in :math:`\\mathrm{Hz}`.
num_samples (int):
Number of discrete time-domain samples of the chanel state information.
max_num_taps (int):
Maximum number of delay taps considered per discrete time-domain sample.
Returns: The channel state information representing this channel realization.
"""
return self.__realization.state(
self.__transmitter, self.__receiver, delay, sampling_rate, num_samples, max_num_taps
)
[docs]
class ChannelPropagation(Generic[CRT]):
"""Propagation over a channel realization.
Generated by :meth:`ChannelRealization.propagate` or :meth:`Channel.propagate`.
"""
__realization: CRT
__signal: Signal
__transmitter: Device
__receiver: Device
__interpolation_mode: InterpolationMode
def __init__(
self,
realization: CRT,
signal: Signal,
transmitter: Device,
receiver: Device,
interpolation_mode: InterpolationMode,
) -> None:
"""
Args:
realization (CRT):
Realization instance that generated this propagation.
signal (Signal):
Signale emerging after channel propagation.
transmitter (Device):
Device the signal was transmitted from.
receiver (Device):
Device the signal was received at.
interpolation_mode (InterpolationMode):
Interpolation mode used for channel propagation.
"""
# Initialize class attributes
self.__realization = realization
self.__signal = signal
self.__transmitter = transmitter
self.__receiver = receiver
self.__interpolation_mode = interpolation_mode
@property
def realization(self) -> DirectiveChannelRealization[CRT]:
"""The channel realization used for propagation."""
return DirectiveChannelRealization[CRT](self.transmitter, self.receiver, self.__realization)
@property
def signal(self) -> Signal:
"""The emerging signal after channel propagation."""
return self.__signal
@property
def transmitter(self) -> Device:
"""The device the signal was transmitted from."""
return self.__transmitter
@property
def receiver(self) -> Device:
"""The device the signal was received at."""
return self.__receiver
@property
def interpolation_mode(self) -> InterpolationMode:
"""The interpolation mode used for channel propagation."""
return self.__interpolation_mode
[docs]
def state(
self, delay: float, sampling_rate: float, num_samples: int, max_num_taps: int
) -> ChannelStateInformation:
"""Generate the discrete channel state information from this channel realization.
Resolves to :meth:`state()<ChannelRealization.state>` method of the :attr:`realization` attribute.
Denoted by
.. math::
\\mathbf{H}^{(m, \\tau)} \\in \\mathbb{C}^{N_{\\mathrm{Rx}} \\times N_{\\mathrm{Tx}}}
within the respective equations.
Args:
delay (float):
Delay in seconds at which the state information should be generated.
sampling_rate (float):
Sampling rate of the state information's discrete samples in :math:`\\mathrm{Hz}`.
num_samples (int):
Number of discrete time-domain samples of the chanel state information.
max_num_taps (int):
Maximum number of delay taps considered per discrete time-domain sample.
Returns: The channel state information representing this channel realization.
"""
# Query the realization's channel state
state = self.__realization.state(
self.transmitter, self.receiver, delay, sampling_rate, num_samples, max_num_taps
)
# Return the channel state
return state
[docs]
class Channel(ABC, RandomNode, Serializable, Generic[CRT]):
"""Abstract base class of all channel models.
The channel model represents the basic configuration of two linked :doc:`SimulatedDevices<simulation.simulated_device.SimulatedDevice>`
:meth:`alpha_device<.alpha_device>` and :meth:`beta_device<.beta_device>` exchanging electromagnetic :doc:`Signals<core.signal_model.Signal>`.
Each invokation of :meth:`.propagate` and :meth:`.realize` will generate a new :doc:`channel.channel.ChannelRealization` instance by internally calling :meth:`._realize`.
In the case of a :meth:`propagate` call the generated :doc:`channel.channel.ChannelRealization` will additionally be wrapped in a :doc:`channel.channel.ChannelPropagation`.
The channel model represents the matrix function of time :math:`t` and delay :math:`\\tau`
.. math::
\\mathbf{H}(t, \\tau; \\mathbf{\\zeta}) \\in \\mathbb{C}^{N_{\\mathrm{Rx}} \\times N_{\\mathrm{Tx}}} \\ \\text{,}
the dimensionality of which depends on the number of transmitting antennas :math:`N_{\\mathrm{Tx}}` and number of receiving antennas :math:`N_{\\mathrm{Rx}}`.
The vector :math:`\\mathbf{\\zeta}` represents the channel model's paramteres as random variables.
Realizing the channel model is synonymous with realizing and "fixing" these random parameters by drawing a sample from their respective
distributions, so that a :doc:`channel.channel.ChannelRealization` represents the deterministic function
.. math::
\\mathbf{H}(t, \\tau) \\in \\mathbb{C}^{N_{\\mathrm{Rx}} \\times N_{\\mathrm{Tx}}} \\ \\text{.}
"""
__alpha_device: SimulatedDevice | None
__beta_device: SimulatedDevice | None
__scenario: SimulationScenario
__gain: float
__interpolation_mode: InterpolationMode
__last_realization: CRT | None
def __init__(
self,
alpha_device: SimulatedDevice | None = None,
beta_device: SimulatedDevice | None = None,
gain: float = 1.0,
interpolation_mode: InterpolationMode = InterpolationMode.NEAREST,
devices: Tuple[SimulatedDevice, SimulatedDevice] | None = None,
seed: Optional[int] = None,
) -> None:
"""
Args:
alpha_device (SimulatedDevice, optional):
First device linked by this channel.
Initializes the :meth:`alpha_device<alpha_device>` property.
If not specified the channel is considered floating,
meaning a call to :meth:`realize` will raise an exception.
beta_device (SimulatedDevice, optional):
Second device linked by this channel.
Initializes the :meth:`beta_device<beta_device>` property.
If not specified the channel is considered floating,
meaning a call to :meth:`realize` will raise an exception.
gain (float, optional):
Linear channel power gain factor.
Initializes the :meth:`gain<gain>` property.
:math:`1.0` by default.
interpolation_mode (InterpolationMode, optional):
Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate.
:attr:`NEAREST<InterpolationMode.NEAREST>` by default, meaning no resampling is required.
devices (Tuple[SimulatedDevice, SimulatedDevice], optional):
Tuple of devices connected by this channel model.
seed (int, optional):
Seed used to initialize the pseudo-random number generator.
"""
# Initialize base classes
# Must be first in order for correct diamond resolve
Serializable.__init__(self)
RandomNode.__init__(self, seed=seed)
# Default parameters
self.__alpha_device = None
self.__beta_device = None
self.gain = gain
self.interpolation_mode = interpolation_mode
self.__scenario = None
self.__last_realization = None
if alpha_device is not None:
self.alpha_device = alpha_device
if beta_device is not None:
self.beta_device = beta_device
if devices is not None:
if self.alpha_device is not None or self.beta_device is not None:
raise ValueError(
"Can't use 'devices' initialization argument in combination with specifying a alpha / beta devices"
)
self.alpha_device = devices[0]
self.beta_device = devices[1]
@property
def alpha_device(self) -> SimulatedDevice | None:
"""First device linked by this channel.
Referred to as :math:`\\alpha` in the respective equations.
If not specified, i.e. :py:obj:`None`, the channel is considered floating,
meaning a call to :meth:`realize` will raise an exception.
"""
return self.__alpha_device
@alpha_device.setter
def alpha_device(self, value: SimulatedDevice) -> None:
self.__alpha_device = value
@property
def beta_device(self) -> SimulatedDevice | None:
"""Second device linked by this channel.
Referred to as :math:`\\beta` in the respective equations.
If not specified, i.e. :py:obj:`None`, the channel is considered floating,
meaning a call to :meth:`realize` will raise an exception.
"""
return self.__beta_device
@beta_device.setter
def beta_device(self, value: SimulatedDevice) -> None:
self.__beta_device = value
@property
def scenario(self) -> SimulationScenario | None:
"""Simulation scenario the channel belongs to.
Handle to the :class:`Scenario <hermespy.simulation.simulation.SimulationScenario>` this channel is asigned to.
:py:obj:`None` if the channel is not part of any specific :class:`Scenario <hermespy.simulation.simulation.SimulationScenario>`.
The recommended way to set the scenario is by calling the :meth:`set_channel<hermespy.simulation.simulation. SimulationScenario.set_channel>` method:
.. code-block:: python
from hermespy.simulation import SimulationScenario
scenario = SimulationScenario()
alpha_device = scenario.new_device()
beta_device = scenario.new_device()
channel = Channel()
scenario.set_channel(alpha_device, beta_device, channel)
"""
return self.__scenario
@scenario.setter
def scenario(self, value: SimulationScenario) -> None:
self.__scenario = value
self.random_mother = value
@property
def gain(self) -> float:
"""Linear channel power gain factor.
The default channel gain is 1.
Realistic physical channels should have a gain less than one.
For configuring logarithmic gains, set the attribute using the dB shorthand:
.. code-block:: python
from hermespy.core import dB
# Configure a 10 dB gain
channel.gain = dB(10)
Raises:
ValueError: For gains smaller than zero.
"""
return self.__gain
@gain.setter
def gain(self, value: float) -> None:
if value < 0.0:
raise ValueError("Channel gain must be greater or equal to zero")
self.__gain = value
@property
def interpolation_mode(self) -> InterpolationMode:
"""Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate."""
return self.__interpolation_mode
@interpolation_mode.setter
def interpolation_mode(self, value: InterpolationMode) -> None:
self.__interpolation_mode = value
[docs]
@abstractmethod
def _realize(self) -> CRT:
"""Generate a new channel realzation.
Abstract subroutine of :meth:`.realize`.
Each :class:`Channel` is required to implement their own
:meth:`._realize` method.
Returns: A new channel realization.
"""
... # pragma: no cover
[docs]
def realize(self, cache: bool = True) -> CRT:
"""Generate a new channel realization.
If `cache` is enabled, :attr:`.realization` will be updated
to the newly generated :class:`ChannelRealization<hermespy.channel.channel.ChannelRealization>`.
Args:
cache (bool, optional):
Cache the realization. Enabled by default.
Returns: A new channel realization.
"""
# Generate a new realization
realization = self._realize()
# Cache if the respective flag is enabled
if cache:
self.__last_realization = realization
return realization
@property
def realization(self) -> CRT | None:
"""The last realization used for channel propagation.
Updated every time :meth:`.propagate` or :meth:`.realize` are called and `cache` is enabled.
:py:obj:`None` if :meth:`.realize` has not been called yet.
"""
return self.__last_realization
[docs]
def propagate(
self,
signal: DeviceOutput | Signal,
transmitter: Device | None = None,
receiver: Device | None = None,
interpolation_mode: InterpolationMode = InterpolationMode.NEAREST,
cache: bool = True,
) -> ChannelPropagation[CRT]:
"""Propagate radio-frequency band signals over this channel.
Generates a new channel realization by calling :meth:`realize<.realize>` and propagates the provided signal over it.
Let
.. math::
\\mathbf{X} = \\left[ \\mathbf{x}^{(0)}, \\mathbf{x}^{(1)},\\, \\dots,\\, \\mathbf{x}^{(M_\\mathrm{Tx} - 1)} \\right] \\in \\mathbb{C}^{N_\\mathrm{Tx} \\times M_\\mathrm{Tx}}
be the `signal` transmitted by `transmitter` and
.. math::
\\mathbf{Y} = \\left[ \\mathbf{y}^{(0)}, \\mathbf{y}^{(1)},\\, \\dots,\\, \\mathbf{x}^{(M_\\mathrm{Rx} - 1)} \\right] \\in \\mathbb{C}^{N_\\mathrm{Rx} \\times M_\\mathrm{Rx}}
the reception of `receiver`, this method implements the channel propagation equation
.. math::
\\mathbf{y}^{(m)} = \\sum_{\\tau = 0}^{m} \\mathbf{H}^{(m, \\tau)} \mathbf{x}^{(m-\\tau)} \\ \\text{.}
Args:
signal (DeviceOutput | Signal):
Signal models emitted by `transmitter` associated with this wireless channel model.
transmitter (Device, optional):
Device transmitting the `signal` to be propagated over this realization.
If not specified :meth:`alpha_device<.alpha_device>` will be assumed.
receiver (Device, optional):
Device receiving the propagated `signal` after propagation.
If not specified :meth:`beta_device<.beta_device>` will be assumed.
interpolation_mode (InterpolationMode, optional):
Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate.
cache (bool, optional):
Should the generated realization be cached at this channel instance?
Enabled by default.
Returns: The channel propagation resulting from the signal propagation.
"""
# Infer parameters
_transmitter = self.alpha_device if transmitter is None else transmitter
_receiver = self.beta_device if receiver is None else receiver
# Generate a new realization
realization = self.realize(cache=cache)
# Propagate the provided signal
propagation = realization.propagate(signal, _transmitter, _receiver, interpolation_mode)
# Return resulting propagation
return propagation
[docs]
@abstractmethod
def recall_realization(self, group: Group) -> CRT:
"""Recall a realization of this channel type from its HDF serialization.
Args:
group (h5py.Group):
HDF group to which the channel realization was serialized.
Returns: The recalled realization instance.
"""
... # pragma: no cover