예제 #1
0
    def test_num_sinusoids_validation(self) -> None:
        """Number of sinusoids property setter should raise ValueError on invalid arguments."""

        channel = MultipathFadingChannel(**self.channel_params)

        with self.assertRaises(ValueError):
            channel.num_sinusoids = -1
예제 #2
0
    def test_num_sinusoids_setget(self) -> None:
        """Number of sinusoids property getter should return setter argument."""

        channel = MultipathFadingChannel(**self.channel_params)

        num_sinusoids = 100
        channel.num_sinusoids = num_sinusoids

        self.assertEqual(num_sinusoids, channel.num_sinusoids)
예제 #3
0
    def test_doppler_frequency_setget(self) -> None:
        """Doppler frequency property getter should return setter argument."""

        channel = MultipathFadingChannel(**self.channel_params)

        doppler_frequency = 5
        channel.doppler_frequency = doppler_frequency

        self.assertEqual(doppler_frequency, channel.doppler_frequency)
예제 #4
0
    def __init__(self,
                 tap_interval: float = 0.0,
                 rms_delay: float = 0.0,
                 **kwargs: Any) -> None:
        """Exponential Multipath Channel Model initialization.

        Args:

            tap_interval (float, optional):
                Tap interval in seconds.

            rms_delay (float, optional):
                Root-Mean-Squared delay in seconds.

            kwargs (Any):
                `MultipathFadingChannel` initialization parameters.

        Raises:
            ValueError: On invalid arguments.
        """

        if tap_interval <= 0.0:
            raise ValueError("Tap interval must be greater than zero")

        if rms_delay <= 0.0:
            raise ValueError(
                "Root-Mean-Squared delay must be greater than zero")

        self.__tap_interval = tap_interval
        self.__rms_delay = rms_delay

        rms_norm = rms_delay / tap_interval

        # Calculate the decay exponent alpha based on an infinite power delay profile, in which case
        # rms_delay = exp(-alpha/2)/(1-exp(-alpha)), cf. geometric distribution.
        # Truncate the distributions for paths whose average power is very
        # small (less than exponential_truncation).

        alpha = -2 * np.log(
            (-1 + np.sqrt(1 + 4 * rms_norm**2)) / (2 * rms_norm))
        max_delay_in_samples = int(
            np.ceil(
                np.log(MultipathFadingExponential.__exponential_truncation) /
                alpha))

        delays = np.arange(max_delay_in_samples + 1) * tap_interval
        power_profile = np.exp(-alpha * np.arange(max_delay_in_samples + 1))
        rice_factors = np.zeros(delays.shape)

        # Init base class with pre-defined model parameters
        MultipathFadingChannel.__init__(self,
                                        delays=delays,
                                        power_profile=power_profile,
                                        rice_factors=rice_factors,
                                        **kwargs)
예제 #5
0
    def test_los_angle_setget(self) -> None:
        """Line of sight angle property getter should return setter argument."""

        channel = MultipathFadingChannel(**self.channel_params)

        los_angle = 15
        channel.los_angle = los_angle
        self.assertEqual(los_angle, channel.los_angle)

        channel.los_angle = None
        self.assertEqual(None, channel.los_angle)
예제 #6
0
    def test_los_doppler_frequency_setget(self) -> None:
        """Line-of-Sight Doppler frequency property getter should return setter argument,
        alternatively the global Doppler."""

        channel = MultipathFadingChannel(**self.channel_params)

        los_doppler_frequency = 5
        channel.los_doppler_frequency = los_doppler_frequency
        self.assertEqual(los_doppler_frequency, channel.los_doppler_frequency)

        doppler_frequency = 4
        channel.doppler_frequency = doppler_frequency
        channel.los_doppler_frequency = None
        self.assertEqual(doppler_frequency, channel.los_doppler_frequency)
예제 #7
0
    def test_multipath_fading(self) -> None:
        self.channel_params['delays'] = np.zeros(1, dtype=float)
        self.channel_params['power_profile'] = np.ones(1, dtype=float)
        self.channel_params['rice_factors'] = np.zeros(1, dtype=float)

        LOW, HIGH = self.add_sync_offsets_to_params()
        ch = MultipathFadingChannel(**self.channel_params)
        self.serialized_channel_contains_sync_offsets(ch, LOW, HIGH)
예제 #8
0
    def test_rayleigh(self) -> None:
        """
        Test if the amplitude of a path is Rayleigh distributed.
        Verify that both real and imaginary components are zero-mean normal random variables with the right variance and
        uncorrelated.
        """
        max_number_of_drops = 200
        samples_per_drop = 1000
        self.doppler_frequency = 200

        self.channel_params['delays'][0] = 0.
        self.channel_params['power_profile'][0] = 1.
        self.channel_params['rice_factors'][0] = 0.
        self.channel_params['doppler_frequency'] = self.doppler_frequency

        channel = MultipathFadingChannel(**self.channel_params)

        samples = np.array([])

        is_rayleigh = False
        alpha = .05
        max_corr = 0.05

        number_of_drops = 0
        while not is_rayleigh and number_of_drops < max_number_of_drops:

            channel_gains = channel.impulse_response(samples_per_drop,
                                                     self.doppler_frequency)
            samples = np.append(samples, channel_gains.ravel())

            _, p_real = stats.kstest(np.real(samples),
                                     'norm',
                                     args=(0, 1 / np.sqrt(2)))
            _, p_imag = stats.kstest(np.imag(samples),
                                     'norm',
                                     args=(0, 1 / np.sqrt(2)))

            corr = np.corrcoef(np.real(samples), np.imag(samples))
            corr = corr[0, 1]

            if p_real > alpha and p_imag > alpha and abs(corr) < max_corr:
                is_rayleigh = True

            number_of_drops += 1

        self.assertTrue(is_rayleigh)
예제 #9
0
    def test_max_delay_get(self) -> None:
        """Max delay property should return maximum of delays."""

        self.channel_params['delays'] = np.array([1, 2, 3])
        self.channel_params['power_profile'] = np.zeros(3)
        self.channel_params['rice_factors'] = np.ones(3)

        channel = MultipathFadingChannel(**self.channel_params)
        self.assertEqual(max(self.channel_params['delays']), channel.max_delay)
예제 #10
0
    def test_num_sequences_get(self) -> None:
        """Number of fading sequences property should return core parameter lengths."""

        self.channel_params['delays'] = np.array([1, 2, 3])
        self.channel_params['power_profile'] = np.zeros(3)
        self.channel_params['rice_factors'] = np.ones(3)

        channel = MultipathFadingChannel(**self.channel_params)
        self.assertEqual(len(self.channel_params['delays']),
                         channel.num_resolvable_paths)
예제 #11
0
    def test_propagation_siso_no_fading(self) -> None:
        """
        Test the propagation through a SISO multipath channel model without fading
        Check if the output sizes are consistent
        Check the output of a SISO multipath channel model without fading (K factor of Rice distribution = inf)
        """

        self.rice_factors[0] = float('inf')
        self.delays[0] = 10 / self.sampling_rate
        channel = MultipathFadingChannel(**self.channel_params)

        timestamps = np.arange(self.num_samples) / self.sampling_rate
        transmission = exp(1j * timestamps * self.transmit_frequency).reshape(
            1, self.num_samples)
        output, _, _ = channel.propagate(
            Signal(transmission, self.sampling_rate))

        self.assertEqual(10, output[0].num_samples - transmission.shape[1],
                         "Propagation impulse response has unexpected length")
예제 #12
0
    def test_init_validation(self) -> None:
        """Object initialization should raise ValueError on invalid arguments."""

        with self.assertRaises(ValueError):
            params = deepcopy(self.channel_params)
            params['delays'] = np.array([1, 2])
            _ = MultipathFadingChannel(**params)

        with self.assertRaises(ValueError):
            params = deepcopy(self.channel_params)
            params['power_profile'] = np.array([1, 2])
            _ = MultipathFadingChannel(**params)

        with self.assertRaises(ValueError):
            params = deepcopy(self.channel_params)
            params['rice_factors'] = np.array([1, 2])
            _ = MultipathFadingChannel(**params)

        with self.assertRaises(ValueError):
            params = deepcopy(self.channel_params)
            params['delays'] = np.array([-1.0])
            _ = MultipathFadingChannel(**params)

        with self.assertRaises(ValueError):
            params = deepcopy(self.channel_params)
            params['power_profile'] = np.array([-1.0])
            _ = MultipathFadingChannel(**params)

        with self.assertRaises(ValueError):
            params = deepcopy(self.channel_params)
            params['rice_factors'] = np.array([-1.0])
            _ = MultipathFadingChannel(**params)
예제 #13
0
    def test_power_delay_profile(self) -> None:
        """
        Test if the resulting power delay profile matches with the one specified in the parameters.
        Test also an interpolated channel (should have the same rms delay spread)
        """
        max_number_of_drops = 100
        samples_per_drop = 1000
        max_delay_spread_dev = 12 / self.sampling_rate  # Check what is acceptable here

        self.doppler_frequency = 50
        self.channel_params['doppler_frequency'] = self.doppler_frequency
        self.channel_params['delays'] = np.zeros(5)
        self.channel_params['power_profile'] = np.ones(5)
        self.channel_params['rice_factors'] = np.zeros(5)
        self.channel_params['delays'] = np.array([0, 3, 6, 7, 8
                                                  ]) / self.sampling_rate

        mean_delay = np.mean(self.channel_params['delays'])
        config_delay_spread = np.mean(
            (self.channel_params['delays'] - mean_delay)**2)

        delayed_channel = MultipathFadingChannel(**self.channel_params)

        for s in range(max_number_of_drops):

            delayed_channel.random_generator = np.random.default_rng(s + 10)
            delayed_response = delayed_channel.impulse_response(
                samples_per_drop, self.sampling_rate)

            delayed_time = np.arange(
                delayed_response.shape[-1]) / self.sampling_rate
            delay_diff = (delayed_time - np.mean(delayed_time))**2
            delayed_power = delayed_response.real**2 + delayed_response.imag**2
            delay_spread = np.sqrt(
                np.mean(delayed_power @ delay_diff) / np.mean(delayed_power))

            spread_delta = abs(config_delay_spread - delay_spread)
            self.assertTrue(
                spread_delta < max_delay_spread_dev,
                msg=f"{spread_delta} larger than max {max_delay_spread_dev}")
예제 #14
0
    def test_impulse_response_seed(self) -> None:
        """Re-setting the random rng seed should result in identical impulse responses."""

        channel = MultipathFadingChannel(**self.channel_params)

        channel.set_seed(100)
        first_draw = channel.impulse_response(self.num_samples,
                                              self.sampling_rate)

        channel.set_seed(100)
        second_draw = channel.impulse_response(self.num_samples,
                                               self.sampling_rate)

        assert_array_almost_equal(first_draw, second_draw)
예제 #15
0
    def test_propagation_fading(self) -> None:
        """
        Test the propagation through a SISO multipath channel with fading.
        """

        test_delays = np.array([1., 2., 3., 4.],
                               dtype=float) / self.sampling_rate

        reference_params = self.channel_params.copy()
        delayed_params = self.channel_params.copy()

        reference_params['delays'] = np.array([0.0])
        reference_channel = MultipathFadingChannel(**reference_params)

        timestamps = np.arange(self.num_samples) / self.sampling_rate
        transmit_samples = np.exp(2j * pi * timestamps *
                                  self.transmit_frequency).reshape(
                                      (1, self.num_samples))
        transmit_signal = Signal(transmit_samples, self.sampling_rate)

        for d, delay in enumerate(test_delays):

            delayed_params['delays'] = reference_params['delays'] + delay
            delayed_channel = MultipathFadingChannel(**delayed_params)

            reference_channel.set_seed(d)
            reference_propagation, _, _ = reference_channel.propagate(
                transmit_signal)

            delayed_channel.set_seed(d)
            delayed_propagation, _, _ = delayed_channel.propagate(
                transmit_signal)

            zero_pads = int(self.sampling_rate * float(delay))
            npt.assert_array_almost_equal(
                reference_propagation[0].samples,
                delayed_propagation[0].samples[:, zero_pads:])
예제 #16
0
    def test_rice(self) -> None:
        """
        Test if the amplitude of a path is Ricean distributed.
        """
        max_number_of_drops = 100
        doppler_frequency = 200
        samples_per_drop = 1000

        self.channel_params['delays'][0] = 0.
        self.channel_params['power_profile'][0] = 1.
        self.channel_params['rice_factors'][0] = 1.
        self.channel_params['doppler_frequency'] = doppler_frequency

        channel = MultipathFadingChannel(**self.channel_params)
        samples = np.array([])

        is_rice = False
        alpha = .05

        number_of_drops = 0
        while not is_rice and number_of_drops < max_number_of_drops:

            channel_gains = channel.impulse_response(samples_per_drop,
                                                     doppler_frequency)
            samples = np.append(samples, channel_gains.ravel())

            dummy, p_real = stats.kstest(np.abs(samples),
                                         'rice',
                                         args=(np.sqrt(2), 0, 1 / 2))

            if p_real > alpha:
                is_rice = True

            number_of_drops += 1

        self.assertTrue(is_rice)
예제 #17
0
    def test_init(self) -> None:
        """The object initialization should properly store all parameters."""

        channel = MultipathFadingChannel(**self.channel_params)

        self.assertIs(self.transmitter, channel.transmitter,
                      "Unexpected transmitter parameter initialization")
        self.assertIs(self.receiver, channel.receiver,
                      "Unexpected receiver parameter initialization")
        self.assertEqual(self.active, channel.active,
                         "Unexpected active parameter initialization")
        self.assertEqual(self.gain, channel.gain,
                         "Unexpected gain parameter initialization")
        self.assertEqual(self.num_sinusoids, channel.num_sinusoids)
        self.assertEqual(self.doppler_frequency, channel.doppler_frequency)
        self.assertEqual(self.sync_offset_low, channel.sync_offset_low)
        self.assertEqual(self.sync_offset_high, channel.sync_offset_high)
예제 #18
0
    def test_delays_get(self) -> None:
        """Delays getter should return init param."""

        channel = MultipathFadingChannel(**self.channel_params)
        np.testing.assert_array_almost_equal(self.delays, channel.delays)
예제 #19
0
    def __init__(self,
                 model_type: TYPE = TYPE.URBAN,
                 los_angle: Optional[float] = None,
                 doppler_frequency: Optional[float] = None,
                 los_doppler_frequency: Optional[float] = None,
                 **kwargs: Any) -> None:
        """Model initialization.

        Args:

            model_type (TYPE):
                The model type..

            los_angle (float, optional):
                Angle phase of the line of sight component within the statistical distribution.

            doppler_frequency (float, optional):
                Doppler frequency shift of the statistical distribution.

            kwargs (Any):
                `MultipathFadingChannel` initialization parameters.

        Raises:
           ValueError:
                If `model_type` is not supported.
                If `los_angle` is defined in HILLY model type.
        """

        if model_type == self.TYPE.URBAN:

            delays = 1e-6 * np.array([
                0, .217, .512, .514, .517, .674, .882, 1.230, 1.287, 1.311,
                1.349, 1.533, 1.535, 1.622, 1.818, 1.836, 1.884, 1.943, 2.048,
                2.140
            ])
            power_db = np.array([
                -5.7, -7.6, -10.1, -10.2, -10.2, -11.5, -13.4, -16.3, -16.9,
                -17.1, -17.4, -19.0, -19.0, -19.8, -21.5, -21.6, -22.1, -22.6,
                -23.5, -24.3
            ])
            rice_factors = np.zeros(delays.shape)

        elif model_type == self.TYPE.RURAL:

            delays = 1e-6 * np.array(
                [0, .042, .101, .129, .149, .245, .312, .410, .469, .528])
            power_db = np.array([
                -5.2, -6.4, -8.4, -9.3, -10.0, -13.1, -15.3, -18.5, -20.4,
                -22.4
            ])
            rice_factors = np.zeros(delays.shape)

        elif model_type == self.TYPE.HILLY:

            if los_angle is not None:
                raise ValueError(
                    "Model type HILLY does not support line of sight angle configuration"
                )

            delays = 1e-6 * np.array([
                0, .356, .441, .528, .546, .609, .625, .842, .916, .941, 15.0,
                16.172, 16.492, 16.876, 16.882, 16.978, 17.615, 17.827, 17.849,
                18.016
            ])
            power_db = np.array([
                -3.6, -8.9, -10.2, -11.5, -11.8, -12.7, -13.0, -16.2, -17.3,
                -17.7, -17.6, -22.7, -24.1, -25.8, -25.8, -26.2, -29.0, -29.9,
                -30.0, -30.7
            ])
            rice_factors = np.hstack(
                [np.array([np.inf]),
                 np.zeros(delays.size - 1)])
            los_angle = np.arccos(.7)

        else:
            raise ValueError("Requested model type not supported")

        self.__model_type = model_type

        # Convert power and normalize
        power_profile = 10**(power_db / 10)
        power_profile /= sum(power_profile)

        # Init base class with pre-defined model parameters
        MultipathFadingChannel.__init__(
            self,
            delays=delays,
            power_profile=power_profile,
            rice_factors=rice_factors,
            los_angle=los_angle,
            doppler_frequency=doppler_frequency,
            los_doppler_frequency=los_doppler_frequency,
            **kwargs)
예제 #20
0
    def __init__(self,
                 model_type: TYPE = 0,
                 rms_delay: float = 0.0,
                 doppler_frequency: Optional[float] = None,
                 los_doppler_frequency: Optional[float] = None,
                 **kwargs: Any) -> None:
        """Model initialization.

        Args:

            model_type (TYPE):
                The model type.

            rms_delay (float):
                Root-Mean-Squared delay in seconds.

            num_sinusoids (int, optional):
                Number of sinusoids used to sample the statistical distribution.

            doppler_frequency (float, optional)
                Doppler frequency shift of the statistical distribution.

            kwargs (Any):
                `MultipathFadingChannel` initialization parameters.

        Raises:
            ValueError:
                If `model_type` is not supported.
                If `rms_delay` is smaller than zero.
                If `los_angle` is specified in combination with `model_type` D or E.
        """

        if rms_delay < 0.0:
            raise ValueError(
                "Root-Mean-Squared delay must be greater or equal to zero")

        self.__rms_delay = rms_delay

        if model_type == self.TYPE.A:

            normalized_delays = np.array([
                0, 0.3819, 0.4025, 0.5868, 0.4610, 0.5375, 0.6708, 0.5750,
                0.7618, 1.5375, 1.8978, 2.2242, 2.1717, 2.4942, 2.5119, 3.0582,
                4.0810, 4.4579, 4.5695, 4.7966, 5.0066, 5.3043, 9.6586
            ])
            power_db = np.array([
                -13.4, 0, -2.2, -4, -6, -8.2, -9.9, -10.5, -7.5, -15.9, -6.6,
                -16.7, -12.4, -15.2, -10.8, -11.3, -12.7, -16.2, -18.3, -18.9,
                -16.6, -19.9, -29.7
            ])
            rice_factors = np.zeros(normalized_delays.shape)

        elif model_type == self.TYPE.B:

            normalized_delays = np.array([
                0, 0.1072, 0.2155, 0.2095, 0.2870, 0.2986, 0.3752, 0.5055,
                0.3681, 0.3697, 0.5700, 0.5283, 1.1021, 1.2756, 1.5474, 1.7842,
                2.0169, 2.8294, 3.0219, 3.6187, 4.1067, 4.2790, 4.7834
            ])
            power_db = np.array([
                0, -2.2, -4, -3.2, -9.8, -3.2, -3.4, -5.2, -7.6, -3, -8.9, -9,
                -4.8, -5.7, -7.5, -1.9, -7.6, -12.2, -9.8, -11.4, -14.9, -9.2,
                -11.3
            ])
            rice_factors = np.zeros(normalized_delays.shape)

        elif model_type == self.TYPE.C:

            normalized_delays = np.array([
                0, 0.2099, 0.2219, 0.2329, 0.2176, 0.6366, 0.6448, 0.6560,
                0.6584, 0.7935, 0.8213, 0.9336, 1.2285, 1.3083, 2.1704, 2.7105,
                4.2589, 4.6003, 5.4902, 5.6077, 6.3065, 6.6374, 7.0427, 8.6523
            ])
            power_db = np.array([
                -4.4, -1.2, -3.5, -5.2, -2.5, 0, -2.2, -3.9, -7.4, -7.1, -10.7,
                -11.1, -5.1, -6.8, -8.7, -13.2, -13.9, -13.9, -15.8, -17.1,
                -16, -15.7, -21.6, -22.8
            ])
            rice_factors = np.zeros(normalized_delays.shape)

        elif model_type == self.TYPE.D:

            if los_doppler_frequency is not None:
                raise ValueError(
                    "Model type D does not support line of sight doppler frequency configuration"
                )

            normalized_delays = np.array([
                0, 0.035, 0.612, 1.363, 1.405, 1.804, 2.596, 1.775, 4.042,
                7.937, 9.424, 9.708, 12.525
            ])
            power_db = np.array([
                -13.5, -18.8, -21, -22.8, -17.9, -20.1, -21.9, -22.9, -27.8,
                -23.6, -24.8, -30.0, -27.7
            ])
            rice_factors = np.zeros(normalized_delays.shape)
            rice_factors[0] = 13.3
            los_doppler_frequency = 0.7

        elif model_type == self.TYPE.E:

            if los_doppler_frequency is not None:
                raise ValueError(
                    "Model type E does not support line of sight doppler frequency configuration"
                )

            normalized_delays = np.array([
                0, 0.5133, 0.5440, 0.5630, 0.5440, 0.7112, 1.9092, 1.9293,
                1.9589, 2.6426, 3.7136, 5.4524, 12.0034, 20.6519
            ])
            power_db = np.array([
                -22.03, -15.8, -18.1, -19.8, -22.9, -22.4, -18.6, -20.8, -22.6,
                -22.3, -25.6, -20.2, -29.8, -29.2
            ])
            rice_factors = np.zeros(normalized_delays.shape)
            rice_factors[0] = 22
            los_doppler_frequency = 0.7

        else:
            raise ValueError("Requested model type not supported")

        self.__model_type = model_type

        # Convert power and normalize
        power_profile = 10**(power_db / 10)
        power_profile /= sum(power_profile)

        # Scale delays
        delays = rms_delay * normalized_delays

        # Init base class with pre-defined model parameters
        MultipathFadingChannel.__init__(
            self,
            delays=delays,
            power_profile=power_profile,
            rice_factors=rice_factors,
            doppler_frequency=doppler_frequency,
            los_doppler_frequency=los_doppler_frequency,
            **kwargs)
예제 #21
0
    def test_rice_factors_get(self) -> None:
        """Rice factors getter should return init param."""

        channel = MultipathFadingChannel(**self.channel_params)
        np.testing.assert_array_almost_equal(self.rice_factors,
                                             channel.rice_factors)
예제 #22
0
    def test_channel_gain(self) -> None:
        """
        Test if channel gain is applied correctly on both propagation and channel impulse response
        """
        gain = 10

        doppler_frequency = 200
        signal_length = 1000

        self.channel_params['delays'][0] = 0.
        self.channel_params['power_profile'][0] = 1.
        self.channel_params['rice_factors'][0] = 0.
        self.channel_params['doppler_frequency'] = doppler_frequency

        channel_no_gain = MultipathFadingChannel(**self.channel_params)

        self.channel_params['gain'] = gain
        channel_gain = MultipathFadingChannel(**self.channel_params)

        frame_size = (1, signal_length)
        tx_samples = rand.normal(
            0, 1, frame_size) + 1j * rand.normal(0, 1, frame_size)
        tx_signal = Signal(tx_samples, self.sampling_rate)

        channel_no_gain.random_generator = np.random.default_rng(
            42)  # Reset random number rng
        signal_out_no_gain, _, _ = channel_no_gain.propagate(tx_signal)

        channel_gain.random_generator = np.random.default_rng(
            42)  # Reset random number rng
        signal_out_gain, _, _ = channel_gain.propagate(tx_signal)

        assert_array_almost_equal(signal_out_gain[0].samples,
                                  signal_out_no_gain[0].samples * gain)

        timestamps = np.array([0, 100, 500]) / self.sampling_rate

        channel_no_gain.random_generator = np.random.default_rng(
            50)  # Reset random number rng
        channel_state_info_no_gain = channel_no_gain.impulse_response(
            len(timestamps), self.sampling_rate)

        channel_gain.random_generator = np.random.default_rng(
            50)  # Reset random number rng
        channel_state_info_gain = channel_gain.impulse_response(
            len(timestamps), self.sampling_rate)

        npt.assert_array_almost_equal(channel_state_info_gain,
                                      channel_state_info_no_gain * gain)
예제 #23
0
    def test_power_profiles_get(self) -> None:
        """Power profiles getter should return init param."""

        channel = MultipathFadingChannel(**self.channel_params)
        np.testing.assert_array_almost_equal(self.power_profile,
                                             channel.power_profile)