Source code for hermespy.channel.cdl.cdl

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

from __future__ import annotations
from typing import Mapping, Set

from h5py import Group
import numpy as np

from hermespy.core import HDFSerializable, SerializableEnum
from ..channel import Channel, ChannelRealization, ChannelSampleHook, LinkState
from ..consistent import ConsistentGenerator, ConsistentUniform, ConsistentRealization
from .cluster_delay_lines import ClusterDelayLineSample, ClusterDelayLineRealization

__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"


[docs] class CDLType(SerializableEnum): """Type of the static cluster delay line model.""" A = 0 B = 1 C = 2 D = 3 E = 4
# ETSI TR 138.901 v17.0.0 Table 7.7.1-1 CDL_Cluster_Parameters: Mapping[CDLType, np.ndarray] = { # Delay Power AOD AOA ZOD ZOA # [ns] [dB] [deg] [deg] [deg] [deg] # Table 7.7.1-1: CDL-A CDLType.A: np.array( [ [0.0000, -13.4, -178.10, +51.30, +050.2, 125.4], [0.3819, +0.00, -4.2000, -152.7, +093.2, 091.3], [0.4025, -2.20, -4.2000, -152.7, +093.2, 091.3], [0.5868, -4.00, -4.2000, -152.7, +093.2, 091.3], [0.4610, -6.00, +90.200, +76.60, +122.0, 094.0], [0.5375, -8.20, +90.200, +76.60, +122.0, 094.0], [0.6708, -9.90, +90.200, +76.60, +122.0, 094.0], [0.5750, -10.5, +121.50, -1.800, +150.2, 047.1], [0.7618, -7.50, -81.700, -41.90, +055.2, 056.0], [1.5375, -15.9, +158.40, +94.20, +026.4, 030.1], [1.8978, -6.60, -83.000, +51.90, +126.4, 058.8], [2.2242, -16.7, +134.80, -115.9, +171.6, 026.0], [2.1718, -12.4, -153.00, +26.60, +151.4, 049.2], [2.4942, -15.2, -172.00, +76.60, +157.2, 143.1], [2.5119, -10.8, -129.90, -07.00, +047.2, 117.4], [3.0582, -11.3, -136.00, -23.00, +040.4, 122.7], [4.0810, -12.7, +165.40, -47.20, +043.3, 123.2], [4.4579, -16.2, +148.40, +110.4, +161.8, 032.6], [4.5695, -18.3, +132.70, +144.5, +010.8, 027.2], [4.7966, -18.9, -118.60, +155.3, +016.7, 015.2], [5.0066, -16.6, -154.10, +102.0, +171.7, 146.0], [5.3043, -19.9, +126.50, -151.8, +022.7, 150.7], [9.6586, -29.7, -56.200, +55.20, +144.9, 156.1], ], dtype=np.float_, ), # Table 7.7.1-2: CDL-B CDLType.B: np.array( [ [0.0000, +0.00, +009.30, -173.3, +105.8, +78.9], [0.1072, -02.2, +009.30, -173.3, +105.8, +78.9], [0.2155, -04.0, +009.30, -173.3, +105.8, +78.9], [0.2095, -03.2, -034.10, +125.5, +115.3, +63.3], [0.2870, -09.8, -065.40, -088.0, +119.3, +59.9], [0.2986, -01.2, -011.40, +155.1, +103.2, +67.5], [0.3752, -03.4, -011.40, +155.1, +103.2, +67.5], [0.5055, -05.2, -011.40, +155.1, +103.2, +67.5], [0.3681, -07.6, -067.20, -089.8, +118.2, +82.6], [0.3697, -03.0, +052.50, +132.1, +102.0, +66.3], [0.5700, -08.9, -072.00, -083.6, +100.4, +61.6], [0.5283, -09.0, +074.30, +095.3, +098.3, +58.0], [1.1021, -4.80, -052.20, +103.7, +103.4, +78.2], [1.2756, -5.70, -050.50, -087.8, +102.5, +82.0], [1.5474, -7.50, +061.40, -092.5, +101.4, +62.4], [1.7842, -1.90, +030.60, -139.1, +103.0, +78.0], [2.0169, -7.60, -072.50, -090.6, +100.0, +60.9], [2.8294, -12.2, -090.60, +058.6, +115.2, +82.9], [3.0219, -9.80, -077.60, -079.0, +100.5, +60.8], [3.6187, -11.4, -082.60, +065.8, +119.6, +57.3], [4.1067, -14.9, -103.60, +052.7, +118.7, +59.9], [4.2790, -9.20, +075.60, +088.7, +117.8, +60.1], [4.7834, -11.3, -077.60, -060.4, +115.7, +62.3], ], dtype=np.float_, ), # Table 7.7.1-3: CDL-C CDLType.C: np.array( [ [0.0000, -04.4, -046.6, -101.0, +097.2, +087.6], [0.2099, -01.2, -022.8, +120.0, +098.6, +072.1], [0.2219, -03.5, -022.8, +120.0, +098.6, +072.1], [0.2329, -05.2, -022.8, +120.0, +098.6, +072.1], [0.2176, -02.5, -040.7, -127.5, +100.6, +070.1], [0.6366, +00.0, +000.3, +170.4, +099.2, +075.3], [0.6448, -02.2, +000.3, +170.4, +099.2, +075.3], [0.6560, -03.9, +000.3, +170.4, +099.2, +075.3], [0.6584, -07.4, +073.1, +055.4, +105.2, +067.4], [0.7935, -07.1, -064.5, +066.5, +095.3, +063.8], [0.8213, -10.7, +080.2, -048.1, +106.1, +071.4], [0.9336, -11.1, -097.1, +046.9, +093.5, +060.5], [1.2285, -05.1, -055.3, +068.1, +103.7, +090.6], [1.3083, -06.8, -064.3, -068.7, +104.2, +060.1], [2.1704, -08.7, -078.5, +081.5, +093.0, +061.0], [2.7105, -13.2, +102.7, +030.7, +104.2, +100.7], [4.2589, -13.9, +099.2, -016.4, +094.9, +062.3], [4.6003, -13.9, +088.8, +003.8, +093.1, +066.7], [5.4902, -15.8, -101.9, -013.7, +092.2, +052.9], [5.6077, -17.1, +092.2, +009.7, +106.7, +061.8], [6.3065, -16.0, +093.3, +005.6, +093.0, +051.9], [6.6374, -15.7, +106.6, +000.7, +092.9, +061.7], [7.0427, -21.6, +119.5, -021.9, +105.2, +058.0], [8.6523, -22.8, -123.8, +033.6, +107.8, +057.0], ], dtype=np.float_, ), # Table 7.7.1-4: CDL-D CDLType.D: np.array( [ [00.000, -00.2, +000.0, -180.0, +098.5, +81.5], [00.000, -13.5, +000.0, -180.0, +098.5, +81.5], [00.035, -18.8, +089.2, +089.2, +085.5, +86.9], [00.612, -21.0, +089.2, +089.2, +085.5, +86.9], [01.363, -22.8, +089.2, +089.2, +085.5, +86.9], [01.405, -17.9, +013.0, +163.0, +097.5, +79.4], [01.804, -20.1, +013.0, +163.0, +097.5, +79.4], [02.596, -21.9, +013.0, +163.0, +097.5, +79.4], [01.775, -22.9, +034.6, -137.0, +098.5, +78.2], [04.042, -27.8, -064.5, +074.5, +088.4, +73.6], [07.937, -23.6, -032.9, +127.7, +091.3, +78.3], [09.424, -24.8, +052.6, -119.6, +103.8, +87.0], [09.708, -30.0, -132.1, -009.1, +080.3, +70.6], [12.525, -27.7, +077.2, -083.8, +086.5, +72.9], ], dtype=np.float_, ), # Table 7.7.1-5: CDL-E CDLType.E: np.array( [ [00.0000, -00.03, +00.0, -180.0, +099.6, +80.4], [00.0000, -22.03, +00.0, -180.0, +099.6, +80.4], [00.5133, -15.80, +57.5, +018.2, +104.2, +80.4], [00.5440, -18.10, +57.5, +018.2, +104.2, +80.4], [00.5630, -19.80, +57.5, +018.2, +104.2, +80.4], [00.5440, -22.90, -20.1, +101.8, +099.4, +80.8], [00.7112, -22.40, +16.2, +112.9, +100.8, +86.3], [1.90920, -18.60, +09.3, -155.5, +098.8, +82.7], [1.92930, -20.80, +09.3, -155.5, +098.8, +82.7], [1.95890, -22.60, +09.3, -155.5, +098.8, +82.7], [2.64260, -22.30, +19.0, -143.3, +100.8, +82.9], [3.71360, -25.60, +32.7, -094.7, +096.4, +88.0], [5.45240, -20.20, +00.5, +147.0, +098.9, +81.0], [12.0034, -29.80, +55.9, -036.2, +095.6, +88.6], [20.6419, -29.20, +57.6, -026.0, +104.6, +78.3], ], dtype=np.float_, ), } CDL_Per_Cluster_Parameters: np.ndarray = np.array( [ [05.0, 11 - 0, 3.0, 3.0, 10.0, False], # Table 7.7.1-1: CDL-A [10.0, 22.0, 3.0, 7.0, 08.0, False], # Table 7.7.1-2: CDL-B [02.0, 15.0, 3.0, 7.0, 07.0, False], # Table 7.7.1-2: CDL-C [05.0, 08.0, 3.0, 3.0, 11.0, True], # Table 7.7.1-4: CDL-D [05.0, 11.0, 3.0, 7.0, 08.0, True], # Table 7.7.1-5: CDL-E ] )
[docs] class CDLRealization(ChannelRealization[ClusterDelayLineSample]): """Realization of a static cluster delay line model for link-level simulations. Generated by the :meth:`realize<CDL.realize>` method of the :class:`CDL` class. """ def __init__( self, type: CDLType, rms_delay: float, rayleigh_factor: float, angle_coupling_indices: np.ndarray, consistent_realization: ConsistentRealization, xpr_phase: ConsistentUniform, sample_hooks: Set[ChannelSampleHook[ClusterDelayLineSample]], gain: float, ) -> None: """ Args: type: Type of the cluster delay line model. rms_delay: Root mean square delay spread of the channel. rayleigh_factor: Rayleigh K-factor of the channel. angle_coupling_indices: Indices for the coupling of rays within a cluster. consistent_realization: Realization of the consistent distribution. xpr_phase: Realization of the cross-polarization phase. sample_hooks: Hooks to be called after the channel sample has been generated. gain: Linear channel gain factor. """ # Initialize base class ChannelRealization.__init__(self, sample_hooks, gain) # Store parameters self.__type = type self.__rms_delay = rms_delay self.__rayleigh_factor = rayleigh_factor self.__angle_coupling_indices = angle_coupling_indices self.__consistent_realization = consistent_realization self.__xpr_phase = xpr_phase
[docs] def _sample(self, state: LinkState) -> ClusterDelayLineSample: # Sample the consistent distribution consistent_sample = self.__consistent_realization.sample( state.transmitter.position, state.receiver.position ) # Fetch the cluster parameters parameters = CDL_Cluster_Parameters[self.__type] per_cluster_parameters = CDL_Per_Cluster_Parameters[self.__type.value, :] normalized_cluster_delays = parameters[:, 0] cluster_powers = 10 ** (parameters[:, 1] / 10) cluster_aods = parameters[:, 2] cluster_aoas = parameters[:, 3] cluster_zods = parameters[:, 4] cluster_zoas = parameters[:, 5] rms_asd_spreads = per_cluster_parameters[0] rms_asa_spreads = per_cluster_parameters[1] rms_zsd_spreads = per_cluster_parameters[2] rms_zsa_spreads = per_cluster_parameters[3] XPR_dB = per_cluster_parameters[4] line_of_sight = bool(per_cluster_parameters[5]) # Generate cluster delays cluster_delays = self.__rms_delay * normalized_cluster_delays # Step 1: Generate departure and arrival angles # Equation 7.7-0a in ETSI TR 138.901 v17.0.0 ray_aods = np.add.outer( cluster_aods, rms_asd_spreads * ClusterDelayLineRealization._ray_offset_angles ) ray_aoas = np.add.outer( cluster_aoas, rms_asa_spreads * ClusterDelayLineRealization._ray_offset_angles ) ray_zods = np.add.outer( cluster_zods, rms_zsd_spreads * ClusterDelayLineRealization._ray_offset_angles ) ray_zoas = np.add.outer( cluster_zoas, rms_zsa_spreads * ClusterDelayLineRealization._ray_offset_angles ) # Step 2: Coupling of rays within a cluster for both azimuth and zenith # Equation 7.7-0b in ETSI TR 138.901 v17.0.0 shuffled_ray_aods = np.take_along_axis( ray_aods, self.__angle_coupling_indices[0, :], axis=1 ) shuffled_ray_aoas = np.take_along_axis( ray_aoas, self.__angle_coupling_indices[1, :], axis=1 ) shuffled_ray_zods = np.take_along_axis( ray_zods, self.__angle_coupling_indices[2, :], axis=1 ) shuffled_ray_zoas = np.take_along_axis( ray_zoas, self.__angle_coupling_indices[3, :], axis=1 ) # Draw initial random phases (step 10) # A single 2x2 slice represents the jones matrix transforming the polarization of a single ray cross_polarization_factor = 10 ** (XPR_dB / 10) polarization_transformations = np.exp( 2j * np.pi * self.__xpr_phase.sample(consistent_sample) ) polarization_transformations[0, 1, ::] *= cross_polarization_factor polarization_transformations[1, 0, ::] *= cross_polarization_factor return ClusterDelayLineSample( line_of_sight, self.__rayleigh_factor, np.pi / 180 * shuffled_ray_aoas, np.pi / 180 * shuffled_ray_zoas, np.pi / 180 * shuffled_ray_aods, np.pi / 180 * shuffled_ray_zods, 0, cluster_delays, self.__rms_delay, cluster_powers, polarization_transformations, state, )
def _reciprocal_sample( self, sample: ClusterDelayLineSample, state: LinkState ) -> ClusterDelayLineSample: return sample.reciprocal(state)
[docs] def to_HDF(self, group: Group) -> None: group.attrs["type"] = self.__type.value group.attrs["rms_delay"] = self.__rms_delay group.attrs["rayleigh_factor"] = self.__rayleigh_factor HDFSerializable._write_dataset( group, "angle_coupling_indices", self.__angle_coupling_indices ) self.__consistent_realization.to_HDF( HDFSerializable._create_group(group, "consistent_realization") ) group.attrs["gain"] = self.gain
[docs] @staticmethod def From_HDF( group: Group, xpr_phase: ConsistentUniform, sample_hooks: Set[ChannelSampleHook[ClusterDelayLineSample]], ) -> CDLRealization: type = CDLType(group.attrs["type"]) rms_delay = group.attrs["rms_delay"] rayleigh_factor = group.attrs["rayleigh_factor"] angle_coupling_indices = np.array(group["angle_coupling_indices"], dtype=np.int_) consistent_realization = ConsistentRealization.from_HDF(group["consistent_realization"]) gain = group.attrs["gain"] return CDLRealization( type, rms_delay, rayleigh_factor, angle_coupling_indices, consistent_realization, xpr_phase, sample_hooks, gain, )
[docs] class CDL(Channel[CDLRealization, ClusterDelayLineSample]): """Static cluster delay line model for link-level simulations.""" yaml_tag = "CDL" __model_type: CDLType __rms_delay: float __rayleigh_factor: float __decorrelation_distance: float def __init__( self, model_type: CDLType, rms_delay: float, rayleigh_factor: float = 0.0, decorrelation_distance: float = 30.0, **kwargs, ) -> None: """ Args: model_type: Type of the cluster delay line model. rms_delay: Root mean square delay spread of the channel. rayleigh_factor: Rayleigh K-factor of the channel. decorrelation_distance: Decorrelation distance of the channel. \**kwargs: Additional parameters for the base class. """ # Initialize base class Channel.__init__(self, **kwargs) # Store parameters self.__model_type = model_type self.rms_delay = rms_delay self.rayleigh_factor = rayleigh_factor self.decorrelation_distance = decorrelation_distance self.__consistent_generator = ConsistentGenerator(self) self.__xpr_phase = ConsistentUniform( self.__consistent_generator, ( 2, 2, CDL_Cluster_Parameters[model_type].shape[0], ClusterDelayLineRealization._ray_offset_angles.size, ), ) @property def model_type(self) -> CDLType: """Type of the cluster delay line model.""" return self.__model_type @property def rms_delay(self) -> float: """Root mean square delay spread of the channel. Raises: ValuError: If the delay spread is negative. """ return self.__rms_delay @rms_delay.setter def rms_delay(self, value: float) -> None: if value < 0: raise ValueError("The delay spread must be non-negative.") self.__rms_delay = value @property def rayleigh_factor(self) -> float: """Rayleigh K-factor of the channel. Raises: ValueError: If the K-factor is negative. """ return self.__rayleigh_factor @rayleigh_factor.setter def rayleigh_factor(self, value: float) -> None: if value < 0: raise ValueError("The K-factor must be non-negative.") self.__rayleigh_factor = value @property def decorrelation_distance(self) -> float: """Decorrelation distance of the channel. Raises: ValueError: If the decorrelation distance is negative. """ return self.__decorrelation_distance @decorrelation_distance.setter def decorrelation_distance(self, value: float) -> None: if value < 0: raise ValueError("The decorrelation distance must be non-negative.") self.__decorrelation_distance = value
[docs] def _realize(self) -> CDLRealization: angle_candidate_indices = np.arange(ClusterDelayLineRealization._ray_offset_angles.size) angle_coupling_indices = np.array( [ [ self._rng.permutation(angle_candidate_indices) for _ in range(CDL_Cluster_Parameters[self.model_type].shape[0]) ] for _ in range(4) ] ) return CDLRealization( self.model_type, self.rms_delay, self.rayleigh_factor, angle_coupling_indices, self.__consistent_generator.realize(self.decorrelation_distance), self.__xpr_phase, self.sample_hooks, self.gain, )
[docs] def recall_realization(self, group: Group) -> CDLRealization: return CDLRealization.From_HDF(group, self.__xpr_phase, self.sample_hooks)