def test_impulse_response_validation(self) -> None: """Impulse response routine must raise errors in case of unsupported scenarios.""" with self.assertRaises(RuntimeError): floating_channel = Channel() floating_channel.impulse_response(np.empty(0, dtype=complex), self.impulse_response_sampling_rate)
def test_receiver_setget(self) -> None: """Receiver property getter must return setter parameter.""" channel = Channel() channel.receiver = self.receiver self.assertIs(self.receiver, channel.receiver, "Receiver property set/get produced unexpected result")
def setUp(self) -> None: self.transmitter = Mock() self.receiver = Mock() self.active = True self.gain = 1.0 self.random_node = Mock() self.random_node._rng = default_rng(42) self.scenario = Mock() self.sampling_rate = 1e3 self.sync_offset_low = 0. self.sync_offset_high = 0. self.channel = Channel( transmitter=self.transmitter, receiver=self.receiver, active=self.active, gain=self.gain, sync_offset_low=self.sync_offset_low, sync_offset_high=self.sync_offset_high) self.channel.random_mother = self.random_node # Number of discrete-time samples generated for baseband_signal propagation testing self.propagate_signal_lengths = [1, 10, 100, 1000] self.propagate_signal_gains = [1.0, 0.5] # Number of discrete-time timestamps generated for impulse response testing self.impulse_response_sampling_rate = self.sampling_rate self.impulse_response_lengths = [1, 10, 100, 1000] # ToDo: Add 0 self.impulse_response_gains = [1.0, 0.5]
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))
def __init__(self, *args, interface: Optional[QuadrigaInterface] = None, **kwargs) -> None: """ Args: interface (Optional[QuadrigaInterface], optional): Specifies the consisdered Quadriga interface. Defaults to None. """ # Init base channel class Channel.__init__(self, *args, **kwargs) # Save interface settings self.__interface = interface # Register this channel at the interface self.__quadriga_interface.register_channel(self)
def test_ideal_channel_psk_qam(self) -> None: """Verify a valid MIMO link over an ideal channel with PSK/QAM modulation""" self.tx_operator.waveform_generator = WaveformGeneratorPskQam( oversampling_factor=8) self.rx_operator.waveform_generator = WaveformGeneratorPskQam( oversampling_factor=8) self.__propagate(Channel(self.tx_device, self.rx_device)) self.assertEqual(0, self.ber.evaluate().to_scalar())
def __propagate(self, channel: Channel) -> None: """Helper function to propagate a signal from transmitter to receiver. Args: channel (Channel): The channel over which to propagate the signal from transmitter to receiver. """ _ = self.tx_operator.transmit() tx_signals = self.tx_device.transmit() rx_signals, _, channel_state = channel.propagate(tx_signals) self.rx_device.receive( np.array([[rx_signals, channel_state]], dtype=object)) _ = self.rx_operator.receive()
def test_channel_base(self) -> None: LOW, HIGH = self.add_sync_offsets_to_params() ch = Channel(**self.channel_params) self.serialized_channel_contains_sync_offsets(ch, LOW, HIGH)
class TestChannel(unittest.TestCase): """Test the channel model base class.""" def setUp(self) -> None: self.transmitter = Mock() self.receiver = Mock() self.active = True self.gain = 1.0 self.random_node = Mock() self.random_node._rng = default_rng(42) self.scenario = Mock() self.sampling_rate = 1e3 self.sync_offset_low = 0. self.sync_offset_high = 0. self.channel = Channel( transmitter=self.transmitter, receiver=self.receiver, active=self.active, gain=self.gain, sync_offset_low=self.sync_offset_low, sync_offset_high=self.sync_offset_high) self.channel.random_mother = self.random_node # Number of discrete-time samples generated for baseband_signal propagation testing self.propagate_signal_lengths = [1, 10, 100, 1000] self.propagate_signal_gains = [1.0, 0.5] # Number of discrete-time timestamps generated for impulse response testing self.impulse_response_sampling_rate = self.sampling_rate self.impulse_response_lengths = [1, 10, 100, 1000] # ToDo: Add 0 self.impulse_response_gains = [1.0, 0.5] def test_init(self) -> None: """Test that the init properly stores all parameters.""" self.assertIs(self.transmitter, self.channel.transmitter, "Unexpected transmitter parameter initialization") self.assertIs(self.receiver, self.channel.receiver, "Unexpected receiver parameter initialization") self.assertEqual(self.active, self.channel.active, "Unexpected active parameter initialization") self.assertEqual(self.gain, self.channel.gain, "Unexpected gain parameter initialization") self.assertEqual(self.sync_offset_low, self.channel.sync_offset_low) self.assertEqual(self.sync_offset_high, self.channel.sync_offset_high) def test_active_setget(self) -> None: """Active property getter must return setter parameter.""" active = not self.active self.channel.active = active self.assertEqual(active, self.channel.active, "Active property set/get produced unexpected result") def test_transmitter_setget(self) -> None: """Transmitter property getter must return setter parameter.""" channel = Channel() channel.transmitter = self.transmitter self.assertIs(self.transmitter, channel.transmitter, "Transmitter property set/get produced unexpected result") def test_transmitter_validation(self) -> None: """Transmitter property setter must raise exception if already configured.""" with self.assertRaises(RuntimeError): self.channel.transmitter = Mock() def test_receiver_setget(self) -> None: """Receiver property getter must return setter parameter.""" channel = Channel() channel.receiver = self.receiver self.assertIs(self.receiver, channel.receiver, "Receiver property set/get produced unexpected result") def test_receiver_validation(self) -> None: """Receiver property setter must raise exception if already configured.""" with self.assertRaises(RuntimeError): self.channel.receiver = Mock() def test_sync_offset_low_setget(self) -> None: """Synchronization offset lower bound property getter should return setter argument.""" expected_sync_offset = 1.2345 self.channel.sync_offset_low = expected_sync_offset self.assertEqual(expected_sync_offset, self.channel.sync_offset_low) def test_sync_offset_low_validation(self) -> None: """Synchronization offset lower bound property setter should raise ValueError on negative arguments.""" with self.assertRaises(ValueError): self.channel.sync_offset_low = -1.0 try: self.channel.sync_offset_low = 0. except ValueError: self.fail() def test_sync_offset_high_setget(self) -> None: """Synchronization offset upper bound property getter should return setter argument.""" expected_sync_offset = 1.2345 self.channel.sync_offset_high = expected_sync_offset self.assertEqual(expected_sync_offset, self.channel.sync_offset_high) def test_sync_offset_high_validation(self) -> None: """Synchronization offset upper bound property setter should raise ValueError on negative arguments.""" with self.assertRaises(ValueError): self.channel.sync_offset_high = -1.0 try: self.channel.sync_offset_high = 0. except ValueError: self.fail() def test_gain_setget(self) -> None: """Gain property getter must return setter parameter.""" gain = 5.0 self.channel.gain = 5.0 self.assertIs(gain, self.channel.gain, "Gain property set/get produced unexpected result") def test_gain_validation(self) -> None: """Gain property setter must raise exception on arguments smaller than zero.""" with self.assertRaises(ValueError): self.channel.gain = -1.0 try: self.channel.gain = 0.0 except ValueError: self.fail("Gain property set to zero raised unexpected exception") def test_num_inputs_get(self) -> None: """Number of inputs property must return number of transmitting antennas.""" num_inputs = 5 self.transmitter.antennas.num_antennas = num_inputs self.assertEqual(num_inputs, self.channel.num_inputs, "Number of inputs property returned unexpected result") def test_num_inputs_validation(self) -> None: """Number of inputs property must raise RuntimeError if the channel is currently floating.""" floating_channel = Channel() with self.assertRaises(RuntimeError): _ = floating_channel.num_inputs def test_num_outputs_get(self) -> None: """Number of outputs property must return number of receiving antennas.""" num_outputs = 5 self.receiver.antennas.num_antennas = num_outputs self.assertEqual(num_outputs, self.channel.num_outputs, "Number of outputs property returned unexpected result") def test_num_outputs_validation(self) -> None: """Number of outputs property must raise RuntimeError if the channel is currently floating.""" floating_channel = Channel() with self.assertRaises(RuntimeError): _ = floating_channel.num_outputs 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) def test_propagate_SIMO(self) -> None: """Test valid propagation for the Single-Input-Multiple-Output channel.""" self.transmitter.antennas.num_antennas = 1 self.receiver.antennas.num_antennas = 3 for num_samples in self.propagate_signal_lengths: for gain in self.propagate_signal_gains: forwards_samples = (np.random.rand(1, num_samples) + 1j * np.random.rand(1, num_samples)) backwards_samples = np.random.rand(3, num_samples) + 1j * np.random.rand(3, 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.repeat(forwards_samples, 3, axis=0) expected_backwards_samples = gain * np.sum(backwards_samples, axis=0, keepdims=True) 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) 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) def test_propagate_MIMO(self) -> None: """Test valid propagation for the Multiple-Input-Multiple-Output channel.""" num_antennas = 3 self.transmitter.antennas.num_antennas = num_antennas self.receiver.antennas.num_antennas = num_antennas for num_samples in self.propagate_signal_lengths: for gain in self.propagate_signal_gains: samples = np.random.rand(num_antennas, num_samples) + 1j * np.random.rand(num_antennas, 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) 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)) def test_impulse_response_SISO(self) -> None: """Test the impulse response generation for the Single-Input-Single-Output case.""" self.transmitter.antennas.num_antennas = 1 self.receiver.antennas.num_antennas = 1 for response_length in self.impulse_response_lengths: for gain in self.impulse_response_gains: self.channel.gain = gain expected_impulse_response = gain * np.ones((response_length, 1, 1, 1), dtype=float) impulse_response = self.channel.impulse_response(response_length, self.impulse_response_sampling_rate) assert_array_equal(expected_impulse_response, impulse_response) def test_impulse_response_SIMO(self) -> None: """Test the impulse response generation for the Single-Input-Multiple-Output case.""" self.transmitter.antennas.num_antennas = 1 self.receiver.antennas.num_antennas = 3 for response_length in self.impulse_response_lengths: for gain in self.impulse_response_gains: self.channel.gain = gain expected_impulse_response = np.zeros((response_length, 3, 1, 1), dtype=complex) expected_impulse_response[:, :, 0, :] = gain impulse_response = self.channel.impulse_response(response_length, self.impulse_response_sampling_rate) assert_array_equal(expected_impulse_response, impulse_response) def test_impulse_response_MISO(self) -> None: """Test the impulse response generation for the Multiple-Input-Single-Output case.""" self.transmitter.antennas.num_antennas = 3 self.receiver.antennas.num_antennas = 1 for response_length in self.impulse_response_lengths: for gain in self.impulse_response_gains: self.channel.gain = gain expected_impulse_response = np.zeros((response_length, 1, 3, 1), dtype=complex) expected_impulse_response[:, 0, :, :] = gain impulse_response = self.channel.impulse_response(response_length, self.impulse_response_sampling_rate) assert_array_equal(expected_impulse_response, impulse_response) def test_impulse_response_MIMO(self) -> None: """Test the impulse response generation for the Multiple-Input-Multiple-Output case.""" num_antennas = 3 self.transmitter.antennas.num_antennas = num_antennas self.receiver.antennas.num_antennas = num_antennas for response_length in self.impulse_response_lengths: for gain in self.impulse_response_gains: self.channel.gain = gain expected_impulse_response = gain * np.tile(np.eye(num_antennas, num_antennas, dtype=complex), (response_length, 1, 1)) expected_impulse_response = np.expand_dims(expected_impulse_response, axis=-1) impulse_response = self.channel.impulse_response(response_length, self.impulse_response_sampling_rate) assert_array_equal(expected_impulse_response, impulse_response) def test_impulse_response_validation(self) -> None: """Impulse response routine must raise errors in case of unsupported scenarios.""" with self.assertRaises(RuntimeError): floating_channel = Channel() floating_channel.impulse_response(np.empty(0, dtype=complex), self.impulse_response_sampling_rate) 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 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 test_to_yaml(self) -> None: """Test YAML serialization dump validity.""" pass def test_from_yaml(self) -> None: """Test YAML serialization recall validity.""" pass
def test_num_outputs_validation(self) -> None: """Number of outputs property must raise RuntimeError if the channel is currently floating.""" floating_channel = Channel() with self.assertRaises(RuntimeError): _ = floating_channel.num_outputs
# Create two simulated devices acting as source and sink tx_device = SimulatedDevice() rx_device = SimulatedDevice() rx_device.analog_digital_converter = AnalogDigitalConverter(num_quantization_bits=10) # Define a transmit operation on the first device tx_operator = Modem() tx_operator.waveform_generator = WaveformGeneratorPskQam(oversampling_factor=8) tx_operator.device = tx_device # Define a receive operation on the second device rx_operator = Modem() rx_operator.waveform_generator = WaveformGeneratorPskQam(oversampling_factor=8) rx_operator.device = rx_device # Simulate a channel between the two devices channel = Channel(tx_operator.device, rx_operator.device) # Simulate the signal transmission over the channel tx_signal, _, tx_bits = tx_operator.transmit() rx_signal, _, channel_state = channel.propagate(tx_signal) rx_device.receive(rx_signal) _, rx_symbols, rx_bits = rx_operator.receive() # Evaluate bit errors during transmission and visualize the received symbol constellation evaluator = BitErrorEvaluator(tx_operator, rx_operator) evaluator.evaluate().plot() rx_symbols.plot_constellation() plt.show()