def setUp(self) -> None: self.range = 100. self.radar_cross_section = 1. self.random_generator = default_rng(42) self.random_node = Mock() self.random_node._rng = self.random_generator self.transmitter = Mock() self.transmitter.carrier_frequency = 1e9 self.transmitter.sampling_rate = 1e6 self.transmitter.antennas.num_antennas = 1 self.transmitter.antennas.spherical_response.return_value = np.array([1.], dtype=complex) self.transmitter.velocity = np.zeros(3, dtype=float) self.receiver = self.transmitter self.target_exists = True self.losses_db = 0 self.channel = RadarChannel(self.range, self.radar_cross_section, transmitter=self.transmitter, receiver=self.receiver, target_exists=self.target_exists, losses_db=self.losses_db) self.channel.random_mother = self.random_node self.expected_delay = 2 * self.range / speed_of_light
def test_to_yaml(self) -> None: """Test YAML serialization""" representer = Mock() _ = RadarChannel.to_yaml(representer, self.channel) representer.represent_mapping.assert_called()
def setUp(self) -> None: self.rng = default_rng(42) self.device = SimulatedDevice() self.device.carrier_frequency = 1e9 self.target_range = 5 self.max_range = 10 self.channel = RadarChannel(target_range=self.target_range, transmitter=self.device, receiver=self.device, radar_cross_section=1.) self.filter_type = 'ROOT_RAISED_COSINE' self.oversampling_factor = 16 self.modulation_order = 16 self.guard_interval = 1e-3 self.filter_length_in_symbols = 16 self.roll_off_factor = .9 self.tx_filter = ShapingFilter(filter_type=self.filter_type, samples_per_symbol=self.oversampling_factor, is_matched=False, length_in_symbols=self.filter_length_in_symbols, roll_off=self.roll_off_factor, bandwidth_factor=1.) self.rx_filter = ShapingFilter(filter_type=self.filter_type, samples_per_symbol=self.oversampling_factor, is_matched=True, length_in_symbols=self.filter_length_in_symbols, roll_off=self.roll_off_factor, bandwidth_factor=1.) self.operator = MatchedFilterJcas(self.max_range) self.operator.device = self.device self.operator.waveform_generator = WaveformGeneratorPskQam(oversampling_factor=self.oversampling_factor, num_preamble_symbols=20, num_data_symbols=100, tx_filter=self.tx_filter, rx_filter=self.rx_filter) self.operator.waveform_generator.synchronization = PskQamCorrelationSynchronization() self.operator.waveform_generator.channel_estimation = PskQamLeastSquaresChannelEstimation() self.operator.waveform_generator.channel_equalization = PskQamZeroForcingChannelEqualization()
def setUp(self) -> None: self.simulation = Simulation() self.device = self.simulation.scenario.new_device() self.device.carrier_frequency = 1e8 self.device.antennas = UniformArray(IdealAntenna(), .5 * speed_of_light / self.device.carrier_frequency, (3, 3)) self.waveform = FMCW() self.beamformer = ConventionalBeamformer() self.radar = Radar() self.radar.waveform = self.waveform self.radar.transmit_beamformer = self.beamformer self.radar.receive_beamformer = self.beamformer self.radar.device = self.device self.device.sampling_rate = self.radar.sampling_rate self.channel = RadarChannel(target_range=.5*self.waveform.max_range, radar_cross_section=1.) self.simulation.scenario.set_channel(self.device, self.device, self.channel)
class TestPskQamMatchedFilterJcas(TestCase): """Test matched filter sensing for psk/qam waveforms.""" def setUp(self) -> None: self.rng = default_rng(42) self.device = SimulatedDevice() self.device.carrier_frequency = 1e9 self.target_range = 5 self.max_range = 10 self.channel = RadarChannel(target_range=self.target_range, transmitter=self.device, receiver=self.device, radar_cross_section=1.) self.filter_type = 'ROOT_RAISED_COSINE' self.oversampling_factor = 16 self.modulation_order = 16 self.guard_interval = 1e-3 self.filter_length_in_symbols = 16 self.roll_off_factor = .9 self.tx_filter = ShapingFilter(filter_type=self.filter_type, samples_per_symbol=self.oversampling_factor, is_matched=False, length_in_symbols=self.filter_length_in_symbols, roll_off=self.roll_off_factor, bandwidth_factor=1.) self.rx_filter = ShapingFilter(filter_type=self.filter_type, samples_per_symbol=self.oversampling_factor, is_matched=True, length_in_symbols=self.filter_length_in_symbols, roll_off=self.roll_off_factor, bandwidth_factor=1.) self.operator = MatchedFilterJcas(self.max_range) self.operator.device = self.device self.operator.waveform_generator = WaveformGeneratorPskQam(oversampling_factor=self.oversampling_factor, num_preamble_symbols=20, num_data_symbols=100, tx_filter=self.tx_filter, rx_filter=self.rx_filter) self.operator.waveform_generator.synchronization = PskQamCorrelationSynchronization() self.operator.waveform_generator.channel_estimation = PskQamLeastSquaresChannelEstimation() self.operator.waveform_generator.channel_equalization = PskQamZeroForcingChannelEqualization() def test_jcas(self) -> None: """The target distance should be properly estimated while transmitting information.""" # Generate transmitted signal tx_signal, tx_symbols, tx_bits = self.operator.transmit() rf_signals = self.device.transmit() # Propagate signal over the radar channel propagetd_signals, _, _ = self.channel.propagate(rf_signals) self.device.receive(propagetd_signals) # Receive signal rx_signal, rx_symbols, rx_bits, radar_cube = self.operator.receive() # The bits should be recovered correctly assert_array_equal(tx_bits, rx_bits)
class FMCWRadarSimulation(TestCase): def setUp(self) -> None: self.simulation = Simulation() self.device = self.simulation.scenario.new_device() self.device.carrier_frequency = 1e8 self.device.antennas = UniformArray(IdealAntenna(), .5 * speed_of_light / self.device.carrier_frequency, (3, 3)) self.waveform = FMCW() self.beamformer = ConventionalBeamformer() self.radar = Radar() self.radar.waveform = self.waveform self.radar.transmit_beamformer = self.beamformer self.radar.receive_beamformer = self.beamformer self.radar.device = self.device self.device.sampling_rate = self.radar.sampling_rate self.channel = RadarChannel(target_range=.5*self.waveform.max_range, radar_cross_section=1.) self.simulation.scenario.set_channel(self.device, self.device, self.channel) def test_beamforming(self) -> None: """The radar channel target located should be estimated correctly by the beamformer""" self.radar.receive_beamformer.probe_focus_points = .25 * pi * np.array([[[0., 0.]], [[0., 1.]], [[1., 1.]], [[2., 1.]], [[3., 1.]], [[4., 1.]], [[5., 1.]], [[6., 1.]], [[7., 1.]]]) for angle_index, (azimuth, zenith) in enumerate(self.radar.receive_beamformer.probe_focus_points[:, 0, :]): # Configure the channel self.channel.target_azimuth = azimuth self.channel.target_zenith = zenith # Generate the radar cube self.radar.transmit() tx_signals = self.device.transmit() rx_signals, _, _ = self.channel.propagate(tx_signals) self.device.receive(rx_signals) cube, = self.radar.receive() directive_powers = np.linalg.norm(cube.data, axis=(1, 2)) self.assertEqual(angle_index, directive_powers.argmax()) def test_detection(self) -> None: self.radar.transmit() tx_signals = self.device.transmit() rx_signals, _, csi = self.channel.propagate(tx_signals) self.device.receive(rx_signals) cube, = self.radar.receive() expected_velocity_peak = 0 expected_range_peak = int(self.channel.target_range / self.waveform.range_resolution) range_profile = np.sum(cube.data, axis=(0, 1)) velocity_profile = np.sum(cube.data, axis=(0, 2)) self.assertEqual(expected_range_peak, np.argmax(range_profile)) self.assertEqual(expected_velocity_peak, np.argmax(velocity_profile))
class TestRadarChannel(unittest.TestCase): def setUp(self) -> None: self.range = 100. self.radar_cross_section = 1. self.random_generator = default_rng(42) self.random_node = Mock() self.random_node._rng = self.random_generator self.transmitter = Mock() self.transmitter.carrier_frequency = 1e9 self.transmitter.sampling_rate = 1e6 self.transmitter.antennas.num_antennas = 1 self.transmitter.antennas.spherical_response.return_value = np.array([1.], dtype=complex) self.transmitter.velocity = np.zeros(3, dtype=float) self.receiver = self.transmitter self.target_exists = True self.losses_db = 0 self.channel = RadarChannel(self.range, self.radar_cross_section, transmitter=self.transmitter, receiver=self.receiver, target_exists=self.target_exists, losses_db=self.losses_db) self.channel.random_mother = self.random_node self.expected_delay = 2 * self.range / speed_of_light def test_init(self) -> None: """The object initialization should properly store all parameters.""" self.assertIs(self.range, self.channel.target_range) self.assertIs(self.radar_cross_section, self.channel.radar_cross_section) self.assertIs(self.transmitter, self.channel.transmitter) self.assertIs(self.receiver, self.channel.receiver) self.assertIs(self.target_exists, self.channel.target_exists) self.assertIs(self.losses_db, self.channel.losses_db) def test_target_range_setget(self) -> None: """Target range property getter should return setter argument.""" new_range = 500 self.channel.target_range = new_range self.assertEqual(new_range, self.channel.target_range) def test_target_range_validation(self) -> None: """Target range property should raise ValueError on arguments smaller than zero""" with self.assertRaises(ValueError): self.channel.target_range = -1.12345 self.channel.target_range = 0. def test_target_exists_setget(self) -> None: """Target exists flag getter should return setter argument.""" new_target_exists = False self.channel.target_exists = new_target_exists self.assertEqual(new_target_exists, self.channel.target_exists) def test_radar_cross_section_get(self) -> None: """Radar cross section getter should return init param.""" self.assertEqual(self.radar_cross_section, self.channel.radar_cross_section) def test_cross_section_validation(self) -> None: """Radar cross section property should raise ValueError on arguments smaller than zero""" with self.assertRaises(ValueError): self.channel.radar_cross_section = -1.12345 self.channel.radar_cross_section = 0. def test_losses_db_get(self) -> None: """Losses getter should return init param.""" self.assertEqual(self.losses_db, self.channel.losses_db) def test_velocity_setget(self) -> None: """Velocity getter should return setter argument.""" new_velocity = 20 self.channel.target_velocity = new_velocity self.assertEqual(new_velocity, self.channel.target_velocity) def test_impulse_response_anchored_validation(self) -> None: """Impulse response should raise FloatingError if not anchored to a device""" with patch.object(RadarChannel, 'transmitter', None), self.assertRaises(FloatingError): _ = self.channel.impulse_response(0, 1.) def test_impulse_response_carrier_frequency_validation(self) -> None: """Impulse response should raise RuntimeError if device carrier frequencies are smaller or equal to zero""" self.transmitter.carrier_frequency = 0. with self.assertRaises(RuntimeError): _ = self.channel.impulse_response(0, 1.) def test_impulse_response_interference_validation(self) -> None: """Impulse response should raise RuntimeError if not configured as a self-interference channel""" with patch.object(RadarChannel, 'receiver', None), self.assertRaises(RuntimeError): _ = self.channel.impulse_response(0, 1.) def _create_impulse_train(self, interval_in_samples: int, number_of_pulses: int): interval = interval_in_samples / self.transmitter.sampling_rate number_of_samples = int(np.ceil(interval * self.transmitter.sampling_rate * number_of_pulses)) output_signal = np.zeros((1, number_of_samples), dtype=complex) interval_in_samples = int(np.around(interval * self.transmitter.sampling_rate)) output_signal[:, :number_of_samples:interval_in_samples] = 1.0 return output_signal def test_propagation_delay_integer_num_samples(self) -> None: """ Test if the received signal corresponds to the expected delayed version, given that the delay is a multiple of the sampling interval. """ samples_per_symbol = 1000 num_pulses = 10 delay_in_samples = 507 input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) expected_range = speed_of_light * delay_in_samples / self.transmitter.sampling_rate / 2 expected_amplitude = ((speed_of_light / self.transmitter.carrier_frequency) ** 2 * self.radar_cross_section / (4 * pi) ** 3 / expected_range ** 4) self.channel.target_range = expected_range output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) expected_output = np.hstack((np.zeros((1, delay_in_samples)), input_signal)) * expected_amplitude assert_array_almost_equal(abs(expected_output), np.abs(output[0].samples[:, :expected_output.size])) def test_propagation_delay_noninteger_num_samples(self) -> None: """ Test if the received signal corresponds to the expected delayed version, given that the delay falls in the middle of two sampling instants. """ samples_per_symbol = 800 num_pulses = 20 delay_in_samples = 312 input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) expected_range = speed_of_light * (delay_in_samples + .5) / self.transmitter.sampling_rate / 2 expected_amplitude = ((speed_of_light / self.transmitter.carrier_frequency) ** 2 * self.radar_cross_section / (4 * pi) ** 3 / expected_range ** 4) self.channel.target_range = expected_range output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) straddle_loss = np.sinc(.5) peaks = np.abs(output[0].samples[:, delay_in_samples:input_signal.size:samples_per_symbol]) assert_array_almost_equal(peaks, expected_amplitude * straddle_loss * np.ones(peaks.shape)) def test_propagation_delay_doppler(self) -> None: """ Test if the received signal corresponds to a frequency-shifted version of the transmitted signal with the expected Doppler shift """ samples_per_symbol = 50 num_pulses = 100 initial_delay_in_samples = 1000 expected_range = speed_of_light * initial_delay_in_samples / self.transmitter.sampling_rate / 2 velocity = 10 expected_amplitude = ((speed_of_light / self.transmitter.carrier_frequency) ** 2 * self.radar_cross_section / (4 * pi) ** 3 / expected_range ** 4) initial_delay = initial_delay_in_samples / self.transmitter.sampling_rate timestamps_impulses = np.arange(num_pulses) * samples_per_symbol / self.transmitter.sampling_rate traveled_distances = velocity * timestamps_impulses delays = initial_delay + 2 * traveled_distances / speed_of_light expected_peaks = timestamps_impulses + delays peaks_in_samples = np.around(expected_peaks * self.transmitter.sampling_rate).astype(int) straddle_delay = expected_peaks - peaks_in_samples / self.transmitter.sampling_rate relative_straddle_delay = straddle_delay * self.transmitter.sampling_rate expected_straddle_amplitude = np.sinc(relative_straddle_delay) * expected_amplitude input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) self.channel.target_range = expected_range self.channel.target_velocity = velocity output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) assert_array_almost_equal(np.abs(output[0].samples[0, peaks_in_samples].flatten()), expected_straddle_amplitude) def test_doppler_shift(self) -> None: """ Test if the received signal corresponds to the expected delayed version, given time variant delays on account of movement """ velocity = 100 self.transmitter.velocity = np.array([0., 0., .5 * velocity]) self.channel.target_velocity = .5 * velocity num_samples = 100000 sinewave_frequency = .25 * self.transmitter.sampling_rate doppler_shift = 2 * velocity / speed_of_light * self.transmitter.carrier_frequency time = np.arange(num_samples) / self.transmitter.sampling_rate input_signal = np.sin(2 * np.pi * sinewave_frequency * time) output, _, _ = self.channel.propagate(Signal(input_signal[np.newaxis, :], self.transmitter.sampling_rate)) input_freq = np.fft.fft(input_signal) output_freq = np.fft.fft(output[0].samples.flatten()[-num_samples:]) freq_resolution = self.transmitter.sampling_rate / num_samples freq_in = np.argmax(np.abs(input_freq[:int(num_samples/2)])) * freq_resolution freq_out = np.argmax(np.abs(output_freq[:int(num_samples/2)])) * freq_resolution self.assertAlmostEqual(freq_out - freq_in, doppler_shift, delta=np.abs(doppler_shift)*.01) def test_no_echo(self) -> None: """ Test if no echos are observed if target_exists is set to False """ samples_per_symbol = 500 num_pulses = 15 input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) self.channel.target_exists = False output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) assert_array_almost_equal(output[0].samples, np.zeros(output[0].samples.shape)) def test_to_yaml(self) -> None: """Test YAML serialization""" representer = Mock() _ = RadarChannel.to_yaml(representer, self.channel) representer.represent_mapping.assert_called()