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)):

            # 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

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

            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

        return symbol_stream, channel_state, stream_noises
Пример #2
0
    def test_synchronize_validation(self) -> None:
        """Synchronization should raise a ValueError if the signal shape does match the stream response shape."""

        with self.assertRaises(ValueError):
            _ = self.waveform_generator.synchronization.synchronize(
                np.zeros(10),
                ChannelStateInformation(ChannelStateFormat.IMPULSE_RESPONSE,
                                        np.zeros((10, 2))))

        with self.assertRaises(ValueError):
            _ = self.waveform_generator.synchronization.synchronize(
                np.zeros((10, 2)),
                ChannelStateInformation(ChannelStateFormat.IMPULSE_RESPONSE,
                                        np.zeros((10, 2))))
    def test_synchronization(self) -> None:
        """Synchronization should properly partition signal samples into frame sections."""

        # Generate frame signal models
        num_samples = 2 * self.max_offset + self.num_frames * self.waveform.samples_in_frame
        csi = ChannelStateInformation.Ideal(num_samples)
        samples = np.zeros((1, num_samples), dtype=complex)
        expected_frames = []
        pilot_indices = self.rng.integers(
            0, self.max_offset, self.num_frames) + np.arange(
                self.num_frames) * self.waveform.samples_in_frame

        for p in pilot_indices:

            data_symbols = Symbols(
                self.rng.integers(0, self.waveform.modulation_order,
                                  self.waveform.symbols_per_frame))
            signal_samples = self.waveform.modulate(data_symbols).samples

            samples[:, p:p + self.waveform.samples_in_frame] += signal_samples
            expected_frames.append(samples[:, p:p +
                                           self.waveform.samples_in_frame])

        synchronized_frames = self.synchronization.synchronize(samples, csi)

        if len(synchronized_frames) != len(expected_frames):
            self.fail()

        for expected_frame, (synchronized_frame,
                             _) in zip(expected_frames, synchronized_frames):
            assert_array_equal(expected_frame, synchronized_frame)
Пример #4
0
    def test_synchronize(self) -> None:
        """Test the proper estimation of delays during Schmidl-Cox synchronization"""

        for d, n in product(self.delays_in_samples, self.num_frames):

            symbols = np.exp(
                2j * pi * self.rng.uniform(0, 1,
                                           (n, self.frame.symbols_per_frame)))
            frames = [
                np.exp(2j * pi * self.rng.uniform(0, 1, (self.num_streams, 1)))
                @ self.frame.modulate(Symbols(symbols[f, :])).samples
                for f in range(n)
            ]

            signal = np.empty((self.num_streams, 0), dtype=complex)
            for frame in frames:

                signal = np.concatenate(
                    (signal, np.zeros(
                        (self.num_streams, d), dtype=complex), frame),
                    axis=1)

            channel_state = ChannelStateInformation.Ideal(
                len(signal), self.num_streams)

            synchronization = self.synchronization.synchronize(
                signal, channel_state)

            self.assertEqual(n, len(synchronization))
            for frame, (synchronized_frame, _) in zip(frames, synchronization):
                assert_array_equal(frame, synchronized_frame)
Пример #5
0
    def test_synchronize(self) -> None:
        """Default synchronization routine should properly split signals into frame-sections."""

        num_streams = 3
        num_samples_test = [50, 100, 150, 200]

        for num_samples in num_samples_test:

            signal = np.exp(
                2j * self.rnd.uniform(0, pi, (num_streams, 1))) @ np.exp(
                    2j * self.rnd.uniform(0, pi, (1, num_samples)))
            channel_state = ChannelStateInformation.Ideal(
                num_samples, num_streams)

            synchronized_frames = self.waveform_generator.synchronization.synchronize(
                signal, channel_state)

            # Number of frames is the number of frames that fit into the samples
            num_frames = len(synchronized_frames)
            expected_num_frames = int(
                floor(num_samples / self.waveform_generator.samples_in_frame))
            self.assertEqual(expected_num_frames, num_frames)

            # Frames and channel states should each contain the correct amount of samples
            for frame_signal, frame_channel_state in synchronized_frames:

                self.assertEqual(num_streams, frame_signal.shape[0])
                self.assertEqual(self.waveform_generator.samples_in_frame,
                                 frame_signal.shape[1])
                self.assertEqual(self.waveform_generator.samples_in_frame,
                                 frame_channel_state.num_samples)
    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)):

            # 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

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

            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

        return equalized_symbols, channel_state, equalized_noises
Пример #7
0
    def test_modulate_demodulate_no_dc_suppression(self) -> None:
        """Modulating and subsequently de-modulating an OFDM symbol section should yield correct symbols."""

        expected_symbols = np.exp(
            2j * self.rnd.uniform(0, pi, self.section.num_symbols))

        modulated_signal = self.section.modulate(expected_symbols)
        channel_state = ChannelStateInformation.Ideal(
            num_samples=modulated_signal.shape[0])
        ofdm_grid, _ = self.section.demodulate(modulated_signal, channel_state)

        symbols = ofdm_grid.T[self.section.resource_mask[
            ElementType.DATA.value].T]
        assert_array_almost_equal(expected_symbols, symbols)
    def test_encode_decode_circular(self) -> None:
        """Encoding and subsequently decoding a data stream should lead to identical symbols."""

        input_stream = self.generator.random((1, 400))
        channel_state = ChannelStateInformation(
            ChannelStateFormat.IMPULSE_RESPONSE,
            self.generator.random((4, 1, 100, 1)))
        stream_noise = self.generator.random((4, 100))

        encoded_stream = self.precoder.encode(input_stream)
        decoded_stream, decoded_responses, _ = self.precoder.decode(
            encoded_stream, channel_state, stream_noise)

        assert_array_equal(input_stream, decoded_stream)
    def decode(
        self, symbol_stream: np.ndarray,
        channel_state: ChannelStateInformation, stream_noises: np.ndarray
    ) -> Tuple[np.ndarray, ChannelStateInformation, np.ndarray]:

        # Collect data symbols from the stream
        symbol_stream = np.reshape(symbol_stream, (1, -1))  # 'F'
        stream_noises = np.reshape(stream_noises, (1, -1))  # 'F'

        state = channel_state.state
        channel_state.state = state.reshape(
            (1, state.shape[1], -1, state.shape[3]))  # 'F'

        return symbol_stream, channel_state, stream_noises
Пример #10
0
    def test_modulate_demodulate(self) -> None:
        """Modulating and subsequently de-modulating a data frame 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.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)
Пример #11
0
    def test_synchronize(self) -> None:
        """Default synchronization should properly split signals into frame-sections."""

        num_streams = 3
        num_frames = 5
        num_offset_samples = 2
        num_samples = num_frames * self.waveform_generator.samples_in_frame + num_offset_samples

        signal = np.exp(
            2j * self.rng.uniform(0, pi, (num_streams, 1))) @ np.exp(
                2j * self.rng.uniform(0, pi, (1, num_samples)))
        csi = ChannelStateInformation.Ideal(num_samples, num_streams)

        frames = self.synchronization.synchronize(signal, csi)
        self.assertEqual(num_frames, len(frames))
Пример #12
0
    def test_reference_based_channel_estimation(self) -> None:
        """Reference-based channel estimation should properly estimate channel at reference points."""

        self.generator.channel_estimation_algorithm = ChannelEstimation.REFERENCE

        expected_bits = self.rng.integers(0, 2, self.generator.bits_per_frame)
        expected_symbols = self.generator.map(expected_bits)
        signal = self.generator.modulate(expected_symbols)
        expected_csi = ChannelStateInformation.Ideal(signal.num_samples)

        symbols, csi, _ = self.generator.demodulate(signal.samples[0, :],
                                                    expected_csi)

        assert_array_almost_equal(np.ones(csi.state.shape, dtype=complex),
                                  csi.state)
    def test_rx_signal_properly_demodulated(self) -> None:
        """Verify the proper demodulation of received signals."""

        rx_signal = self.__read_saved_results_from_file('rx_signal.npy')
        channel_state = ChannelStateInformation.Ideal(
            num_samples=rx_signal.shape[0])
        noise_variance = 0.0

        received_symbols, _, _ = self.generator.demodulate(
            rx_signal, channel_state, noise_variance)
        received_bits = self.generator.unmap(received_symbols)

        received_bits_expected = self.__read_saved_results_from_file(
            'received_bits.npy').ravel()
        np.testing.assert_array_equal(
            received_bits[:len(received_bits_expected)],
            received_bits_expected)
Пример #14
0
    def test_decode_noiseless(self) -> None:
        """Decode should properly equalize the provided stream response in the absence of noise."""

        num_samples = 100
        num_streams = 4

        expected_symbols = np.exp(
            1j * self.generator.uniform(0, 2 * pi, (num_streams, num_samples)))
        stream_responses = np.exp(
            1j *
            self.generator.uniform(0, 2 * pi,
                                   (num_streams, num_streams, num_samples, 1)))
        channel_state = ChannelStateInformation(
            ChannelStateFormat.IMPULSE_RESPONSE, stream_responses)

        propagated_symbol_stream = np.empty((num_streams, num_samples),
                                            dtype=complex)
        expected_equalized_responses = np.zeros(
            (num_streams, num_streams, num_samples, 1), dtype=complex)
        for symbol_idx, (response, symbol) in enumerate(
                zip(stream_responses.transpose((2, 0, 1, 3)),
                    expected_symbols.T)):

            propagated_symbol_stream[:,
                                     symbol_idx] = response[:, :, 0] @ symbol
            expected_equalized_responses[:, :, symbol_idx,
                                         0] = np.identity(num_streams,
                                                          dtype=complex)

        stream_noises = np.zeros((num_streams, num_samples), dtype=float)

        equalized_symbols, equalized_csi, equalized_noises = self.precoder.decode(
            propagated_symbol_stream, channel_state, stream_noises)

        assert_array_almost_equal(expected_symbols, equalized_symbols)
        assert_array_almost_equal(expected_equalized_responses,
                                  equalized_csi.state)
        assert_array_almost_equal(
            equalized_noises, np.zeros((num_streams, num_samples),
                                       dtype=float))
    def demodulate(
        self, baseband_signal: np.ndarray,
        channel_state: ChannelStateInformation, noise_variance: float
    ) -> Tuple[Symbols, ChannelStateInformation, np.ndarray]:

        # Assess number of frames contained within this signal
        samples_in_chirp = self.samples_in_chirp
        samples_in_pilot_section = samples_in_chirp * self.num_pilot_chirps
        prototypes, _ = self._prototypes()

        data_frame = baseband_signal[samples_in_pilot_section:]

        symbol_signals = data_frame.reshape(-1, self.samples_in_chirp)
        symbol_metrics = abs(symbol_signals @ prototypes.T.conj())

        # ToDo: Unfortunately the demodulation-scheme is non-linear. Is there a better way?
        symbols = np.argmax(symbol_metrics, axis=1)
        channel_state = ChannelStateInformation.Ideal(
            len(symbols), channel_state.num_transmit_streams,
            channel_state.num_receive_streams)
        noises = np.repeat(noise_variance, self.num_data_chirps)

        return Symbols(symbols), channel_state, noises
Пример #16
0
    def test_decode_validation(self) -> None:
        """Decoding should result in a RuntimeError, if multiple streams result."""

        num_samples = 10
        num_streams = 2

        precoder = Mock()
        precoder.decode = lambda symbols, channels, noise: (symbols, channels,
                                                            noise)

        self.precoding[0] = precoder
        input_symbols = self.generator.random((num_streams, num_samples))
        input_channel = ChannelStateInformation.Ideal(
            num_samples=num_samples, num_receive_streams=num_streams)
        input_noise = self.generator.random((num_streams, num_samples))

        with self.assertRaises(RuntimeError):
            _ = self.precoding.decode(input_symbols, input_channel,
                                      input_noise)

        with self.assertRaises(ValueError):
            _ = self.precoding.decode(input_symbols[0, :], input_channel,
                                      input_noise)
Пример #17
0
    def receive(self) -> Tuple[Signal, Symbols, np.ndarray]:

        signal = self._receiver.signal.resample(self.waveform_generator.sampling_rate)
        if signal is None:
            raise RuntimeError("No signal received by modem")
            # signal = Signal.empty(sampling_rate=self.device.sampling_rate)

        csi = self._receiver.csi
        if csi is None:
            csi = ChannelStateInformation.Ideal(signal.num_samples)

        # Workaround for non-matching csi and signal model pairs
        elif signal.num_samples > (csi.num_samples + csi.num_delay_taps - 1):
            csi = ChannelStateInformation.Ideal(signal.num_samples)

        # Pull signal and channel state from the registered device slot
        noise_power = signal.noise_power
        num_samples = signal.num_samples

        # Number of frames within the received samples
        frames_per_stream = int(floor(num_samples / self.waveform_generator.samples_in_frame))

        # 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)

        # Data bits required by the bit encoder to generate the input bits for the waveform generator
        num_data_bits = self.encoder_manager.required_num_data_bits(num_code_bits)

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

        # Synchronize all streams into frames
        synchronized_frames = self.waveform_generator.synchronization.synchronize(signal.samples, csi)

        # Abort at this point if no frames have been detected
        if len(synchronized_frames) < 1:
            return signal, Symbols(), np.empty(0, dtype=complex)
        
        # Demodulate signal frame by frame
        decoded_raw_symbols = np.empty(0, dtype=complex)
        for frame_samples, frame_csi in synchronized_frames:
            
            stream_symbols: List[np.ndarray] = []
            stream_csis: List[ChannelStateInformation] = []
            stream_noises: List[np.ndarray] = []
            
            # Demodulate each stream within each frame independently
            for stream_samples, stream_csi in zip(frame_samples, frame_csi.received_streams()):
                
                symbols, csi, noise_powers = self.waveform_generator.demodulate(stream_samples, stream_csi, noise_power)
                stream_symbols.append(symbols.raw)
                stream_csis.append(csi)
                stream_noises.append(noise_powers)
                
            frame_symbols = np.array(stream_symbols, dtype=complex)
            frame_csi = ChannelStateInformation.concatenate(stream_csis,
                                                            ChannelStateDimension.RECEIVE_STREAMS)
            frame_noises = np.array(stream_noises, dtype=float)
            
            decoded_frame_symbols = self.precoding.decode(frame_symbols, frame_csi, frame_noises)
            decoded_raw_symbols = np.append(decoded_raw_symbols, decoded_frame_symbols)

        # Convert decoded symbols to from array to symbols
        decoded_symbols = Symbols(decoded_raw_symbols)

        # Map the symbols to code bits
        code_bits = self.waveform_generator.unmap(decoded_symbols)

        # Decode the coded bit stream to plain data bits
        data_bits = self.encoder_manager.decode(code_bits, num_data_bits)

        # Cache receptions
        self.__received_bits = data_bits
        self.__received_symbols = decoded_symbols

        # We're finally done, blow the fanfares, throw confetti, etc.
        return signal, decoded_symbols, data_bits
Пример #18
0
    def test_demodulate(self) -> None:
        """Demodulation should return an empty tuple."""

        _ = self.section.demodulate(np.empty(0, dtype=complex),
                                    ChannelStateInformation.Ideal(0))