Пример #1
0
    def test_transmit_receive(self) -> None:

        num_delay_samples = 10
        signal, _, _ = self.joint.transmit()

        delay_offset = Signal(np.zeros((1, num_delay_samples), dtype=complex),
                              signal.sampling_rate)
        delay_offset.append_samples(signal)

        self.joint._receiver.cache_reception(delay_offset)

        signal, _, _, cube = self.joint.receive()
        self.assertTrue(10, cube.data.argmax)
 def test_probe_validation(self) -> None:
     """Probe routine should raise exceptions on invalid configurations"""
     
     with self.assertRaises(RuntimeError):
         self.beamformer.probe(Signal(np.zeros((1, 10), dtype=complex), 1.))
         
     self.operator.device = None
     
     with self.assertRaises(FloatingError):
         self.beamformer.probe(Signal(np.zeros((2, 10), dtype=complex), 1.))
         
     self.beamformer.operator = None
     
     with self.assertRaises(FloatingError):
         self.beamformer.probe(Signal(np.zeros((2, 10), dtype=complex), 1.))
Пример #3
0
    def setUp(self) -> None:

        self.device = Mock()
        self.device.num_antennas = 1
        self.device.carrier_frequency = 0.
        self.waveform = Mock()
        self.waveform.frame_duration = 1e-5
        self.waveform.sampling_rate = speed_of_light
        self.waveform.bits_per_frame = 0
        self.waveform.symbols_per_frame = 0
        self.waveform.samples_in_frame = 5
        self.waveform.map.return_value = Symbols(np.empty(0, dtype=complex))
        self.waveform.unmap.return_value = np.empty(0, dtype=complex)
        self.waveform.modulate.return_value = Signal(
            np.ones((1, 5), dtype=complex),
            sampling_rate=self.waveform.sampling_rate)
        self.waveform.demodulate.return_value = Symbols(
            np.empty(0, dtype=complex)), ChannelStateInformation.Ideal(
                0, 1, 1), np.empty(0, dtype=float)
        self.waveform.synchronization.synchronize.return_value = [(np.ones(
            (1, 5), dtype=complex), ChannelStateInformation.Ideal(5))]

        self.range_resolution = 10

        self.joint = MatchedFilterJcas(max_range=10)
        self.joint.device = self.device
        self.joint.waveform_generator = self.waveform
Пример #4
0
    def convert(self, input_signal: Signal) -> Signal:
        output_signal = input_signal.copy()
        self.gain.multiply_signal(output_signal)
        output_signal.samples = self._quantize(output_signal.samples)
        self.gain.divide_signal(output_signal)

        return output_signal
Пример #5
0
    def test_doppler_shift(self) -> None:
        """A signal being propagated over the channel should be frequency shifted according to the doppler effect"""

        self.channel.num_clusters = 1
        self.receiver.velocity = np.array([10., 0., 0.])

        sampling_rate = 1e2
        signal_frequency = .25 * sampling_rate
        signal = np.outer(np.ones(4, dtype=complex),
                          np.exp(2j * pi * .25 * np.arange(200)))

        expected_doppler_shift = np.linalg.norm(
            self.receiver.velocity - self.transmitter.velocity
        ) * self.transmitter.carrier_frequency / speed_of_light
        frequency_resolution = sampling_rate / 200

        shifted_signal, _, _ = self.channel.propagate(
            Signal(signal, sampling_rate))

        input_freq = np.fft.fft(signal[0, :])
        output_freq = np.fft.fft(shifted_signal[0].samples[0, :].flatten())

        self.assertAlmostEqual(
            expected_doppler_shift,
            (np.argmax(output_freq) - np.argmax(input_freq)) *
            frequency_resolution,
            delta=1)
    def modulate(self, data_symbols: Symbols) -> Signal:

        frame = np.zeros(self.symbol_samples_in_frame, dtype=complex)

        # Set preamble symbols
        num_preamble_samples = self.oversampling_factor * self.num_preamble_symbols
        frame[:num_preamble_samples:self.
              oversampling_factor] = self.pilot_symbols(
                  self.num_preamble_symbols)

        # Set data symbols
        num_data_samples = self.oversampling_factor * self.__num_data_symbols
        data_start_idx = num_preamble_samples
        data_stop_idx = data_start_idx + num_data_samples
        frame[data_start_idx:data_stop_idx:self.
              oversampling_factor] = data_symbols.raw

        # Set postamble symbols
        num_postamble_samples = self.oversampling_factor * self.num_postamble_symbols
        postamble_start_idx = data_stop_idx
        postamble_stop_idx = postamble_start_idx + num_postamble_samples
        frame[postamble_start_idx:postamble_stop_idx:self.
              oversampling_factor] = 1.

        # Generate waveforms by treating the frame as a comb and convolving with the impulse response
        output_signal = self.tx_filter.filter(frame)
        return Signal(output_signal, self.sampling_rate)
    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_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_cdl(self):

        num_samples = 1000
        signal_samples = np.tile(np.exp(2j * pi * self.frequency * np.arange(num_samples) / self.sampling_rate),
                                 (1, 1))
        signal = Signal(signal_samples, sampling_rate=self.sampling_rate, carrier_frequency=self.carrier_frequency)

        reception_a, _, csi = self.channel.propagate(signal)
        samples = reception_a[0].samples

        num_angle_candidates = 80
        zenith_angles = np.linspace(0, pi, num_angle_candidates)
        azimuth_angles = np.linspace(0, 2 * pi, num_angle_candidates)

        dictionary = np.empty((self.antennas.num_antennas, num_angle_candidates ** 2), dtype=complex)
        for i, (aoa, zoa) in enumerate(product(azimuth_angles, zenith_angles)):

            dictionary[:, i] = self.device_a.antennas.spherical_response(self.frequency, aoa, zoa)

        beamformer = np.linalg.norm(dictionary.T @ samples, axis=1, keepdims=False).reshape((num_angle_candidates, num_angle_candidates))
        
        import matplotlib.pyplot as plt
        
        fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
        X, Y = np.meshgrid(azimuth_angles, zenith_angles)
        ax.pcolormesh(X, Y, beamformer.T, shading='nearest')
        ax.plot(azimuth_angles, zenith_angles, color='k', ls='none')
        ax.grid()
        #plt.show()
        return
 def test_receive(self) -> None:
     """Receive routine should correctly envoke the encode subroutine"""
     
     expected_signal = Signal(np.ones((2, 10), dtype=complex), 1.)
     focus = np.ones((2, self.beamformer.num_receive_focus_angles), dtype=float)
     
     steered_signal = self.beamformer.receive(expected_signal, focus)
     assert_array_equal(expected_signal.samples, steered_signal.samples)
 def test_probe(self) -> None:
     """Probe routine should correctly envoke the encode subroutine"""
     
     expected_samples = np.ones((2, 10), dtype=complex)
     expected_signal = Signal(expected_samples, 1.)
     focus = np.ones((1, 2, self.beamformer.num_receive_focus_angles), dtype=float)
     
     steered_signal = self.beamformer.probe(expected_signal, focus)
     assert_array_equal(expected_samples[np.newaxis, ::], steered_signal)
    def equalize_channel(
        self,
        signal: Signal,
        csi: ChannelStateInformation,
        snr: float = float('inf')) -> Signal:

        signal = signal.copy()
        signal.samples /= (csi.state[0, 0, :signal.num_samples, 0] + 1 / snr)

        return signal
    def pilot_signal(self) -> Signal:

        filter_delay = self.tx_filter.delay_in_samples
        pilot = np.zeros(filter_delay +
                         self.oversampling_factor * self.num_preamble_symbols,
                         dtype=complex)
        pilot[filter_delay::self.oversampling_factor] = self.pilot_symbols(
            self.num_preamble_symbols)

        return Signal(pilot, sampling_rate=self.sampling_rate)
Пример #14
0
    def test_receive_no_beamformer(self) -> None:
        """Receiving without a beamformer should result in a valid radar cube"""

        self.device.antennas.num_antennas = 1
        self.radar._receiver.cache_reception(
            Signal(np.zeros((1, 5)), self.waveform.sampling_rate))
        self.radar.receive_beamformer = None

        cube, = self.radar.receive()
        self.assertEqual(1, len(cube.angle_bins))
    def demodulate(
        self,
        baseband_signal: np.ndarray,
        channel_state: ChannelStateInformation,
        noise_variance: float = 0.
    ) -> Tuple[Symbols, ChannelStateInformation, np.ndarray]:

        # Filter the signal and csi
        filter_delay = self.tx_filter.delay_in_samples + self.rx_filter.delay_in_samples + 0
        filtered_signal = self.rx_filter.filter(baseband_signal)
        filter_states = np.zeros(
            (channel_state.state.shape[0], channel_state.state.shape[1],
             filter_delay, channel_state.state.shape[3]),
            dtype=complex)
        filter_states[:, :, :, 0] = 1.
        channel_state.state = np.append(filter_states,
                                        channel_state.state,
                                        axis=2)

        # Extract preamble symbols
        num_preamble_samples = self.oversampling_factor * self.num_preamble_symbols
        preamble_start_idx = filter_delay
        preamble_stop_idx = preamble_start_idx + num_preamble_samples
        # preamble = filtered_signal[preamble_start_idx:preamble_stop_idx:self.oversampling_factor]

        # Re-build signal model
        signal = Signal(filtered_signal, sampling_rate=self.sampling_rate)

        # Estimate the channel
        estimated_csi = self.channel_estimation.estimate_channel(
            signal, channel_state)

        # Equalize the signal
        snr = self.power / noise_variance if noise_variance > 0. else float(
            'inf')
        equalized_signal = self.channel_equalization.equalize_channel(
            signal, estimated_csi, snr)

        # Extract data symbols
        num_data_samples = self.oversampling_factor * self.__num_data_symbols
        data_start_idx = preamble_stop_idx
        data_stop_idx = data_start_idx + num_data_samples
        data = equalized_signal.samples[
            0, data_start_idx:data_stop_idx:self.oversampling_factor]
        data_state = estimated_csi[:, :, data_start_idx:data_stop_idx:self.
                                   oversampling_factor, :]

        # Extract postamble symbols
        # num_postamble_samples = self.oversampling_factor * self.num_postamble_symbols
        # postamble_start_idx = data_stop_idx
        # postamble_stop_idx = postamble_start_idx + num_postamble_samples
        # postamble = filtered_signal[postamble_start_idx:postamble_stop_idx:self.oversampling_factor]

        noise = np.repeat(noise_variance, len(data))
        return Symbols(data), data_state, noise
Пример #16
0
    def test_spatial_properties(self) -> None:
        """Direction of arrival estimation should result in the correct angle estimation of impinging devices"""

        self.channel.num_clusters = 1

        self.transmitter.antennas = UniformArray(
            IdealAntenna(), .5 * speed_of_light / self.carrier_frequency,
            (1, ))
        self.transmitter.orientation = np.zeros(3, dtype=float)
        self.receiver.position = np.zeros(3, dtype=float)
        self.receiver.antennas = UniformArray(
            IdealAntenna(), .5 * speed_of_light / self.carrier_frequency,
            (8, 8))

        angle_candidates = [
            (.25 * pi, 0),
            (.25 * pi, .25 * pi),
            (.25 * pi, .5 * pi),
            (.5 * pi, 0),
            (.5 * pi, .25 * pi),
            (.5 * pi, .5 * pi),
        ]
        range = 1e3

        steering_codebook = np.empty((8**2, len(angle_candidates)),
                                     dtype=complex)
        for a, (zenith, azimuth) in enumerate(angle_candidates):
            steering_codebook[:,
                              a] = self.receiver.antennas.spherical_response(
                                  self.carrier_frequency, azimuth, zenith)

        probing_signal = Signal(np.exp(2j * pi * .25 * np.arange(100)),
                                sampling_rate=1e3,
                                carrier_frequency=self.carrier_frequency)

        for a, (zenith, azimuth) in enumerate(angle_candidates):

            self.channel.set_seed(1)
            self.transmitter.position = range * np.array([
                cos(azimuth) * sin(zenith),
                sin(azimuth) * sin(zenith),
                cos(zenith)
            ],
                                                         dtype=float)

            received_signal, _, _ = self.channel.propagate(probing_signal)

            beamformer = np.linalg.norm(
                steering_codebook.T.conj() @ received_signal[0].samples,
                2,
                axis=1,
                keepdims=False)
            self.assertEqual(a, np.argmax(beamformer))
Пример #17
0
    def receive(self) -> Tuple[Signal, Symbols, np.ndarray, RadarCube]:

        # There must be a recent transmission being cached in order to correlate
        if self.__transmission is None:
            raise RuntimeError(
                "Receiving from a matched filter joint must be preceeded by a transmission"
            )

        # Receive information
        _, symbols, bits = Modem.receive(self)

        # Re-sample communication waveform
        signal = self._receiver.signal.resample(self.sampling_rate)

        resolution = self.range_resolution
        num_propagated_samples = int(2 * self.max_range / resolution)

        # Append additional samples if the signal is too short
        required_num_received_samples = self.__transmission.num_samples + num_propagated_samples
        if signal.num_samples < required_num_received_samples:
            signal.append_samples(
                Signal(
                    np.zeros(
                        (1,
                         required_num_received_samples - signal.num_samples),
                        dtype=complex), self.sampling_rate,
                    signal.carrier_frequency))

        # Remove possible overhead samples if signal is too long
        # resampled_signal.samples = resampled_signal.samples[:, :num_samples]

        correlation = abs(
            correlate(
                signal.samples,
                self.__transmission.samples,
                mode='valid',
                method='fft').flatten()) / self.__transmission.num_samples
        lags = correlation_lags(signal.num_samples,
                                self.__transmission.num_samples,
                                mode='valid')

        # Append zeros for correct depth estimation
        #num_appended_zeros = max(0, num_samples - resampled_signal.num_samples)
        #correlation = np.append(correlation, np.zeros(num_appended_zeros))

        angle_bins = np.array([0.])
        velocity_bins = np.array([0.])
        range_bins = .5 * lags * resolution
        cube_data = np.array([[correlation]], dtype=float)
        cube = RadarCube(cube_data, angle_bins, velocity_bins, range_bins)

        return signal, symbols, bits, cube
    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))
Пример #19
0
    def Propagate(signal: Signal, impulse_response: np.ndarray,
                  delay: float) -> Signal:
        """Propagate a single signal model given a specific channel impulse response.

        Args:

            signal (Signal):
                Signal model to be propagated.

            impulse_response (np.ndarray):
                The impulse response by which to propagate the signal model.

            delay (float):
                Additional delays, for example synchronization offsets.

        Returns:

            propagated_signal (Signal):
                Propagated signal model.
        """

        # The maximum delay in samples is modeled by the last impulse response dimension
        num_signal_samples = signal.num_samples
        num_delay_samples = impulse_response.shape[3] - 1
        num_tx_streams = impulse_response.shape[2]
        num_rx_streams = impulse_response.shape[1]

        # Propagate the signal
        propagated_samples = np.zeros((impulse_response.shape[1],
                                       signal.num_samples + num_delay_samples),
                                      dtype=complex)

        for delay_index in range(num_delay_samples + 1):
            for tx_idx, rx_idx in product(range(num_tx_streams),
                                          range(num_rx_streams)):

                delayed_signal = impulse_response[:num_signal_samples, rx_idx,
                                                  tx_idx,
                                                  delay_index] * signal.samples[
                                                      tx_idx, :]
                propagated_samples[rx_idx, delay_index:delay_index +
                                   num_signal_samples] += delayed_signal

        return Signal(propagated_samples,
                      sampling_rate=signal.sampling_rate,
                      carrier_frequency=signal.carrier_frequency,
                      delay=signal.delay + delay)
Пример #20
0
    def receive(self,
                signal: Signal,
                focus_points: Optional[np.ndarray] = None,
                focus_mode: FocusMode = FocusMode.SPHERICAL) -> Signal:
        """Focus a signal model towards a certain target.
        
        Args:
        
            signal (Signal):
                The signal to be steered.
                
            focus_points (np.ndarray, optional):
                Focus point of the steered signal power.
                Two-dimensional numpy array with the first dimension representing the number of points
                and the second dimension representing the point values.
        
            focus_mode (FocusMode, optional):
                Type of focus points.
                By default, spherical coordinates are expected.
        
        Returns:
        
            Signal focused towards the requested focus points.
        """

        if self.operator is None:
            raise FloatingError(
                "Unable to steer a signal over a floating beamformer")

        if self.operator.device is None:
            raise FloatingError(
                "Unable to steer a signal over a floating operator")

        if signal.num_streams != self.num_receive_input_streams:
            raise RuntimeError(
                f"The provided signal contains {signal.num_streams}, but the beamformer requires {self.num_receive_input_streams} streams"
            )

        carrier_frequency = signal.carrier_frequency
        samples = signal.samples.copy()
        focus_angles = self.receive_focus[0][
            np.newaxis, ::] if focus_points is None else focus_points[
                np.newaxis, ::]

        beamformed_samples = self._decode(samples, carrier_frequency,
                                          focus_angles)
        return Signal(beamformed_samples[0, ::], signal.sampling_rate)
Пример #21
0
    def test_synchronize(self) -> None:
        """Synchronization should properly order pilot sections into frames"""

        pilot_sequence = Signal(np.ones(20, dtype=complex), 1.)

        waveform_generator = Mock()
        waveform_generator.pilot_signal = pilot_sequence
        waveform_generator.samples_in_frame = 20
        self.synchronization.waveform_generator = waveform_generator

        shifted_sequence = np.append(np.zeros((1, 10), dtype=complex), pilot_sequence.samples, axis=1)
        shifted_csi = ChannelStateInformation.Ideal(30)

        frames = self.synchronization.synchronize(shifted_sequence, shifted_csi)

        self.assertEqual(1, len(frames))
        assert_array_equal(pilot_sequence.samples, frames[0][0])
Пример #22
0
    def transmit(self, duration: float = 0.) -> Tuple[Signal]:

        if not self.waveform:
            raise RuntimeError("Radar waveform not specified")

        if not self.device:
            raise RuntimeError(
                "Error attempting to transmit over a floating radar operator")

        # Generate the radar waveform
        signal = self.waveform.ping()

        # If the device has more than one antenna, a beamforming strategy is required
        if self.device.antennas.num_antennas > 1:

            # If no beamformer is configured, only the first antenna will transmit the ping
            if self.transmit_beamformer is None:

                additional_streams = Signal(
                    np.zeros((self.device.antennas.num_antennas -
                              signal.num_streams, signal.num_samples),
                             dtype=complex), signal.sampling_rate)
                signal.append_streams(additional_streams)

            elif self.transmit_beamformer.num_transmit_input_streams != 1:
                raise RuntimeError(
                    "Only transmit beamformers requiring a single input stream are supported by radar operators"
                )

            elif self.transmit_beamformer.num_transmit_output_streams != self.device.antennas.num_antennas:
                raise RuntimeError(
                    "Radar operator transmit beamformers are required to consider the full number of antennas"
                )

            else:
                signal = self.transmit_beamformer.transmit(signal)

        # Transmit signal over the occupied device slot (if the radar is attached to a device)
        if self._transmitter.attached:
            self._transmitter.slot.add_transmission(self._transmitter, signal)

        return 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]))
Пример #24
0
    def transmit(self,
                 signal: Signal,
                 focus: Optional[np.ndarray] = None) -> Signal:
        """Focus a signal model towards a certain target.
        
        Args:
        
            signal (Signal):
                The signal to be steered.
                
            focus (np.ndarray, optional):
                Focus point of the steered signal power.
        
        Returns:
        
            Samples of the focused signal.
        """

        if self.operator is None:
            raise FloatingError(
                "Unable to steer a signal over a floating beamformer")

        if self.operator.device is None:
            raise FloatingError(
                "Unable to steer a signal over a floating operator")

        if signal.num_streams != self.num_transmit_input_streams:
            raise RuntimeError(
                f"The provided signal contains {signal.num_streams}, but the beamformer requires {self.num_transmit_input_streams} streams"
            )

        carrier_frequency = signal.carrier_frequency
        samples = signal.samples.copy()
        focus = self.transmit_focus[0] if focus is None else focus

        steered_samples = self._encode(samples, carrier_frequency, focus)
        return Signal(steered_samples,
                      sampling_rate=signal.sampling_rate,
                      carrier_frequency=signal.carrier_frequency)
    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))
Пример #26
0
    def receive(self,
                device_signals: Union[List[Signal], np.ndarray],
                snr: Optional[float] = None,
                snr_type: SNRType = SNRType.EBN0) -> Signal:
        """Receive signals at this device.

        Args:

            device_signals (Union[List[Signal], np.ndarray]):
                List of signal models arriving at the device.
                May also be a two-dimensional numpy object array where the first dimension indicates the link
                and the second dimension contains the transmitted signal as the first element and the link channel
                as the second element.

            snr (float, optional):
                Signal to noise power ratio.
                Infinite by default, meaning no noise will be added to the received signals.

            snr_type (SNRType, optional):
                Type of signal to noise ratio.

        Returns:

            baseband_signal (Signal):
                Baseband signal sampled after hardware-modeling.
        """

        # Default to the device's configured SNR if no snr argument was provided
        snr = self.snr if snr is None else snr

        # Mix arriving signals
        mixed_signal = Signal.empty(sampling_rate=self.sampling_rate, num_streams=self.num_antennas,
                                    num_samples=0, carrier_frequency=self.carrier_frequency)

        # Tranform list arguments to matrix arguments
        if isinstance(device_signals, list):
            
            propagation_matrix = np.empty(1, dtype=object)
            propagation_matrix[0] = (device_signals, None)
            device_signals = propagation_matrix

        # Superimpose transmit signals
        for signals, _ in device_signals:

            if signals is not None:
                for signal in signals:
                    mixed_signal.superimpose(signal)

        # Model radio-frequency chain during transmission
        baseband_signal = self.rf_chain.receive(mixed_signal)

        baseband_signal = self.adc.convert(baseband_signal)
        
        # Cache received signal at receiver slots
        for receiver in self.receivers:

            # Collect the reference channel if a reference transmitter has been specified
            if receiver.reference_transmitter is not None and self.attached:

                reference_device = receiver.reference_transmitter.device
                reference_device_idx = self.scenario.devices.index(reference_device)

                reference_csi = device_signals[reference_device_idx][1] if isinstance(device_signals[reference_device_idx], (tuple, list, np.ndarray)) else None

                if self.operator_separation:

                    reference_transmitter_idx = receiver.slot_index
                    receiver_signal = device_signals[reference_device_idx][0][reference_transmitter_idx].copy()

                else:
                    receiver_signal = baseband_signal.copy()

            else:

                reference_csi = None
                receiver_signal = baseband_signal.copy()

            # Add noise to the received signal according to the selected ratio
            noise_power = receiver.energy / snr
            self.__noise.add(receiver_signal, noise_power)

            # Cache reception
            receiver.cache_reception(receiver_signal, reference_csi)

        return baseband_signal
Пример #27
0
 def multiply_signal(self, input_signal: Signal) -> None:
     input_signal.samples = input_signal.samples * self.gain
Пример #28
0
 def divide_signal(self, input_signal: Signal) -> None:
     input_signal.samples = input_signal.samples / self.gain
Пример #29
0
    def receive(self) -> Tuple[RadarCube]:

        if not self.waveform:
            raise RuntimeError("Radar waveform not specified")

        if not self.device:
            raise RuntimeError(
                "Error attempting to transmit over a floating radar operator")

        # Retrieve signal from receiver slot
        signal = self._receiver.signal.resample(self.waveform.sampling_rate)

        # If the device has more than one antenna, a beamforming strategy is required
        if self.device.antennas.num_antennas > 1:

            if self.receive_beamformer is None:
                raise RuntimeError(
                    "Receiving over a device with more than one antenna requires a beamforming configuration"
                )

            if self.receive_beamformer.num_receive_output_streams != 1:
                raise RuntimeError(
                    "Only receive beamformers generating a single output stream are supported by radar operators"
                )

            if self.receive_beamformer.num_receive_input_streams != self.device.antennas.num_antennas:
                raise RuntimeError(
                    "Radar operator receive beamformers are required to consider the full number of antenna streams"
                )

            beamformed_samples = self.receive_beamformer.probe(signal)[:, 0, :]

        else:

            beamformed_samples = signal.samples

        # Build the radar cube by generating a beam-forming line over all angles of interest
        angles_of_interest = np.array(
            [[0., 0.]], dtype=float
        ) if self.receive_beamformer is None else self.receive_beamformer.probe_focus_points[:,
                                                                                             0, :]

        range_bins = self.waveform.range_bins
        velocity_bins = self.waveform.velocity_bins

        cube_data = np.empty(
            (len(angles_of_interest), len(velocity_bins), len(range_bins)),
            dtype=float)

        for angle_idx, line in enumerate(beamformed_samples):

            # Process the single angular line by the waveform generator
            line_signal = Signal(line,
                                 signal.sampling_rate,
                                 carrier_frequency=signal.carrier_frequency)
            line_estimate = self.waveform.estimate(line_signal)

            cube_data[angle_idx, ::] = line_estimate

        # Create radar cube object
        cube = RadarCube(cube_data, angles_of_interest, velocity_bins,
                         range_bins)

        return cube,
Пример #30
0
    def ping(self) -> Signal:

        return Signal(
            np.exp(2j * np.pi *
                   self.rng.uniform(0, 1, size=(1, self.num_samples))),
            self.sampling_rate)