예제 #1
0
    def decode(
        self, symbol_stream: np.ndarray,
        channel_state: ChannelStateInformation, stream_noises: np.ndarray
    ) -> Tuple[np.ndarray, ChannelStateInformation, np.ndarray]:

        for time_idx, (symbols, csi, noise) in enumerate(
                zip(symbol_stream.T, channel_state.samples(),
                    stream_noises.T)):
            noise_variance = np.mean(noise)

            # Combine the responses of all superimposed transmit antennas for equalization
            transform = np.sum(csi.linear[:, :, 0, :], axis=2, keepdims=False)

            # Compute the pseudo-inverse from the singular-value-decomposition of the linear channel transform
            # noinspection PyTupleAssignmentBalance
            u, s, vh = svd(transform.todense(),
                           full_matrices=False,
                           check_finite=False)
            u *= s / (s**2 + noise_variance)

            equalizer = (u @ vh).T.conj()

            symbol_stream[:, time_idx] = equalizer @ symbols
            channel_state.state[:, :,
                                time_idx, :] = np.tensordot(equalizer,
                                                            csi.linear[:, :,
                                                                       0, :],
                                                            axes=(1, 0))
            stream_noises[:, time_idx] = noise * (s**2 + noise_variance)

        return symbol_stream, channel_state, stream_noises
    def setUp(self) -> None:

        self.generator = default_rng(42)

        self.num_rx_streams = 2
        self.num_tx_streams = 3
        self.num_samples = 10
        self.num_information = 10
        self.state_format = ChannelStateFormat.IMPULSE_RESPONSE
        self.state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))

        self.csi = ChannelStateInformation(self.state_format, self.state)
예제 #3
0
    def decode(
        self, symbol_stream: np.ndarray,
        channel_state: ChannelStateInformation, stream_noises: np.ndarray
    ) -> Tuple[np.ndarray, ChannelStateInformation, np.ndarray]:

        equalized_symbols = np.empty(
            (channel_state.num_receive_streams, channel_state.num_samples),
            dtype=complex)
        equalized_noises = np.empty(
            (channel_state.num_receive_streams, channel_state.num_samples),
            dtype=float)
        equalized_channel_state = ChannelStateInformation(
            channel_state.state_format)

        # Equalize in space in a first step
        for idx, (symbols, stream_state, noise) in enumerate(
                zip(symbol_stream, channel_state.received_streams(),
                    stream_noises)):

            noise_variance = np.mean(noise)

            # Combine the responses of all superimposed transmit antennas for equalization
            linear_state = stream_state.linear
            transform = np.sum(linear_state[0, ::], axis=0, keepdims=False)

            # Compute the pseudo-inverse from the singular-value-decomposition of the linear channel transform
            # noinspection PyTupleAssignmentBalance
            u, s, vh = svd(transform.todense(),
                           full_matrices=False,
                           check_finite=False)
            u *= s / (s**2 + noise_variance)

            equalizer = (u @ vh).T.conj()

            equalized_symbols[idx, :] = equalizer @ symbols
            equalized_csi_slice = tensordot(equalizer,
                                            linear_state,
                                            axes=(1, 2)).transpose(
                                                (1, 2, 0, 3))
            equalized_channel_state.append_linear(equalized_csi_slice, 0)
            equalized_noises[idx, :] = noise[:stream_state.num_samples] * (
                s**2 + noise_variance)

        return equalized_symbols, channel_state, equalized_noises
예제 #4
0
    def test_symbols_per_frame(self) -> None:
        """Symbols per frame property should compute correct amount of symbols per frame."""

        signal = (
            self.rng.normal(0, 1.0, self.generator.samples_in_frame) +
            1j * self.rng.normal(0, 1.0, self.generator.samples_in_frame))
        channel_state = ChannelStateInformation.Ideal(
            self.generator.samples_in_frame)

        symbols, _, _ = self.generator.demodulate(signal, channel_state)

        self.assertEqual(len(symbols.raw.flatten()),
                         self.generator.symbols_per_frame)
예제 #5
0
    def test_bits_per_frame(self) -> None:
        """Bits per frame property should compute correct amount of data bits per frame."""

        signal = (
            self.rng.normal(0, 1.0, self.generator.samples_in_frame) +
            1j * self.rng.normal(0, 1.0, self.generator.samples_in_frame))
        channel_state = ChannelStateInformation.Ideal(
            self.generator.samples_in_frame)

        data_symbols, _, _ = self.generator.demodulate(signal, channel_state)
        bits = self.generator.unmap(data_symbols)

        self.assertEqual(len(bits), self.generator.bits_per_frame)
예제 #6
0
    def test_phase_shift_synchronization(self) -> None:
        """Test synchronization with arbitrary sample offset and phase shift."""

        bits = self.rng.integers(0, 2, self.waveform.bits_per_frame)
        symbols = self.waveform.map(bits)

        samples = self.waveform.modulate(symbols).samples * np.exp(
            0.24567j * pi)
        padded_samples = np.append(np.zeros((1, 15), dtype=complex), samples)

        frames = self.synchronization.synchronize(
            padded_samples, ChannelStateInformation.Ideal(len(padded_samples)))

        self.assertEqual(len(frames), 1)
        assert_array_almost_equal(frames[0][0], samples)
예제 #7
0
    def test_modulate_demodulate(self) -> None:
        """Modulating and subsequently de-modulating a symbol stream should yield identical symbols."""

        expected_symbols = Symbols(
            np.exp(2j *
                   self.rng.uniform(0, pi, self.generator.symbols_per_frame)))
        # * np.arange(1, 1 + self.rng.symbols_per_frame))

        baseband_signal = self.generator.modulate(expected_symbols)
        channel_state = ChannelStateInformation.Ideal(
            num_samples=baseband_signal.num_samples)
        symbols, _, _ = self.generator.demodulate(
            baseband_signal.samples[0, :], channel_state)

        assert_array_almost_equal(expected_symbols.raw, symbols.raw, decimal=1)
예제 #8
0
    def test_delay_synchronization(self) -> None:
        """Test synchronization with arbitrary sample offset."""

        bits = self.rng.integers(0, 2, self.waveform.bits_per_frame)
        symbols = self.waveform.map(bits)

        signal = self.waveform.modulate(symbols)

        for offset in [0, 1, 10, 15, 20]:

            samples = np.append(np.zeros((1, offset), dtype=complex),
                                signal.samples)

            frames = self.synchronization.synchronize(
                samples, ChannelStateInformation.Ideal(signal.num_samples))

            self.assertEqual(len(frames), 1)
            assert_array_equal(frames[0][0], signal.samples)
예제 #9
0
    def test_modulate_demodulate_no_filter(self) -> None:
        """Modulating and subsequently de-modulating a symbol stream should yield identical symbols."""

        self.generator.rx_filter = ShapingFilter(ShapingFilter.FilterType.NONE,
                                                 self.oversampling_factor)
        self.generator.tx_filter = ShapingFilter(ShapingFilter.FilterType.NONE,
                                                 self.oversampling_factor)

        expected_symbols = Symbols(
            np.exp(2j *
                   self.rng.uniform(0, pi, self.generator.symbols_per_frame)) *
            np.arange(1, 1 + self.generator.symbols_per_frame))

        baseband_signal = self.generator.modulate(expected_symbols)
        channel_state = ChannelStateInformation.Ideal(
            num_samples=baseband_signal.num_samples)
        symbols, _, _ = self.generator.demodulate(
            baseband_signal.samples[0, :], channel_state)

        assert_array_almost_equal(expected_symbols.raw, symbols.raw)
class TestChannelStateInformation(TestCase):
    """Test Channel State Information."""
    def setUp(self) -> None:

        self.generator = default_rng(42)

        self.num_rx_streams = 2
        self.num_tx_streams = 3
        self.num_samples = 10
        self.num_information = 10
        self.state_format = ChannelStateFormat.IMPULSE_RESPONSE
        self.state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))

        self.csi = ChannelStateInformation(self.state_format, self.state)

    def test_init(self) -> None:
        """Init parameters should be properly stored as class attributes."""

        self.assertEqual(self.state_format, self.csi.state_format)
        assert_array_equal(self.state, self.csi.state)

    def test_state_format_get(self) -> None:
        """State format property should return the current state format enum."""

        with patch.object(self.csi,
                          '_ChannelStateInformation__state_format',
                          new_callable=PropertyMock) as mock:
            self.assertIs(mock, self.csi.state_format)

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

        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))

        self.csi.state = state
        assert_array_equal(state, self.csi.state)

    def test_set_state(self) -> None:
        """Set state function should properly modify the channel state."""

        for state_format in [
                ChannelStateFormat.FREQUENCY_SELECTIVITY,
                ChannelStateFormat.IMPULSE_RESPONSE
        ]:

            state = exp(2j * self.generator.uniform(
                0, pi, (self.num_rx_streams, self.num_tx_streams,
                        self.num_samples, self.num_information)))

            self.csi.set_state(state_format, state)
            self.assertEqual(state_format, self.csi.state_format)
            assert_array_equal(state, self.csi.state)

    def test_impulse_response_no_conversion(self) -> None:
        """Test impulse response property get without conversion."""

        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))
        self.csi.set_state(ChannelStateFormat.IMPULSE_RESPONSE, state)

        assert_array_equal(state, self.csi.to_impulse_response().state)
        self.assertEqual(ChannelStateFormat.IMPULSE_RESPONSE,
                         self.csi.state_format)

    def test_impulse_response_conversion(self) -> None:
        """Test impulse response property get without conversion."""
        pass

    def test_frequency_selectivity_no_conversion(self) -> None:
        """Test impulse response property get without conversion."""

        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))
        self.csi.set_state(ChannelStateFormat.FREQUENCY_SELECTIVITY, state)

        assert_array_equal(state, self.csi.to_frequency_selectivity().state)
        self.assertEqual(ChannelStateFormat.FREQUENCY_SELECTIVITY,
                         self.csi.state_format)

    def test_frequency_selectivity_conversion(self) -> None:
        """Test impulse response property get without conversion."""
        pass

#    def test_conversion_frequency_selectivity(self) -> None:
#        """Channel states should remain identical after a round-trip conversion
#        starting from frequency selectivity."""
#
#        state = exp(2j * self.generator.uniform(0, pi, (self.num_rx_streams, self.num_tx_streams,
#                                                        self.num_samples, self.num_information)))
#
#        self.csi.set_state(ChannelStateFormat.FREQUENCY_SELECTIVITY, state)
#        round_trip_state = self.csi.to_impulse_response().to_frequency_selectivity().state
#
#        assert_array_almost_equal(state, round_trip_state)

    def test_conversion_power_scaling(self) -> None:
        """Power of channel states should remain identical after a round-trip conversion."""

        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))

        self.csi.set_state(ChannelStateFormat.FREQUENCY_SELECTIVITY, state)
        round_trip_state = self.csi.to_impulse_response(
        ).to_frequency_selectivity().state

        self.assertAlmostEqual(norm(round_trip_state), norm(state))

    def test_num_receive_streams(self) -> None:
        """Number of receive streams property should report the correct matrix dimension."""

        num_rx_streams = 20
        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (num_rx_streams, self.num_tx_streams,
                                    self.num_samples, self.num_information)))

        self.csi.set_state(ChannelStateFormat.IMPULSE_RESPONSE, state)
        self.assertEqual(num_rx_streams, self.csi.num_receive_streams)

    def test_num_transmit_streams(self) -> None:
        """Number of transmit streams property should report the correct matrix dimension."""

        num_tx_streams = 20
        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, num_tx_streams,
                                    self.num_samples, self.num_information)))

        self.csi.set_state(ChannelStateFormat.IMPULSE_RESPONSE, state)
        self.assertEqual(num_tx_streams, self.csi.num_transmit_streams)

    def test_num_samples(self) -> None:
        """Number of samples property should report the correct matrix dimension."""

        num_samples = 20
        state = exp(
            2j *
            self.generator.uniform(0, pi,
                                   (self.num_rx_streams, self.num_tx_streams,
                                    num_samples, self.num_information)))

        self.csi.set_state(ChannelStateFormat.IMPULSE_RESPONSE, state)
        self.assertEqual(num_samples, self.csi.num_samples)