Пример #1
0
    def test_propagate_MISO(self) -> None:
        """Test valid propagation for the Multiple-Input-Single-Output channel."""

        num_transmit_antennas = 3
        self.transmitter.antennas.num_antennas = num_transmit_antennas
        self.receiver.antennas.num_antennas = 1

        for num_samples in self.propagate_signal_lengths:
            for gain in self.propagate_signal_gains:

                forwards_samples = (np.random.rand(num_transmit_antennas, num_samples)
                                    + 1j * np.random.rand(num_transmit_antennas, num_samples))
                backwards_samples = np.random.rand(1, num_samples) + 1j * np.random.rand(1, num_samples)
                forwards_input = Signal(forwards_samples, self.sampling_rate)
                backwards_input = Signal(backwards_samples, self.sampling_rate)

                self.channel.gain = gain

                expected_forwards_samples = gain * np.sum(forwards_samples, axis=0, keepdims=True)
                expected_backwards_samples = gain * np.repeat(backwards_samples, num_transmit_antennas, axis=0)

                forwards_signal, backwards_signal,  _ = self.channel.propagate(forwards_input, backwards_input)

                assert_array_equal(expected_forwards_samples, forwards_signal[0].samples)
                assert_array_equal(expected_backwards_samples, backwards_signal[0].samples)
Пример #2
0
    def test_propagate_validation(self) -> None:
        """Propagation routine must raise errors in case of unsupported scenarios."""

        with self.assertRaises(ValueError):
            _ = self.channel.propagate(Signal(np.array([1, 2, 3]), self.sampling_rate))

        with self.assertRaises(ValueError):

            self.transmitter.num_antennas = 1
            _ = self.channel.propagate(Signal(np.array([[1, 2, 3], [4, 5, 6]]), self.sampling_rate))

        with self.assertRaises(RuntimeError):

            floating_channel = Channel()
            _ = floating_channel.propagate(Signal(np.array([[1, 2, 3]]), self.sampling_rate))
Пример #3
0
    def test_append_stream_assert(self) -> None:
        """Appending to a signal model should raise a ValueError if the models don't match."""

        with self.assertRaises(ValueError):

            samples = self.signal.samples[:, 0]
            append_signal = Signal(samples, self.signal.sampling_rate,
                                   self.signal.carrier_frequency)
            self.signal.append_streams(append_signal)

        with self.assertRaises(ValueError):

            samples = self.signal.samples
            append_signal = Signal(samples, self.signal.sampling_rate, 0.)
            self.signal.append_streams(append_signal)
    def test_equalize_5GTDL(self) -> None:
        """Test equalization of 5GTDL multipath fading channels."""

        for model_type, num_antennas in product(MultipathFading5GTDL.TYPE,
                                                self.num_antennas):

            self.transmitter.antennas.num_antennas = num_antennas
            self.receiver.antennas.num_antennas = num_antennas

            samples = np.exp(
                2j * self.rng.uniform(0., pi,
                                      (num_antennas, self.num_samples)))
            signal = Signal(samples, self.sampling_rate)
            noise = np.zeros((num_antennas, self.num_samples))

            channel = MultipathFading5GTDL(model_type=model_type,
                                           rms_delay=0.,
                                           transmitter=self.transmitter,
                                           receiver=self.receiver)
            channel.random_mother = self.random_mother

            propagated_signal, _, channel_state = channel.propagate(signal)

            equalized_signal, _, _ = self.equalizer.decode(
                propagated_signal[0].samples, channel_state, noise)
            assert_array_almost_equal(samples, equalized_signal)
    def test_quantization_complex(self):
        """ Test correct quantization of complex numbers"""
        max_amplitude = 100
        self.quantizer.gain = Gain(1 / max_amplitude)

        # randomly choose quantization levels
        quantization_idx = self.rng.integers(
            self.quantizer.num_quantization_levels, size=(2, self.num_samples))
        quantization_step = 2 * max_amplitude / self.quantizer.num_quantization_levels
        quantization_levels = -max_amplitude + quantization_step * (
            quantization_idx + .5)

        # add random noise within quantization interval
        random_noise = self.rng.uniform(-quantization_step / 2,
                                        quantization_step / 2,
                                        size=(2, self.num_samples))
        input_signal = quantization_levels + random_noise

        quantization_levels = quantization_levels[
            0, :] + 1j * quantization_levels[1, :]
        input_signal = input_signal[0, :] + 1j * input_signal[1, :]

        input_signal = Signal(samples=input_signal, sampling_rate=1.)

        output_signal = self.quantizer.convert(input_signal)

        np.testing.assert_almost_equal(output_signal.samples.flatten(),
                                       quantization_levels)
    def test_quantization_rms(self):
        """ Test correct quantizer output with gain control to rms amplitude"""

        rms_amplitude = 576577.79

        self.quantizer.gain = AutomaticGainControl(
            GainControlType.RMS_AMPLITUDE)

        # create signal with desired rms
        input_signal = self.rng.normal(size=self.num_samples)
        measured_rms = rms_value(input_signal)
        input_signal = input_signal / measured_rms * rms_amplitude
        test_signal = deepcopy(input_signal)

        # create non-adaptive quantizer with desired amplitude
        quantizer_no_gain_control = deepcopy(self.quantizer)
        quantizer_no_gain_control.gain = Gain(1 / rms_amplitude)

        # Output of both quantizers must be the same
        input_signal = Signal(samples=input_signal, sampling_rate=1.)

        output_signal_adaptive = self.quantizer.convert(input_signal)
        output_signal_non_adaptive = quantizer_no_gain_control.convert(
            input_signal)

        np.testing.assert_almost_equal(output_signal_adaptive.samples,
                                       output_signal_non_adaptive.samples)
    def test_quantization_max_amplitude(self):
        """ Test correct quantizer output with gain control to maximum amplitude"""

        self.quantizer.gain = AutomaticGainControl(
            GainControlType.MAX_AMPLITUDE)

        max_amplitude = 123.7

        # randomly choose quantization levels
        quantization_idx = self.rng.integers(
            self.quantizer.num_quantization_levels, size=self.num_samples)

        quantization_step = 2 * max_amplitude / self.quantizer.num_quantization_levels
        quantization_levels = -max_amplitude + quantization_step * (
            quantization_idx + .5)

        # add random noise within quantization interval
        random_noise = self.rng.uniform(-quantization_step / 2,
                                        quantization_step / 2,
                                        size=self.num_samples)
        input_signal = quantization_levels + random_noise

        # add maximum amplitude value to input_vector
        input_signal = np.append(input_signal, [max_amplitude])
        quantization_levels = np.append(
            quantization_levels, [max_amplitude - quantization_step / 2])

        input_signal = Signal(samples=input_signal, sampling_rate=1.)

        output_signal = self.quantizer.convert(input_signal)

        np.testing.assert_almost_equal(
            np.real(output_signal.samples.flatten()), quantization_levels)
    def test_quantization_no_gain_control(self):
        """ Test correct quantizer output without gain control"""

        max_amplitude = 100
        self.quantizer.gain = Gain(1 / max_amplitude)

        # randomly choose quantization levels
        quantization_idx = self.rng.integers(
            self.quantizer.num_quantization_levels, size=self.num_samples)
        quantization_step = 2 * max_amplitude / self.quantizer.num_quantization_levels
        quantization_levels = -max_amplitude + quantization_step * (
            quantization_idx + .5)

        # add random noise within quantization interval
        random_noise = self.rng.uniform(-quantization_step / 2,
                                        quantization_step / 2,
                                        size=self.num_samples)
        input_signal = quantization_levels + random_noise

        # add saturated values
        max_quantization_level = max_amplitude - quantization_step / 2
        quantization_levels = np.append(
            quantization_levels,
            [-max_quantization_level, max_quantization_level])
        saturated_level = max_amplitude + 10.
        input_signal = np.append(input_signal,
                                 [-saturated_level, saturated_level])

        input_signal = Signal(samples=input_signal, sampling_rate=1.)

        output_signal = self.quantizer.convert(input_signal)

        np.testing.assert_almost_equal(
            np.real(output_signal.samples.flatten()), quantization_levels)
Пример #9
0
    def test_synchronization_offset(self) -> None:
        """The synchronization offset should be applied properly by adding a delay to the propgated signal."""

        self.transmitter.antennas.num_antennas = 1
        self.receiver.antennas.num_antennas = 1

        mock_generator = Mock()
        self.random_node._rng = mock_generator

        for num_samples in self.propagate_signal_lengths:
            for offset in [0, 1., -10., 100.]:

                samples = np.random.rand(1, num_samples) + 1j * np.random.rand(1, num_samples)
                signal_delay = 0.11
                signal = Signal(samples, self.sampling_rate, delay=signal_delay)

                mock_generator.uniform.return_value = 0.
                instant_signal, _, instant_channel = self.channel.propagate(signal)

                mock_generator.uniform.return_value = offset
                offset_signal, _, offset_channel = self.channel.propagate(signal)

                # The propagated signal should be delayed by the offset, while the CSI does not change
                assert_array_equal(instant_signal[0].samples, offset_signal[0].samples)
                assert_array_equal(instant_channel.state, offset_channel.state)
                self.assertEqual(signal_delay + offset, offset_signal[0].delay)
    def transmit(self, duration: float = 0.) -> Tuple[Signal]:

        silence = Signal(np.zeros(
            (self.device.num_antennas, self.__num_samples), dtype=complex),
                         sampling_rate=self.sampling_rate,
                         carrier_frequency=self.device.carrier_frequency)

        self.slot.add_transmission(self, silence)
        return silence,
Пример #11
0
    def setUp(self) -> None:

        self.random = default_rng(42)

        self.num_streams = 3
        self.num_samples = 100
        self.sampling_rate = 1e4
        self.carrier_frequency = 1e3
        self.delay = 0.

        self.samples = (self.random.random(
            (self.num_streams, self.num_samples)) + 1j * self.random.random(
                (self.num_streams, self.num_samples)))

        self.signal = Signal(samples=self.samples,
                             sampling_rate=self.sampling_rate,
                             carrier_frequency=self.carrier_frequency,
                             delay=self.delay)
Пример #12
0
    def test_estimate_noise_power(self, mock_trigger) -> None:
        """Noise power estimation should return the correct power estimate."""

        num_samples = 10000
        expected_noise_power = .1
        samples = 2 ** -.5 * (self.rng.normal(size=num_samples, scale=expected_noise_power ** .5) + 1j *
                              self.rng.normal(size=num_samples, scale=expected_noise_power ** .5))
        signal = Signal(samples, sampling_rate=self.sampling_rate)
        mock_trigger.side_effect = lambda: [receiver.cache_reception(signal) for receiver in self.device.receivers]

        noise_power = self.device.estimate_noise_power(num_samples)
        self.assertAlmostEqual(expected_noise_power, noise_power[0], places=2)
Пример #13
0
    def test_append_streams(self) -> None:
        """Appending a signal model should yield the proper result."""

        samples = self.signal.samples.copy()
        append_samples = self.signal.samples + 1j
        append_signal = Signal(append_samples, self.signal.sampling_rate,
                               self.signal.carrier_frequency)

        self.signal.append_streams(append_signal)

        assert_array_equal(np.append(samples, append_samples, axis=0),
                           self.signal.samples)
Пример #14
0
    def test_empty(self) -> None:
        """Using the empty initializer should result in an empty signal model."""

        sampling_rate = 2
        num_streams = 5
        num_samples = 6
        empty_signal = Signal.empty(sampling_rate,
                                    num_streams=num_streams,
                                    num_samples=num_samples)

        self.assertEqual(sampling_rate, empty_signal.sampling_rate)
        self.assertEqual(num_samples, empty_signal.num_samples)
        self.assertEqual(num_streams, empty_signal.num_streams)
    def test_no_quantization(self) -> None:
        """ Test correct quantizer output with infinite resolution"""

        self.quantizer.num_quantization_bits = 0

        input_signal = (np.random.normal(size=self.num_samples) +
                        1j * np.random.normal(size=self.num_samples))

        input_signal = Signal(sampling_rate=1., samples=input_signal)
        output_signal = self.quantizer.convert(input_signal)

        np.testing.assert_array_equal(input_signal.samples,
                                      output_signal.samples)
    def receive(self, signal: Signal) -> None:
        """Receive a new signal at this physical device.

        Args:
            signal (Signal):
                Signal model to be received.
        """

        # Signal is now a baseband-signal
        signal.carrier_frequency = 0.

        # Account for negative calibrations delays by
        # appending zeros to the signal start
        if self.__calibration_delay < 0.:

            num_padding_zeros = int(-self.__calibration_delay *
                                    signal.sampling_rate)
            signal.samples = signal.samples[:, num_padding_zeros:]

        # Cache reception at each operator
        for receiver in self.receivers:
            receiver.cache_reception(signal)
Пример #17
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)
Пример #18
0
    def test_add_noise_power(self) -> None:
        """Added noise should have correct power"""

        signal = np.zeros(1000000, dtype=complex)
        powers = np.array([0, 1, 100, 1000])

        for expected_noise_power in powers:

            noisy_signal = Signal(signal, sampling_rate=1.)
            self.noise.add(noisy_signal, expected_noise_power)
            noise_power = np.var(noisy_signal.samples)

            self.assertTrue(
                abs(noise_power -
                    expected_noise_power) <= (0.001 * expected_noise_power))
Пример #19
0
    def test_channel_state_information(self) -> None:
        """Propagating over the linear channel state model should return identical results."""

        self.transmitter.antennas.num_antennas = 1
        self.receiver.antennas.num_antennas = 1

        for num_samples in self.propagate_signal_lengths:
            for gain in self.propagate_signal_gains:

                samples = np.random.rand(1, num_samples) + 1j * np.random.rand(1, num_samples)
                signal = Signal(samples, self.sampling_rate)
                self.channel.gain = gain

                forwards, backwards, csi = self.channel.propagate(signal, signal)
                expected_csi_signal = csi.linear[0, 0, ::].todense() @ samples.T

                assert_array_equal(forwards[0].samples, expected_csi_signal.T)
                assert_array_equal(backwards[0].samples, expected_csi_signal.T)
    def pilot_signal(self) -> Signal:
        """Samples of the frame's pilot section.

        Returns:
            samples (np.ndarray): Pilot samples.
        """

        # Generate single pilot chirp prototype
        prototypes, _ = self._prototypes()

        pilot_samples = np.empty(self.samples_in_chirp * self.num_pilot_chirps,
                                 dtype=complex)
        for pilot_idx in range(self.num_pilot_chirps):

            pilot_samples[pilot_idx*self.samples_in_chirp:(1+pilot_idx)*self.samples_in_chirp] = \
                prototypes[pilot_idx % len(prototypes)]

        return Signal(pilot_samples, self.sampling_rate)
Пример #21
0
    def test_propagate_SISO(self) -> None:
        """Test valid propagation for the Single-Input-Single-Output channel."""

        self.transmitter.antennas.num_antennas = 1
        self.receiver.antennas.num_antennas = 1

        for num_samples in self.propagate_signal_lengths:
            for gain in self.propagate_signal_gains:

                samples = np.random.rand(1, num_samples) + 1j * np.random.rand(1, num_samples)
                signal = Signal(samples, self.sampling_rate)

                self.channel.gain = gain

                expected_propagated_samples = gain * samples
                forwards_signal, backwards_signal, _ = self.channel.propagate(signal, signal)

                assert_array_equal(expected_propagated_samples, forwards_signal[0].samples)
                assert_array_equal(expected_propagated_samples, backwards_signal[0].samples)
Пример #22
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")
    def setUp(self) -> None:

        self.precoding = SymbolPrecoding()
        self.equalizer = MMSETimeEqualizer()
        self.precoding[0] = self.equalizer

        self.transmitter = Mock()
        self.transmitter.antennas.num_antennas = 1
        self.receiver = Mock()
        self.receiver.antennas.num_antennas = 1

        self.rng = np.random.default_rng(42)
        self.random_mother = Mock()
        self.random_mother._rng = self.rng
        self.num_samples = 1000
        self.sampling_rate = 1e6
        self.samples = np.exp(2j * self.rng.uniform(0., pi, self.num_samples))
        self.signal = Signal(self.samples, self.sampling_rate)

        self.noise_variances = [0, 1e-4]
        self.rms_delays = [0., 1e-5]
    def modulate(self, data_symbols: Symbols) -> Signal:

        prototypes, _ = self._prototypes()
        samples = np.empty(self.samples_in_frame, dtype=complex)

        sample_idx = 0
        samples_in_chirp = self.samples_in_chirp

        # Add pilot samples
        pilot_samples = self.pilot_signal.samples.flatten()
        num_pilot_samples = len(pilot_samples)
        samples[:num_pilot_samples] = pilot_samples
        sample_idx += num_pilot_samples

        # Modulate data symbols
        for symbol in data_symbols.raw[0, :]:

            samples[sample_idx:sample_idx +
                    samples_in_chirp] = prototypes[symbol, :]
            sample_idx += samples_in_chirp

        return Signal(samples, self.sampling_rate)
Пример #25
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:])
    def test_quantization_mid_tread(self):
        """ Test correct mid-tread quantizer output without gain control"""

        max_amplitude = 150.
        self.quantizer.gain = Gain(1 / max_amplitude)
        self.quantizer.quantizer_type = QuantizerType.MID_TREAD

        # randomly choose quantization levels
        quantization_idx = self.rng.integers(
            self.quantizer.num_quantization_levels, size=self.num_samples)
        quantization_step = 2 * max_amplitude / self.quantizer.num_quantization_levels
        quantization_levels = -max_amplitude + quantization_step * quantization_idx

        # add random noise within quantization interval
        random_noise = self.rng.uniform(-quantization_step / 2,
                                        quantization_step / 2,
                                        size=self.num_samples)
        input_signal = quantization_levels + random_noise

        # add saturated values
        max_quantization_level = max_amplitude - quantization_step
        min_quantization_level = -max_amplitude
        quantization_levels = np.append(
            quantization_levels,
            [min_quantization_level, max_quantization_level])
        saturated_level_pos = max_quantization_level + 10.
        saturated_level_neg = min_quantization_level - 10.
        input_signal = np.append(input_signal,
                                 [saturated_level_neg, saturated_level_pos])

        input_signal = Signal(samples=input_signal, sampling_rate=1.)

        output_signal = self.quantizer.convert(input_signal)

        np.testing.assert_almost_equal(
            np.real(output_signal.samples.flatten()), quantization_levels)
Пример #27
0
 def modulate(self, data_symbols: np.ndarray) -> Signal:
     return Signal(data_symbols, self.sampling_rate)
Пример #28
0
class TestSignal(unittest.TestCase):
    """Test the signal model base class."""
    def setUp(self) -> None:

        self.random = default_rng(42)

        self.num_streams = 3
        self.num_samples = 100
        self.sampling_rate = 1e4
        self.carrier_frequency = 1e3
        self.delay = 0.

        self.samples = (self.random.random(
            (self.num_streams, self.num_samples)) + 1j * self.random.random(
                (self.num_streams, self.num_samples)))

        self.signal = Signal(samples=self.samples,
                             sampling_rate=self.sampling_rate,
                             carrier_frequency=self.carrier_frequency,
                             delay=self.delay)

    def test_init(self) -> None:
        """Initialization arguments should be properly stored as object attributes."""

        assert_array_equal(self.samples, self.signal.samples)
        self.assertEqual(self.sampling_rate, self.signal.sampling_rate)
        self.assertEqual(self.carrier_frequency, self.signal.carrier_frequency)
        self.assertEqual(self.delay, self.signal.delay)

    def test_empty(self) -> None:
        """Using the empty initializer should result in an empty signal model."""

        sampling_rate = 2
        num_streams = 5
        num_samples = 6
        empty_signal = Signal.empty(sampling_rate,
                                    num_streams=num_streams,
                                    num_samples=num_samples)

        self.assertEqual(sampling_rate, empty_signal.sampling_rate)
        self.assertEqual(num_samples, empty_signal.num_samples)
        self.assertEqual(num_streams, empty_signal.num_streams)

    def test_samples_setget(self) -> None:
        """Samples property getter should return setter argument."""

        samples = (self.random.random(
            (self.num_streams + 1, self.num_samples + 1)) +
                   1j * self.random.random(
                       (self.num_streams + 1, self.num_samples + 1)))

        self.signal.samples = samples
        assert_array_equal(samples, self.signal.samples)

    def test_samples_validation(self) -> None:
        """Samples property setter should raise ValueError on invalid arguments."""

        with self.assertRaises(ValueError):
            self.signal.samples = self.random.random((1, 2, 3))

    def test_num_streams(self) -> None:
        """Number of streams property should return the correct number of streams."""

        self.assertEqual(self.num_streams, self.signal.num_streams)

    def test_num_samples(self) -> None:
        """Number of samples property should return the correct number of samples."""

        self.assertEqual(self.num_samples, self.signal.num_samples)

    def test_sampling_rate_setget(self) -> None:
        """Sampling rate property getter should return setter argument."""

        sampling_rate = 1.123e4
        self.signal.sampling_rate = sampling_rate

        self.assertEqual(sampling_rate, self.signal.sampling_rate)

    def test_sampling_rate_validation(self) -> None:
        """Sampling rate property setter should raise ValueError on invalid arguments."""

        with self.assertRaises(ValueError):
            self.signal.sampling_rate = -1.23

        with self.assertRaises(ValueError):
            self.signal.sampling_rate = 0.

    def test_carrier_frequency_setget(self) -> None:
        """Carrier frequency property getter should return setter argument."""

        carrier_frequency = 1.123
        self.signal.carrier_frequency = carrier_frequency

        self.assertEqual(carrier_frequency, self.signal.carrier_frequency)

    def test_carrier_frequency_validation(self) -> None:
        """Carrier frequency setter should raise ValueError on invalid arguments."""

        with self.assertRaises(ValueError):
            self.signal.carrier_frequency = -1.0

        try:
            self.signal.carrier_frequency = 0.

        except ValueError:
            self.fail()

    def test_copy(self) -> None:
        """Copying a signal model should result in a completely independent instance."""

        samples = self.signal.samples.copy()
        signal_copy = self.signal.copy()
        signal_copy.samples += 1j

        assert_array_equal(samples, self.signal.samples)

    def test_resampling_power_up(self) -> None:
        """Resampling to a higher sampling rate should not affect the signal power."""

        # Create an oversampled sinusoid signal
        frequency = 0.1 * self.sampling_rate
        self.num_samples = 1000
        timestamps = np.arange(self.num_samples) / self.sampling_rate
        samples = np.outer(np.exp(2j * pi * np.array([0, .33, .66])),
                           np.exp(2j * pi * timestamps * frequency))
        self.signal.samples = samples

        expected_sampling_rate = 2 * self.sampling_rate
        resampled_signal = self.signal.resample(expected_sampling_rate)

        expected_power = self.signal.power
        resampled_power = resampled_signal.power

        assert_array_almost_equal(expected_power, resampled_power, decimal=3)
        self.assertEqual(expected_sampling_rate,
                         resampled_signal.sampling_rate)

    def test_resampling_power_down(self) -> None:
        """Resampling to a lower sampling rate should not affect the signal power."""

        # Create an oversampled sinusoid signal
        frequency = 0.1 * self.sampling_rate
        self.num_samples = 1000
        timestamps = np.arange(self.num_samples) / self.sampling_rate
        samples = np.outer(np.exp(2j * pi * np.array([0, .33, .66])),
                           np.exp(2j * pi * timestamps * frequency))
        self.signal.samples = samples

        expected_sampling_rate = .5 * self.sampling_rate
        resampled_signal = self.signal.resample(expected_sampling_rate)

        expected_power = self.signal.power
        resampled_power = resampled_signal.power

        assert_array_almost_equal(expected_power, resampled_power, decimal=3)
        self.assertEqual(expected_sampling_rate,
                         resampled_signal.sampling_rate)

    def test_resampling_circular(self) -> None:
        """Up- and subsequently down-sampling a signal model should result in the identical signal."""

        # Create an oversampled sinusoid signal
        frequency = 0.3 * self.sampling_rate
        timestamps = np.arange(self.num_samples) / self.sampling_rate
        samples = np.outer(np.exp(2j * pi * np.array([0, 0.33, 0.66])),
                           np.exp(2j * pi * timestamps * frequency))
        self.signal.samples = samples

        # Up-sample and down-sample again
        up_signal = self.signal.resample(1.5 * self.sampling_rate)
        down_signal = up_signal.resample(self.sampling_rate)

        # Compare to the initial samples
        assert_array_almost_equal(samples, down_signal.samples, decimal=1)
        self.assertEqual(self.sampling_rate, down_signal.sampling_rate)

    def test_superimpose_power(self) -> None:
        """Superimposing two signal models should yield approximately the sum of both model's individual power."""

        expected_power = 4 * self.signal.power
        self.signal.superimpose(self.signal)

        assert_array_almost_equal(expected_power, self.signal.power)

    def test_timestamps(self) -> None:
        """Timestamps property should return the correct sampling times."""

        expected_timestamps = np.arange(self.num_samples) / self.sampling_rate
        assert_array_equal(expected_timestamps, self.signal.timestamps)

    def test_plot(self) -> None:
        """The plot routine should not raise any exceptions."""
        pass

    def test_append_samples(self) -> None:
        """Appending a signal model should yield the proper result."""

        samples = self.signal.samples.copy()
        append_samples = self.signal.samples + 1j
        append_signal = Signal(append_samples, self.signal.sampling_rate,
                               self.signal.carrier_frequency)

        self.signal.append_samples(append_signal)

        assert_array_equal(np.append(samples, append_samples, axis=1),
                           self.signal.samples)

    def test_append_samples_assert(self) -> None:
        """Appending to a signal model should raise a ValueError if the models don't match."""

        with self.assertRaises(ValueError):

            samples = self.signal.samples[0, :]
            append_signal = Signal(samples, self.signal.sampling_rate,
                                   self.signal.carrier_frequency)
            self.signal.append_samples(append_signal)

        with self.assertRaises(ValueError):

            samples = self.signal.samples
            append_signal = Signal(samples, self.signal.sampling_rate, 0.)
            self.signal.append_samples(append_signal)

    def test_append_streams(self) -> None:
        """Appending a signal model should yield the proper result."""

        samples = self.signal.samples.copy()
        append_samples = self.signal.samples + 1j
        append_signal = Signal(append_samples, self.signal.sampling_rate,
                               self.signal.carrier_frequency)

        self.signal.append_streams(append_signal)

        assert_array_equal(np.append(samples, append_samples, axis=0),
                           self.signal.samples)

    def test_append_stream_assert(self) -> None:
        """Appending to a signal model should raise a ValueError if the models don't match."""

        with self.assertRaises(ValueError):

            samples = self.signal.samples[:, 0]
            append_signal = Signal(samples, self.signal.sampling_rate,
                                   self.signal.carrier_frequency)
            self.signal.append_streams(append_signal)

        with self.assertRaises(ValueError):

            samples = self.signal.samples
            append_signal = Signal(samples, self.signal.sampling_rate, 0.)
            self.signal.append_streams(append_signal)
Пример #29
0
    def transmit(self,
                 duration: float = 0.) -> Tuple[Signal, Symbols, np.ndarray]:
        """Returns an array with the complex base-band samples of a waveform generator.

        The signal may be distorted by RF impairments.

        Args:
            duration (float, optional): Length of signal in seconds.

        Returns:
            transmissions (tuple):

                signal (Signal):
                    Signal model carrying the `data_bits` in multiple streams, each stream encoding multiple
                    radio frequency band communication frames.

                symbols (Symbols):
                    Symbols to which `data_bits` were mapped, used to modulate `signal`.

                data_bits (np.ndarray):
                    Vector of bits mapped to `data_symbols`.
        """

        # By default, the drop duration will be exactly one frame
        if duration <= 0.:
            duration = self.frame_duration

        # Number of data symbols per transmitted frame
        symbols_per_frame = self.waveform_generator.symbols_per_frame

        # Number of frames fitting into the selected drop duration
        frames_per_stream = int(duration / self.waveform_generator.frame_duration)

        # Generate data bits
        data_bits = self.generate_data_bits()

        # Number of code bits required to generate all frames for all streams
        num_code_bits = int(self.waveform_generator.bits_per_frame * frames_per_stream / self.precoding.rate)

        # Encode the data bits
        code_bits = self.encoder_manager.encode(data_bits, num_code_bits)

        # Map data bits to symbols
        symbols = self.waveform_generator.map(code_bits)

        # Apply symbol precoding
        symbol_streams = Symbols(self.precoding.encode(symbols.raw))

        # Check that the number of symbol streams matches the number of required symbol streams
        if symbol_streams.num_streams != self.num_streams:
            raise RuntimeError("Invalid precoding configuration, the number of resulting streams does not "
                               "match the number of transmit antennas")

        # Generate a dedicated base-band signal for each symbol stream
        signal = Signal(np.empty((0, 0), dtype=complex),
                        self.waveform_generator.sampling_rate)

        for stream_idx, stream_symbols in enumerate(symbol_streams):

            stream_signal = Signal(np.empty((0, 0), dtype=complex),
                                   self.waveform_generator.sampling_rate)

            for frame_idx in range(frames_per_stream):

                data_symbols = stream_symbols[frame_idx*symbols_per_frame:(1+frame_idx)*symbols_per_frame]

                frame_signal = self.waveform_generator.modulate(data_symbols)
                stream_signal.append_samples(frame_signal)

            signal.append_streams(stream_signal)

        # Apply stream coding, for instance beam-forming
        # TODO: Not yet supported.

        # Change the signal carrier
        # signal.carrier_frequency = self.carrier_frequency

        # Transmit signal over the occupied device slot (if the modem is attached to a device)
        if self._transmitter.attached:
            self._transmitter.slot.add_transmission(self._transmitter, signal)

        # Cache transmissions
        self.__transmitted_bits = data_bits
        self.__transmitted_symbols = symbols

        # We're finally done, blow the fanfares, throw confetti, etc.
        return signal, symbols, data_bits
    def calibrate(self,
                  max_delay: float,
                  calibration_file: str,
                  num_iterations: int = 10,
                  wait: float = 0.) -> plt.Figure:
        """Calibrate the hardware.

        Currently the calibration will only compensate possible time-delays.
        Ideally, the transmit and receive channels of the device should be connected by a patch cable.
        WARNING: An attenuator element may be required! Be careful!!!!

        Args:

            max_delay (float):
                The maximum expected delay which the calibration should compensate for in seconds.

            calibration_file (str):
                Location of the calibration dump within the filesystem.

            num_iterations (int, optional):
                Number of calibration iterations.
                Default is 10.

            wait (float, optional):
                Idle time between iteration transmissions in seconds.

        Returns:

            plt.Figure:
                A figure of information regarding the calibration.
        """

        if num_iterations < 1:
            raise ValueError(
                "The number of iterations must be greater or equal to one")

        if wait < 0.:
            raise ValueError(
                "The waiting time must be greater or equal to zero")

        # Clear transmit caches
        self.transmitters.clear_cache()

        num_samples = int(2 * max_delay * self.sampling_rate)
        if num_samples <= 1:
            raise ValueError(
                "The assumed maximum delay is not resolvable by the configured sampling rate"
            )

        # Register operators
        calibration_transmitter = SignalTransmitter(num_samples)
        self.transmitters.add(calibration_transmitter)

        calibration_receiver = SignalReceiver(num_samples)
        self.receivers.add(calibration_receiver)

        dirac_index = int(max_delay * self.sampling_rate)
        waveform = np.zeros((self.num_antennas, num_samples), dtype=complex)
        waveform[:, dirac_index] = 1.
        calibration_signal = Signal(waveform, self.sampling_rate,
                                    self.carrier_frequency)

        propagated_signals: List[Signal] = []
        propagated_dirac_indices = np.empty(num_iterations, dtype=int)

        # Make multiple iteration calls for calibration
        for i in range(num_iterations):

            # Send and receive calibration waveform
            calibration_transmitter.transmit(calibration_signal)
            self.trigger()
            propagated_signal = calibration_receiver.receive()

            # Infer the implicit delay by estimating the sample index of the propagated dirac
            propagated_signals.append(propagated_signal)
            propagated_dirac_indices = np.argmax(
                abs(propagated_signal.samples[0, :]))

            # Wait the configured amount of time between iterations
            sleep(wait)

        # Un-register operators
        self.transmitters.remove(calibration_transmitter)
        self.receivers.remove(calibration_receiver)

        # Compute calibration delay
        mean_dirac_index = np.mean(
            propagated_dirac_indices)  # This is just a ML estimation
        calibration_delay = (
            dirac_index - mean_dirac_index) / propagated_signal.sampling_rate

        # Dump calibration to pickle savefile
        with open(calibration_file, 'wb') as file_stream:
            dump(calibration_delay, file_stream)

        # Save register calibration delay internally
        self.__calibration_delay = calibration_delay

        # Visualize the results
        fig, axes = plt.subplots(2, 1)
        fig.suptitle('Device Delay Calibration')

        axes[0].plot(calibration_signal.timestamps,
                     abs(calibration_signal.samples[0, :]))
        for sig in propagated_signals:
            axes[1].plot(sig.timestamps, abs(sig.samples[0, :]), color='blue')
        axes[1].axvline(x=(dirac_index / self.sampling_rate -
                           calibration_delay),
                        color='red')