Example #1
0
class Simulator(object):
    def __init__(self):
        self.wind_speed = 5.0  # default wind speed
        self.coherent_pow_flg = True  # add coherent power
        self.coh_integrate_time = 0.001  # coherent integrateed time
        self.num_angles = 360  # integrated angle resolution
        self.interface_flg = 1  # curvature surface
        self.ddm_cov_factor = 5  # cov mode factor for ddm
        # # atmosphere loss for signal propagation 0.5 dB
        self.atmospheric_loss = pow(10.0, (0.5 / 10.0))
        # # members
        self.geometry = Geometry()
        self.nadir_RF = Antenna()
        self.zenith_RF = Antenna()
        self.signal = Signal()
        self.power_waveform = Waveform(True, 1000)
        self.interface = Interface()

    def plot_delay_waveform(self, flg=False):
        """ plot delay simulated waveform """
        if flg:
            waveform = self.integrate_waveform
            title = "Integrated waveform"
        else:
            waveform = self.waveform
            title = "Delay waveform"
        noise_level = waveform[0]
        plt.figure()
        plt.plot(self.wave_range / 1000.0,
                 10.0 * np.log10(waveform / noise_level), '*-')
        plt.grid()
        plt.title(title)
        plt.xlabel("Range from specular [km]")
        plt.ylabel("SNR [dB]")
        plt.ylim(0, 5)
        plt.xlim(-1.0, 5.0)
        plt.tight_layout()
        plt.show()

    def plot_power_ddm(self):
        """ plot scattered power DDM """
        plt.figure(figsize=(4, 3))
        noise_level = self.ddm.min()
        extent = [
            self.wave_range[0] / 1000.0, self.wave_range[-1] / 1000.0,
            -self.dopp_bin * self.center_dopp / 1000.0,
            self.dopp_bin * self.center_dopp / 1000.0
        ]
        plt.imshow(self.ddm,
                   extent=extent,
                   vmin=noise_level,
                   vmax=max(self.ddm[self.center_dopp, :]),
                   cmap='jet',
                   aspect='auto')
        plt.title("Scattered power DDM")
        plt.xlabel("Range from specular [km]")
        plt.ylabel("Doppler [kHz]")
        plt.colorbar()
        plt.tight_layout()
        plt.show()

    def plot_scattered_area(self, flg=True):
        """ plot scattered area """
        if flg:
            sca_area = self.eff_area
            title = "Effective scatter area"
        else:
            sca_area = self.phy_area
            title = "Physical scatter area"
        plt.figure(figsize=(4, 3))
        noise_level = sca_area.min()
        max_pow = max(sca_area[self.center_dopp, :])
        extent = [
            self.wave_range[0] / 1000.0, self.wave_range[-1] / 1000.0,
            -self.dopp_bin * self.center_dopp / 1000.0,
            self.dopp_bin * self.center_dopp / 1000.0
        ]
        plt.imshow(sca_area,
                   extent=extent,
                   vmin=noise_level,
                   vmax=max_pow,
                   cmap='jet',
                   aspect='auto')
        plt.title(title)
        plt.xlabel("Range from specular [km]")
        plt.ylabel("Doppler [kHz]")
        plt.colorbar()
        plt.tight_layout()
        plt.show()

    ###########################################################################
    def compute_direct_power(self, transmit_pow):
        """ compute direct signal power """
        # # receiver body frame
        bf_e, bf_h, bf_k = self.zenith_RF.get_antenna_bf()
        # # receiver body frame to specular
        self.geometry.BF2spfs(bf_e, bf_h, bf_k)
        scat_vec, rang = self.geometry.compute_r2t_vector()
        angles = self.geometry.compute_antenna_gain_pos(scat_vec)
        directivity = pow(10.0,
                          (self.nadir_RF.get_receiver_gain(angles)) / 10.0)
        tmp = const.C_LIGHT / (self.signal.freq * 4.0 * const.PI * rang)
        self.direct_pow = pow(tmp, 2)
        self.direct_pow *= transmit_pow * directivity / self.atmospheric_loss

    def correlate_direct_signal(self):
        sin_arg = 2 * self.zenith_RF.filter_bb_bw / self.sampling_rate
        sin_arg *= np.arange(0, self.range_samples_len)
        sin_arg[sin_arg == 0.0] = 1.0
        sin_arg = np.sinc(sin_arg)
        self.nd_nr_cov = abs(sin_arg)
        self.sd_nr_cov = np.convolve(sin_arg,
                                     self.signal.lambda_fun,
                                     mode="same")
        self.sd_nr_cov = np.abs(self.sd_nr_cov)
        max_value = self.sd_nr_cov.max()
        if max_value > 0.0:
            self.sd_nr_cov = self.sd_nr_cov / max_value
        for i in range(self.dopps):
            power = self.power[:, i]
            pass

    ###########################################################################
    def compute_coherent_power(self, scat_vec):
        """
        compute coherent power at scat_vec diretion, commonly coherent
        relection accur at specular point
        """
        # # compute receiver antenna gain at scat_vec diretion
        # # receiver body frame
        bf_e, bf_h, bf_k = self.nadir_RF.get_antenna_bf()
        # # receiver body frame to specular
        self.geometry.BF2spfs(bf_e, bf_h, bf_k)
        angles = self.geometry.compute_antenna_gain_pos(scat_vec)
        directivity = pow(10.0,
                          (self.nadir_RF.get_receiver_gain(angles)) / 10.0)
        # # specular point sinc function
        self.cosi = cos(self.geometry.tx_inc)
        self.tani = tan(self.geometry.tx_inc)
        self.sinc_dopps = np.zeros(self.dopps)
        sinc_arg = self.dopp_bin * self.coh_integrate_time
        if sinc_arg == 0.0:
            self.sinc_dopps[self.center_dopp] = 1.0
        else:
            self.sinc_dopps[self.center_dopp] = pow(np.sinc(sinc_arg), 2)
        # # compute fresnel coefficients
        self.interface.compute_fresnel(self.geometry.tx_inc)
        # # get corherent power
        tmp = 4.0 * const.PI * self.interface.sigma_z * self.cosi
        coherent_pow = self.signal.trans_pow * self.interface.fresnel
        coherent_pow *= exp(-1.0 * pow(tmp / self.signal.wavelen, 2))
        tmp = const.C_LIGHT * self.coh_integrate_time
        tmp /= (4.0 * const.PI * self.signal.freq)
        tmp /= (norm(self.geometry.tx_spf) + norm(self.geometry.rx_spf))
        coherent_pow *= directivity * self.sinc_dopps[self.center_dopp]
        coherent_pow *= pow(tmp, 2) / self.atmospheric_loss
        self.coherent_pow = coherent_pow

    def set_pow_waveform(self, init_range, sampling_rate):
        """ set power waveform for delays computation """
        self.power_waveform.sampling_rate = sampling_rate
        self.power_waveform.set_init_range(init_range)

    def set_nadir_antenna(self,
                          gain,
                          temp,
                          figure,
                          filter_bb_bw,
                          antenna_flg=True):
        """
        set antenna attitude information for receiver antenna gain
        computation
        """
        self.nadir_RF.set_receiver(gain, temp, figure, filter_bb_bw,
                                   antenna_flg)

    def set_radar_signal(self, sampling_rate, filter_bb_bw, exponent):
        """ initailze the bistatic radar signal """
        self.signal.set_radar_signal(sampling_rate, filter_bb_bw)
        # # compute corelation function of WAF
        self.signal.compute_lambda()
        self.isotropic_factor = (self.signal.wavelen**2) / (4.0 * const.PI)**3
        self.dt = const.C_LIGHT / sampling_rate  # just for computation later
        # # compute the transmit power
        ele = const.PI / 2 - self.geometry.tx_inc
        self.signal.compute_transmit_power(self.signal, ele)

    def set_interface(self, wind_speed):
        self.interface.ws = wind_speed
        self.interface.set_polarization(self.polar_mode)

    def configure_radar_geometry(self, tx, tv, rx, rv, undulation_flg=True):
        """
        set bistatic radar configuration, need the ecef postion and velocity
        of transimiter and receiver, compute specular point postion. function
        can also account for the undualtion of geoid
        """
        self.geometry.tx_pos = np.asarray(tx)
        self.geometry.tx_vel = np.asarray(tv)
        self.geometry.rx_pos = np.asarray(rx)
        self.geometry.rx_vel = np.asarray(rv)
        # # compute the specular point
        self.geometry.compute_sp_pos(undulation_flg)

    def earth_curvature_appro(self, tau, x):
        """ modified surface glisten zone coordinations for earth curvature """
        rad = norm(x[:2])
        az = atan2(x[1], x[0])
        x[2] = sqrt(const.RE_WGS84**2 - rad**2) - const.RE_WGS84
        rr = norm(self.geometry.rx_spf - x)
        rt = norm(self.geometry.tx_spf - x)
        delay = rr + rt - self.geometry.rrt - self.sp_delay
        rad *= sqrt(tau / delay)
        x[0] = rad * cos(az)
        x[1] = rad * sin(az)
        x[2] = sqrt(const.RE_WGS84**2 - rad**2) - const.RE_WGS84

    def compute_sinc(self, dopp):
        """ compute doppler sinc function """
        sinc_arg = (np.arange(self.dopps) - self.center_dopp) * self.dopp_bin
        sinc_arg = (dopp - sinc_arg) * self.coh_integrate_time
        ind = sinc_arg != 0.0
        self.sinc_dopps[ind] = pow(np.sinc(sinc_arg[ind]), 2)
        self.sinc_dopps[~ind] = 1.0

    def delay_integration(self, tau, a, c, delta):
        """
        integration points over the surface ellipse for each range sample
        """
        x = np.zeros(3)
        pow_tau = np.zeros(self.dopps)
        phy_area = np.zeros(self.dopps)
        eff_area = np.zeros(self.dopps)
        left_side = -1.0 * (self.center_dopp + 0.5) * self.dopp_bin
        for i in range(self.num_angles):
            # # surface point calculation
            theta = i * delta
            x[0] = a * self.cosi * cos(theta)
            x[1] = a * sin(theta) + c
            # # surface point earth curvature modified
            if self.interface_flg == 1:
                self.earth_curvature_appro(tau, x)
            # # surface point scatter vector and scatter area
            inc_vec, sca_vec, jacob, coeff = self.geometry.compute_scattering_vector(
                x)
            # # surface point relative doppler shift to the specular point
            dopp = self.geometry.doppler_shift(inc_vec, sca_vec,
                                               self.signal.freq) - self.sp_dopp
            # if self.dopps % 2 == 0:
            #     dopp -= self.dopp_bin
            # # sinc function
            self.compute_sinc(dopp)
            # # reflected coeffcient
            simga0 = self.interface.compute_scattered_coeff(
                inc_vec, sca_vec, self.geometry.tx_az)
            # # receicer antenna gain at the surface point direction
            angles = self.geometry.compute_antenna_gain_pos(inc_vec)
            rev_gain_db = self.nadir_RF.get_receiver_gain(angles)
            directivity = pow(10.0, rev_gain_db / 10.0)
            # # a factor for correlation calculation
            factor = directivity * self.isotropic_factor * simga0 * jacob * coeff
            if i == 0:
                # # restore the first surface point
                fx = np.copy(x[:2])
                px = np.copy(x[:2])  # the former point relative to current one
                fst_dopp = pre_dopp = dopp
                fst_jac = pre_jac = jacob
                fst_ft = factor * self.sinc_dopps
                pre_ft = fst_ft.copy()
                fst_area = jacob * self.sinc_dopps
                pre_area = fst_area.copy()
                continue
            diff_ang = abs(atan2(x[1], x[0]) - atan2(px[1], px[0]))
            new_theta = min(diff_ang, 2.0 * const.PI - diff_ang)
            px = np.copy(x[:2])
            tmp = factor * self.sinc_dopps
            area = jacob * self.sinc_dopps
            pow_tau += new_theta * (tmp + pre_ft) / 2.0  # accumulate the power
            ind = int(((dopp + pre_dopp) / 2.0 - left_side) // self.dopp_bin)
            if (ind >= 0) and (ind < self.dopps):
                phy_area[ind] += (jacob + pre_jac) / 2.0 * new_theta
            eff_area += new_theta * (area + pre_area) / 2.0
            pre_dopp = dopp
            pre_jac = jacob
            pre_ft = tmp.copy()
            pre_area = area.copy()
            if i == self.num_angles - 1:
                # # intergration to finish the whole ellipse, connect
                # # the last point to the first point
                diff_ang = abs(atan2(fx[1], fx[0]) - atan2(x[1], x[0]))
                new_theta = min(diff_ang, 2.0 * const.PI - diff_ang)
                pow_tau += new_theta * (fst_ft +
                                        tmp) / 2.0  # accumulate the power
                ind = int(
                    ((dopp + fst_dopp) / 2.0 - left_side) // self.dopp_bin)
                if (ind >= 0) and (ind < self.dopps):
                    phy_area[ind] += (jacob + fst_jac) / 2.0 * new_theta
                eff_area += new_theta * (fst_area + area) / 2.0
        return pow_tau, phy_area, eff_area

    def compute_noise_floor(self):
        """ compute noise floor """
        eta_factor = 1.0
        self.noise_floor = eta_factor * pow(10.0,
                                            self.nadir_RF.noise_power / 10.0)
        self.noise_floor /= self.nadir_RF.filter_bb_bw * self.coh_integrate_time

    def compute_power_waveform(self, ind):
        """ get lambda function """
        lam_len = self.signal.lambda_size
        half_lam = lam_len // 2
        waveform_conv = np.convolve(self.power[:, ind],
                                    self.signal.lambda_fun,
                                    mode="same")
        area_conv = np.convolve(self.dis_eff_area[:, ind],
                                self.signal.lambda_fun,
                                mode="same")
        # # compute delay power waveform
        tmp = waveform_conv[half_lam:half_lam + self.delays]
        tmp *= self.signal.trans_pow * self.dt / self.atmospheric_loss
        tmp += self.noise_floor
        if lam_len > self.delays:
            lam_len = self.delays
        tmp[:lam_len] += self.coherent_pow * self.signal.lambda_fun[:lam_len]
        return abs(tmp), area_conv[half_lam:half_lam + self.delays]

    def compute_ddm(self):
        """ compute ddm of scattered surface """
        half_lam = self.signal.lambda_size // 2
        self.ddm = np.zeros((self.dopps, self.delays))
        self.eff_area = np.zeros((self.dopps, self.delays))
        self.phy_area = self.dis_phy_area[half_lam:half_lam + self.delays,
                                          range(self.dopps)].T
        # # zero doppler shift delay waveform
        for i in range(self.dopps):
            sca_pow, eff_area = self.compute_power_waveform(i)
            self.ddm[i, :] = sca_pow
            self.eff_area[i, :] = eff_area
        # # integrated waveform
        self.integrate_waveform = self.ddm.sum(axis=0)
        if not hasattr(self, "wave_range"):
            self.power_waveform.set_waveform(self.ddm[self.center_dopp, :])
            self.power_waveform.compute_waveform_delays()
            self.wave_range = self.power_waveform.get_range_waveform()
            self.wave_range -= self.geometry.geometric_delay

    def compute_center_waveform(self):
        """ compute delay power waveform  """
        self.compute_noise_floor()
        self.waveform, _ = self.compute_power_waveform(self.center_dopp)
        # # compute spatical delays of delay waveform in meters
        self.power_waveform.set_waveform(self.waveform)
        self.power_waveform.compute_waveform_delays()
        self.wave_range = self.power_waveform.get_range_waveform()
        self.wave_range -= self.geometry.geometric_delay

    def compute_power_distribution(self):
        """
        Computation power distribution over reflecting surface
        origin located at sample = gnss_signal.lambda_size
        """
        # # signal correlation starting postion
        lam_len = self.signal.lambda_size
        end = self.range_samples_len - lam_len
        self.power = np.zeros((self.range_samples_len, self.dopps))
        self.dis_phy_area = np.zeros((self.range_samples_len, self.dopps))
        self.dis_eff_area = np.zeros((self.range_samples_len, self.dopps))
        for i in range(lam_len, end):
            # # compute relative delay
            tau = (i - lam_len) * self.dt
            tau = 1.0e-6 if i == lam_len else tau
            # # compute absolute delay
            tau_abs = tau + self.sp_delay
            a = tau_abs / self.cosi**2 * sqrt(1.0 - self.sp_delay / tau_abs)
            c = self.tani / self.cosi * tau
            delta = 2.0 * const.PI / self.num_angles
            sca_pow, phy_area, eff_area = self.delay_integration(
                tau, a, c, delta)
            self.power[i, :] = sca_pow
            self.dis_phy_area[i, :] = phy_area
            self.dis_eff_area[i, :] = eff_area

    def compute_sp_info(self):
        """
        compute the delay/dopper on the specular point, also calculate the
        coherent power on the specular point for the coherent reflection
        """
        # # compute scattering vector
        inc_vec = -1.0 * self.geometry.tx_spf / norm(self.geometry.tx_spf)
        scat_vec = self.geometry.rx_spf / norm(self.geometry.rx_spf)
        # # delay and dopper at specular point
        self.sp_dopp = self.geometry.doppler_shift(inc_vec, scat_vec,
                                                   self.signal.freq)
        self.sp_delay = self.geometry.geometric_delay
        # # coherent power
        if self.coherent_pow_flg:
            self.compute_coherent_power(scat_vec)

    def set_model(self, rx_pos, rx_vel, tx_pos, tx_vel, cov_mode=False):
        """ set model for simulator initalization"""
        self.ddm_cov_mode = cov_mode
        # # equal 244ns( 1/(1/1023000/4)), it is ddm delay sampling rate,
        self.sampling_rate = 4091750.0  # 4092000
        self.range_len_exponent = 8
        self.delays = 17  # DDM delay chips
        self.dopps = 11  # DDM doppler bins
        self.dopp_bin = 500.0  # doppler resolution unit in Hz
        self.filter_bb_bw = 5000000.0  # receiver baseband bandwidth in Hz
        self.polar_mode = "RL"  # poliariztion of reflected signal
        # # variable initialize
        if self.ddm_cov_mode:
            self.dopps = (self.dopps - 1) * 2 * self.ddm_cov_factor
        self.center_dopp = self.dopps // 2
        self.range_samples_len = 2**self.range_len_exponent
        # # set bistatic radar geometry
        self.configure_radar_geometry(tx_pos, tx_vel, rx_pos, rx_vel, True)
        # # set interface
        self.set_interface(self.wind_speed)
        # # set radar signal
        self.set_radar_signal(self.sampling_rate, self.filter_bb_bw,
                              self.range_len_exponent)
        # # set intrument information
        self.gain = 0.0
        self.antenna_temperature = 200
        self.noise_figure = 3.0
        self.set_nadir_antenna(self.gain, self.antenna_temperature,
                               self.noise_figure, self.filter_bb_bw, False)
        # # set power waveform information
        init_range = self.geometry.geometric_delay
        init_range -= (self.signal.lambda_size // 2 + 1) * self.dt
        self.set_pow_waveform(init_range, self.sampling_rate)

    def simulate(self, rx_pos, rx_vel, tx_pos, tx_vel):
        self.set_model(rx_pos, rx_vel, tx_pos, tx_vel)
        self.compute_sp_info()
        self.compute_power_distribution()
        self.compute_center_waveform()
        self.compute_ddm()
        # self.output_subddm()
        return self.waveform, self.integrate_waveform, self.wave_range