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