Source code for hermespy.channel.channel

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

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Callable, Generic, overload, Set, TypeVar, TYPE_CHECKING

import numpy as np
from h5py import Group

from hermespy.core import (
    AntennaArrayState,
    SignalBlock,
    DeviceOutput,
    RandomNode,
    SerializableEnum,
    Signal,
    SparseSignal,
    Transformation,
    ChannelStateInformation,
    Serializable,
)

if TYPE_CHECKING:
    from hermespy.simulation import (
        DeviceState,
        SimulatedDevice,
        SimulationScenario,
    )  # pragma: no cover

__author__ = "Andre Noll Barreto"
__copyright__ = "Copyright 2024, Barkhausen Institut gGmbH"
__credits__ = ["Andre Noll Barreto", "Tobias Kronauer", "Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.3.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. """
CST = TypeVar("CST", bound="ChannelSample") """Type of channel sample.""" CRT = TypeVar("CRT", bound="ChannelRealization") """Type of channel realization.""" CT = TypeVar("CT", bound="Channel") """Type of channel."""
[docs] class ChannelSampleHook(Generic[CST]): """Hook for a callback to be called after a specific channel sample is generated.""" __callback: Callable[[CST], None] __transmitter: SimulatedDevice | None __receiver: SimulatedDevice | None def __init__( self, callback: Callable[[CST], None], transmitter: SimulatedDevice | None, receiver: SimulatedDevice | None, ) -> None: """ Args: callback (Callable[[CST], None]): Function to be called after the channel is sampled. transmitter (SimulatedDevice, optional): Transmitter device the hook is associated with. receiver (SimulatedDevice, optional): Receiver device the hook is associated with. """ # Initialize class attributes self.__callback = callback self.__transmitter = transmitter self.__receiver = receiver def __call__( self, sample: CST, transmitter: SimulatedDevice, receiver: SimulatedDevice ) -> None: """Call the hook with the given sample.""" # Abort if the hook is associated with a specific device and the sample does not match if self.__transmitter is not None and transmitter is not self.__transmitter: return if self.__receiver is not None and receiver is not self.__receiver: return # Call the hook self.__callback(sample)
class LinkState(object): """Physical paramters of wireless channel link in time and space.""" __transmitter: DeviceState __receiver: DeviceState __carrier_frequency: float __bandwidth: float __time: float def __init__( self, transmitter: DeviceState, receiver: DeviceState, carrier_frequency: float, bandwidth: float, time: float, ) -> None: """ Args: transmitter (DeviceState): State of the transmitting device at the time of sampling. receiver (DeviceState): State of the receiving device at the time of sampling. carrier_frequency (float): Carrier frequency of the channel in Hz. bandwidth (float): Bandwidth of the propagated signal in Hz. time (float): Time of the channel state information in seconds. """ # Initialize class attributes self.__transmitter = transmitter self.__receiver = receiver self.__carrier_frequency = carrier_frequency self.__bandwidth = bandwidth self.__time = time @property def transmitter(self) -> DeviceState: """State of the transmitting device at the time of sampling.""" return self.__transmitter @property def receiver(self) -> DeviceState: """State of the receiving device at the time of sampling.""" return self.__receiver @property def carrier_frequency(self) -> float: """Carrier frequency of the channel in Hz.""" return self.__carrier_frequency @property def bandwidth(self) -> float: """Bandwidth of the propagated signal in Hz.""" return self.__bandwidth @property def time(self) -> float: """Time of the channel state information in seconds.""" return self.__time
[docs] class ChannelSample(object): """Immutable sample of a wireless channel model in time and space. Channel samples represent the channel state at a given point in time and two distinct observation points at transmitter and receiver in three-dimensional space. """ __state: LinkState def __init__(self, state: LinkState) -> None: """ Args: state (ChannelState): State of the channel at the time of sampling. """ # Initialize class attributes self.__state = state @property def transmitter_state(self) -> DeviceState: """State of the transmitting device at the time of sampling.""" return self.__state.transmitter @property def receiver_state(self) -> DeviceState: """State of the receiving device at the time of sampling.""" return self.__state.receiver @property def transmitter_pose(self) -> Transformation: """Global position and orientation of the transmitter.""" return self.__state.transmitter.pose @property def receiver_pose(self) -> Transformation: """Global position and orientation of the receiver.""" return self.__state.receiver.pose @property def transmitter_velocity(self) -> np.ndarray: """Velocity of the transmitter in m/s.""" return self.__state.transmitter.velocity @property def receiver_velocity(self) -> np.ndarray: """Velocity of the receiver in m/s.""" return self.__state.receiver.velocity @property def transmitter_antennas(self) -> AntennaArrayState: """Antenna array model of the transmitter.""" return self.__state.transmitter.antennas @property def receiver_antennas(self) -> AntennaArrayState: """Antenna array model of the receiver.""" return self.__state.receiver.antennas @property def num_transmit_antennas(self) -> int: """Number of antennas at the transmitter.""" return self.__state.transmitter.antennas.num_transmit_antennas @property def num_receive_antennas(self) -> int: """Number of antennas at the receiver.""" return self.__state.receiver.antennas.num_receive_antennas @property def carrier_frequency(self) -> float: """Carrier frequency of the channel in Hz.""" return self.__state.carrier_frequency @property def bandwidth(self) -> float: """Bandwidth of the propagated signal in Hz.""" return self.__state.bandwidth @property def time(self) -> float: """Time of the channel state information in seconds.""" return self.__state.time @property @abstractmethod def expected_energy_scale(self) -> float: """Expected linear scaling of a propagated signal's energy at each receiving antenna. Required to compute the expected energy of a signal after propagation, and therfore signal-to-noise ratios (SNRs) and signal-to-interference-plus-noise ratios (SINRs). """ ... # pragma: no cover @abstractmethod def _propagate(self, signal: SignalBlock, interpolation: InterpolationMode) -> SignalBlock: """Propagate radio-frequency band signals over a channel instance. Abstract subroutine of :meth:`propagate<ChannelSample.propagate>`. Args: signal (SignalBlock): The signal block to be propagated. 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: CST, signal: DeviceOutput | Signal, interpolation_mode: InterpolationMode = InterpolationMode.NEAREST, ) -> Signal: """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. 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, an integer rounding to the nearest sampling instance will be assumed. Returns: All information generated by the propagation. """ # 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 != self.num_transmit_antennas: raise ValueError( f"Number of signal streams to be propagated does not match the number of transmitter antennas ({_signal.num_streams} != {self.num_transmit_antennas}))" ) # Propagate each signal block propagated_signal = SparseSignal.Empty( self.bandwidth, self.num_receive_antennas, carrier_frequency=self.carrier_frequency ) for signal_block in _signal: propagated_signal._blocks.append(self._propagate(signal_block, interpolation_mode)) return propagated_signal
[docs] @abstractmethod def state( self, num_samples: int, max_num_taps: int, interpolation_mode: InterpolationMode = InterpolationMode.NEAREST, ) -> 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: 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. 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, an integer rounding to the nearest sampling instance will be assumed. Returns: The channel state information representing this channel realization. """ ... # pragma: no cover
[docs] class ChannelRealization(ABC, Generic[CST]): """Realization of a wireless channel channel model. Channel realizations represent a realization of all random processes of a wireless channel model. They are generated by the :meth:`realize()<Channel.realize>` method of :class:`.Channel` instances. """ __sample_hooks: Set[ChannelSampleHook[CST]] def __init__( self, sample_hooks: Set[ChannelSampleHook[CST]] | None = None, gain: float = 1.0 ) -> None: """ Args: sample_hooks (Set[ChannelSampleHook[CST]], optional): Hooks to be called after the channel is sampled. gain (float, optional): Linear power gain factor a signal experiences when being propagated over this realization. :math:`1.0` by default, meaning no gain or loss. """ # Initialize class attributes self.__sample_hooks = set() if sample_hooks is None else sample_hooks self.__gain = gain @property def sample_hooks(self) -> Set[ChannelSampleHook[CST]]: """Hooks to be called after the channel is sampled.""" return self.__sample_hooks.copy() @property def gain(self) -> float: """Linear power gain factor a signal experiences when being propagated over this realization.""" return self.__gain @overload def sample( self, transmitter: DeviceState, receiver: DeviceState, carrier_frequency: float | None = None, bandwidth: float | None = None, ) -> CST: """Sample the channel realization at a given point in time and space. Wrapper around :meth:`._sample` that converts the input arguments to the correct type. Args: transmitter (DeviceState): State of the transmitting device at the time of sampling. receiver (DeviceState): State of the receiving device at the time of sampling. carrier_frequency (float, optional): Carrier frequency of the channel in Hz. If not specified, the transmitting device's carrier frequency will be assumed. bandwidth (float, optional): Bandwidth of the propagated signal in Hz. If not specified, the transmitting device's sampling rate will be assumed. Returns: The channel sample for the given configuration. """ ... # pragma: no cover @overload def sample( self, transmitter: SimulatedDevice, receiver: SimulatedDevice, timestamp: float = 0.0, carrier_frequency: float | None = None, bandwidth: float | None = None, ) -> CST: """Sample the channel realization at a given point in time and space. Wrapper around :meth:`._sample` that converts the input arguments to the correct type. Args: transmitter (SimulatedDevice): Transmitting device feeding into the channel model to be sampled. receiver (SimulatedDevice): Receiving device observing the channel model to be sampled. timestamp (float, optional): Time at which the channel is sampled in seconds. carrier_frequency (float, optional): Carrier frequency of the channel in Hz. If not specified, the transmitting device's carrier frequency will be assumed. bandwidth (float, optional): Bandwidth of the propagated signal in Hz. If not specified, the transmitting device's sampling rate will be assumed. Returns: The channel sample at the given point in time. """ ... # pragma: no cover
[docs] def sample( self, transmitter: SimulatedDevice | DeviceState, receiver: SimulatedDevice | DeviceState, *args, **kwargs, ) -> CST: from hermespy.simulation import SimulatedDevice, DeviceState if isinstance(transmitter, SimulatedDevice) and isinstance(receiver, SimulatedDevice): timestamp = float(args[0]) if len(args) > 0 else 0.0 carrier_frequency = float(args[1]) if len(args) > 1 else None bandwidth = float(args[2]) if len(args) > 2 else None transmitter_device = transmitter receiver_device = receiver transmitter_state = transmitter.state(timestamp) receiver_state = receiver.state(timestamp) elif isinstance(transmitter, DeviceState) and isinstance(receiver, DeviceState): timestamp = 0.0 carrier_frequency = float(args[0]) if len(args) > 0 else None bandwidth = float(args[1]) if len(args) > 1 else None transmitter_device = transmitter.device receiver_device = receiver.device transmitter_state = transmitter receiver_state = receiver else: raise ValueError("Invalid input argument types for channel sampling.") _carrier_frequency = ( carrier_frequency if carrier_frequency is not None else transmitter_state.carrier_frequency ) _bandwidth = bandwidth if bandwidth is not None else transmitter_state.sampling_rate state = LinkState( transmitter_state, receiver_state, _carrier_frequency, _bandwidth, timestamp ) # Generate a new sample sample = self._sample(state) # Notify the registered hooks for hook in self.sample_hooks: hook(sample, transmitter_device, receiver_device) return sample
@abstractmethod def _sample(self, state: LinkState) -> CST: """Sample the channel realization at a given point in time and space. Abstract subroutine of :meth:`sample<hermespy.channel.channel.ChannelRealization.sample>`. Args: state (LinkState): State of the channel at the time of sampling. Returns: The channel sample at the given point in time. """ ... # pragma: no cover @overload def reciprocal_sample( self, sample: CST, transmitter: DeviceState, receiver: DeviceState, carrier_frequency: float | None = None, bandwidth: float | None = None, ) -> CST: ... # pragma: no cover @overload def reciprocal_sample( self, sample: CST, transmitter: SimulatedDevice, receiver: SimulatedDevice, timestamp: float = 0.0, carrier_frequency: float | None = None, bandwidth: float | None = None, ) -> CST: """Sample the reciprocal channel realization at a given point in time and space. Wrapper around :meth:`._reciprocal_sample` that converts the input arguments to the correct type. Args: sample (CST): Channel sample to be reciprocally sampled. transmitter (SimulatedDevice): Transmitting device in the reciprocal channel at the time of sampling. receiver (SimulatedDevice): Receiving device in the reciprocal channel at the time of sampling. timestamp (float, optional): Time at which the channel is sampled in seconds. Zero by default. carrier_frequency (float, optional): Carrier frequency of the channel in Hz. If not specified, the transmitting device's carrier frequency will be assumed. bandwidth (float, optional): Bandwidth of the propagated signal in Hz. If not specified, the transmitting device's sampling rate will be assumed. Returns: The channel sample at the given point in time. """ ... # pragma: no cover
[docs] def reciprocal_sample( self, sample: CST, transmitter: SimulatedDevice | DeviceState, receiver: SimulatedDevice | DeviceState, *args, **kwargs, ) -> CST: from hermespy.simulation import SimulatedDevice, DeviceState if isinstance(transmitter, SimulatedDevice) and isinstance(receiver, SimulatedDevice): timestamp = float(args[0]) if len(args) > 0 else 0.0 carrier_frequency = float(args[1]) if len(args) > 1 else None bandwidth = float(args[2]) if len(args) > 2 else None transmitter_device = transmitter receiver_device = receiver transmitter_state = transmitter.state(timestamp) receiver_state = receiver.state(timestamp) elif isinstance(transmitter, DeviceState) and isinstance(receiver, DeviceState): timestamp = 0.0 carrier_frequency = float(args[0]) if len(args) > 0 else None bandwidth = float(args[1]) if len(args) > 1 else None transmitter_device = transmitter.device receiver_device = receiver.device transmitter_state = transmitter receiver_state = receiver else: raise ValueError("Invalid input argument types for channel sampling.") _carrier_frequency = ( carrier_frequency if carrier_frequency is not None else transmitter_state.carrier_frequency ) _bandwidth = bandwidth if bandwidth is not None else transmitter_state.sampling_rate state = LinkState( transmitter_state, receiver_state, _carrier_frequency, _bandwidth, timestamp ) # Generate a new sample reciprocal_sample = self._reciprocal_sample(sample, state) # Notify the registered hooks for hook in self.sample_hooks: hook(reciprocal_sample, transmitter_device, receiver_device) return reciprocal_sample
@abstractmethod def _reciprocal_sample(self, sample: CST, state: LinkState) -> CST: """Sample the reciprocal channel realization at a given point in time and space. Abstract subroutine of :meth:`reciprocal_sample()<ChannelRealization.reciprocal_sample>`. Args: sample (CST): Channel sample to be reciprocally sampled. state (LinkState): Physical state of the channel at the time of sampling. Returns: The channel sample at the given point in time. """ ... # pragma: no cover
[docs] @abstractmethod def to_HDF(self, group: Group) -> None: """Serialize the channel realization to HDF5. Args: group (Group): HDF5 group to serialize the channel realization to. """ ... # pragma: no cover
[docs] class Channel(ABC, RandomNode, Serializable, Generic[CRT, CST]): """Abstract base class of all channel models. Channel models represent the basic physical properties of a elemtromagnetic waves propagating through space in between devices. """ __scenario: SimulationScenario __gain: float __sample_hooks: Set[ChannelSampleHook[CST]] def __init__(self, gain: float = 1.0, seed: int | None = None) -> None: """ Args: gain (float, optional): Linear channel energy gain factor. Initializes the :meth:`gain<gain>` property. :math:`1.0` by default. 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.gain = gain self.__scenario = None self.__sample_hooks = set() @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 sample_hooks(self) -> Set[ChannelSampleHook[CST]]: """Hooks to be called after a channel sample is generated.""" return self.__sample_hooks.copy()
[docs] def add_sample_hook( self, callback: Callable[[CST], None], transmitter: SimulatedDevice | None = None, receiver: SimulatedDevice | None = None, ) -> ChannelSampleHook[CST]: """Add a hook to be called after a channel sample is generated. Args: callback (Callable[[CST], None]): Function to be called after the channel is sampled. transmitter (SimulatedDevice, optional): Transmitter device the hook is associated with. If not specified the hook will be called for all transmitters. receiver (SimulatedDevice, optional): Receiver device the hook is associated with. If not specified the hook will be called for all receivers. """ hook = ChannelSampleHook(callback, transmitter, receiver) self.__sample_hooks.add(hook) return hook
[docs] def remove_sample_hook(self, hook: ChannelSampleHook[CST]) -> None: """Remove a hook from the list of hooks to be called after a channel sample is generated. Args: hook (ChannelSampleHook[CST], None]): Hook to be removed from the list of hooks. """ self.__sample_hooks.discard(hook)
@abstractmethod def _realize(self) -> CRT: """Generate a new channel realzation. Abstract subroutine of :meth:`realize<hermespy.channel.channel.Channel.realize>`. Each :class:`Channel<hermespy.channel.channel.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: SimulatedDevice, receiver: SimulatedDevice, timestamp: float = 0.0, interpolation_mode: InterpolationMode = InterpolationMode.NEAREST, ) -> Signal: """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 (SimulatedDevice): Device transmitting the `signal` to be propagated over this realization. receiver (SimulatedDevice): Device receiving the propagated `signal` after propagation. timestamp (float, optional): Time at which the signal is propagated in seconds. Defaults to 0.0. interpolation_mode (InterpolationMode, optional): Interpolation behaviour of the channel realization's delay components with respect to the proagated signal's sampling rate. Returns: The channel propagation resulting from the signal propagation. """ # Generate a new realization realization = self.realize() # Sample the channel realization sample: ChannelSample = realization.sample( transmitter, receiver, timestamp, signal.carrier_frequency, signal.sampling_rate ) # Propagate the provided signal propagation = sample.propagate(signal, 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