def compute_intercept_waypoint(self, wpt=None):
        ''' Computes an intercept waypoint for the UAV to steer to
        @param wpt: waypoint object to use for computed values
        @return waypoint object (LLA) with the intercept waypoint (or None)
        '''
        lead_uav = self._pre_check()
        if not lead_uav:
            return None

        # Update pursuer and target position and velocity info
        lead_lat, lead_lon = \
            gps.gps_newpos(lead_uav.state.pose.pose.position.lat, \
                           lead_uav.state.pose.pose.position.lon, \
                           self._follow_distance,  self._follow_angle)
        lead_spd = math.hypot(lead_uav.state.twist.twist.linear.x, \
                              lead_uav.state.twist.twist.linear.y)
        lead_crs = math.atan2(lead_uav.state.twist.twist.linear.y, \
                              lead_uav.state.twist.twist.linear.x)
        own_lat = self.own_pose.pose.pose.position.lat
        own_lon = self.own_pose.pose.pose.position.lon
        own_spd = math.hypot(self.own_pose.twist.twist.linear.x, \
                             self.own_pose.twist.twist.linear.y)
        if (own_spd - lead_spd) < 1.0: own_spd = lead_spd + 1.0
        own_crs = math.atan2(self.own_pose.twist.twist.linear.y, \
                             self.own_pose.twist.twist.linear.x)

        # Compute the control parameters
        self._theta = gps.gps_bearing(own_lat, own_lon, lead_lat, lead_lon)
        tgt_distance = gps.gps_distance(own_lat, own_lon, lead_lat, lead_lon)
        crossing_spd = own_spd * math.sin(self._theta - own_crs) + \
                       lead_spd * math.sin(self._theta + math.pi - lead_crs)
        self._theta_dot = crossing_spd / tgt_distance
        theta_diff = gps.normalize_pi(self._theta - own_crs)
        self._bias = min(gps.signum(theta_diff) * 2.5, theta_diff / 10.0)

        # Use the PN calculation to compute the ordered acceleration
        ordered_a = (self._gain * self._theta_dot + self._bias) * own_spd
        a_max = min(self._a_max, ((2.0 * math.pow(own_spd, 2.0)) / self._L1))
        if math.fabs(ordered_a) > a_max:
            ordered_a = gps.signum(ordered_a) * a_max
        nu = math.asin((ordered_a * self._L1) / (2.0 * math.pow(own_spd, 2.0)))

        # Compute the waypoint command
        if not wpt: wpt = brdgmsg.LLA()
        wpt.lat, wpt.lon = \
            gps.gps_newpos(lead_lat, lead_lon, nu + own_crs, self._L1)

        if self._alt_mode == InterceptCalculator.BASE_ALT_MODE:
            wpt.alt = self._alt
        else:
            wpt.alt = lead_uav.state.pose.pose.rel_alt + self._alt
        self._owner.log_dbug(
            "intercept waypoint (lat, lon, alt) = (%f, %f, %f)" %
            (wpt.lat, wpt.lon, wpt.alt))
        return wpt
    def compute_intercept_waypoint(self, wpt=None):
        ''' Computes an intercept waypoint for the UAV to steer to
        @param wpt: waypoint object to use for computed values
        @return waypoint object (LLA) with the intercept waypoint (or None)
        '''
        lead_uav = self._pre_check()
        if not lead_uav:
            return None

        # Update pursuer and target position and velocity info
        lead_lat, lead_lon = \
            gps.gps_newpos(lead_uav.state.pose.pose.position.lat, \
                           lead_uav.state.pose.pose.position.lon, \
                           self._follow_distance,  self._follow_angle)
        lead_spd = math.hypot(lead_uav.state.twist.twist.linear.x, \
                              lead_uav.state.twist.twist.linear.y)
        lead_crs = math.atan2(lead_uav.state.twist.twist.linear.y, \
                              lead_uav.state.twist.twist.linear.x)
        own_lat = self.own_pose.pose.pose.position.lat
        own_lon = self.own_pose.pose.pose.position.lon
        own_spd = math.hypot(self.own_pose.twist.twist.linear.x, \
                             self.own_pose.twist.twist.linear.y)
        if (own_spd - lead_spd) < 1.0: own_spd = lead_spd + 1.0
        own_crs = math.atan2(self.own_pose.twist.twist.linear.y, \
                             self.own_pose.twist.twist.linear.x)

        # Compute the control parameters
        self._theta = gps.gps_bearing(own_lat, own_lon, lead_lat, lead_lon)
        tgt_distance = gps.gps_distance(own_lat, own_lon, lead_lat, lead_lon)
        crossing_spd = own_spd * math.sin(self._theta - own_crs) + \
                       lead_spd * math.sin(self._theta + math.pi - lead_crs)
        self._theta_dot = crossing_spd / tgt_distance
        theta_diff = gps.normalize_pi(self._theta - own_crs)
        self._bias = min(gps.signum(theta_diff) * 2.5, theta_diff / 10.0)

        # Use the PN calculation to compute the ordered acceleration
        ordered_a = (self._gain * self._theta_dot + self._bias) * own_spd
        a_max = min(self._a_max, ((2.0 * math.pow(own_spd, 2.0)) / self._L1))
        if math.fabs(ordered_a) > a_max:
            ordered_a = gps.signum(ordered_a) * a_max
        nu = math.asin((ordered_a * self._L1) / (2.0 * math.pow(own_spd, 2.0)))

        # Compute the waypoint command
        if not wpt:  wpt = brdgmsg.LLA()
        wpt.lat, wpt.lon = \
            gps.gps_newpos(lead_lat, lead_lon, nu + own_crs, self._L1)

        if self._alt_mode == InterceptCalculator.BASE_ALT_MODE:
            wpt.alt = self._alt
        else:
            wpt.alt = lead_uav.state.pose.pose.rel_alt + self._alt
        self._owner.log_dbug("intercept waypoint (lat, lon, alt) = (%f, %f, %f)"
                             %(wpt.lat, wpt.lon, wpt.alt))
        return wpt
Beispiel #3
0
    def _send_ctrl_wp(self):
        ''' Uses the computed gain, bias, and theta_dot to compute a waypoint
        '''
        v_p = math.hypot(self._vp[0], self._vp[1])
        a_p = (self._gain * self._theta_dot + self._bias) * v_p
        if math.fabs(a_p) > PNInterceptor.A_P_MAX:
            a_p = gps.signum(a_p) * PNInterceptor.A_P_MAX

        L1 = 150.0  #min((v_p * 5.0 * self._dt), self._range)
        nu = math.asin((a_p * L1) / (2.0 * math.pow(v_p, 2.0)))
        self.wp_msg.lat, self.wp_msg.lon = \
            gps.gps_newpos(self._xp[0], self._xp[1], nu + self._xp[2], L1)
        self.publishWaypoint(self.wp_msg)
    def _send_ctrl_wp(self):
        ''' Uses the computed gain, bias, and theta_dot to compute a waypoint
        '''
        v_p = math.hypot(self._vp[0], self._vp[1])
        a_p = (self._gain * self._theta_dot + self._bias) * v_p
        if math.fabs(a_p) > PNInterceptor.A_P_MAX:
            a_p = gps.signum(a_p) * PNInterceptor.A_P_MAX

        L1 = 150.0 #min((v_p * 5.0 * self._dt), self._range)
        nu = math.asin((a_p * L1) / (2.0 * math.pow(v_p, 2.0)))
        self.wp_msg.lat, self.wp_msg.lon = \
            gps.gps_newpos(self._xp[0], self._xp[1], nu + self._xp[2], L1)
        self.publishWaypoint(self.wp_msg)
Beispiel #5
0
    def _s_turn(self):
        ''' Calculations associated with the S-turn to the virtual target
        '''
        if self._s_turn_state == self.S1:  # Offset turn
            self._gain = 2.0
            self._bias = 0.5 * gps.signum(self._theta_dot)
            if math.fabs(gps.signum(self._theta_dot) - \
                         gps.signum(self._theta_dot_last)) == 2:
                self._s_turn_state = self.S2
                self._theta2 = self._theta
                self._theta3 = self._theta2 + 0.75 * (self._theta_VT_init -
                                                      self._theta2)
                self._theta3 = gps.normalize(self._theta3)
                self._s2_check = \
                    gps.signum(gps.normalize_pi(self._theta - self._theta3))
                self.log_info("Shift to S-turn phase S2:\tTheta=%f\tTheta3=%f\tTheta_dot=%f"\
                              %(self._theta, self._theta3, self._theta_dot))

        if self._s_turn_state == self.S2:  # Middle portion of the S
            self._bias = -0.25 * gps.signum(self._theta_dot)
            self._gain = -2.0
            if self._s2_check != \
               gps.signum(gps.normalize_pi(self._theta - self._theta3)):
                self._s_turn_state = self.S3
                self._theta3 = self._theta
                self._psi_p3 = self._xp[2]
                self.log_info("Shift to S-turn phase S3")
                self._bias_initialized = False  # to facilitate a log entry

        if self._s_turn_state == self.S3:  # Final turn to VT
            if (self._theta_dot_VT_init < 0.0 and \
                angle_between(self._psi_p_f, self._theta_VT_init, \
                              2.0 * self._theta3 - self._psi_p3)) or \
               (self._theta_dot_VT_init >= 0.0 and \
                angle_between(self._psi_p_f, \
                              2.0 * self._theta3 - self._psi_p3, \
                              self._theta_VT_init)):
                self._gain = math.fabs((gps.normalize_pi(self._psi_p_f - self._psi_p3)) / \
                                       (gps.normalize_pi(self._psi_p_f - self._theta3)))
                self._bias = 0.0
                if not self._bias_initialized:
                    self._bias_initialized = True
                    self.log_info("Phase S3 using 1-bias turn")

            else:
                if math.fabs(gps.normalize_pi(self._psi_p_f - self._xp[2]) / \
                             gps.normalize_pi(self._psi_p_f - self._theta)) < 2.0:
                    self._gain = gps.normalize_pi(self._psi_p3) / \
                                 gps.normalize_pi(math.pi/2.0 + self._theta3)
                    self._bias = 0.0
                else:
                    self._gain = 2.0
                    self._bias = 0.0
                if not self._bias_initialized:
                    self._bias_initialized = True
                    self.log_info("Phase S3 using 2-bias turn")
Beispiel #6
0
    def _send_ctrl_wp(self):
        ''' Uses the computed gain, bias, and theta_dot to compute a waypoint
        '''
        v_p = math.hypot(self._vp[0], self._vp[1])
        a_p = (self._gain * self._theta_dot + self._bias) * v_p
        if math.fabs(a_p) > USMAInterceptor.A_P_MAX:
            a_p = gps.signum(a_p) * USMAInterceptor.A_P_MAX

        L1 = 150.0  #min((v_p * 5.0 * self._dt), self._range)
        nu = 0
        if v_p != 0:
            nu = math.asin((a_p * L1) / (2.0 * math.pow(v_p, 2.0)))
        self.wp_msg = self._own_pose.pose.pose.position
        self.wp_msg.lat, self.wp_msg.lon = \
            gps.gps_newpos(self._xp[0], self._xp[1], nu + self._xp[2], L1)
        self._wp = numpy.array(
            [self.wp_msg.lat, self.wp_msg.lon,
             self.wp_msg.rel_alt])  # publishWaypoint(self.wp_msg)
    def _runin_state(self):
        ''' Performs calculations and operations required for the "runin" state
        '''
        if not self._state_initialized:
            self.log_info("Commence run-in phase")
            self._gain = 2.5
            self._state_initialized = True

        # Compute the control parameters & set a bias value if rqd
        # to force the pursuer towards the target
        self._set_ctrl_params(self._xp, self._vp, self._xt, self._vt)
        theta_diff = gps.normalize_pi(self._theta - self._xp[2])
        self._bias = min(gps.signum(theta_diff) * 2.5, theta_diff / 10.0)

        # Keep going until the offset range is reached
        if self._range <= PNInterceptor.R_TH1:
            self._state_initialized = False
            self._state = PNInterceptor.OFFSET
            self.log_info("Transition to offset phase")
Beispiel #8
0
    def _runin_state(self):
        ''' Performs calculations and operations required for the "runin" state
        '''
        if not self._state_initialized:
            self.log_info("Commence run-in phase")
            self._gain = 2.5
            self._state_initialized = True

        # Compute the control parameters & set a bias value if rqd
        # to force the pursuer towards the target
        self._set_ctrl_params(self._xp, self._vp, self._xt, self._vt)
        theta_diff = gps.normalize_pi(self._theta - self._xp[2])
        self._bias = min(gps.signum(theta_diff) * 2.5, theta_diff / 10.0)

        # Keep going until the offset range is reached
        if self._range <= PNInterceptor.R_TH1:
            self._state_initialized = False
            self._state = PNInterceptor.OFFSET
            self.log_info("Transition to offset phase")
Beispiel #9
0
 def _calculate_E(self):
     ''' Calculates the approximated integral of control energy square (E)
     @return calculated value
     '''
     theta_p = gps.normalize(self._xp[2] - self._theta)
     theta_t = gps.normalize(self._xt[2] - self._theta)
     theta_tr = self._theta
     R = self._range
     delta_theta = 0.0025 * gps.signum(self._theta_dot)
     v_p = math.hypot(self._vp[0], self._vp[1])
     v_t = math.hypot(self._vt[0], self._vt[1])
     v_r = v_t * math.cos(theta_t) - v_p * math.cos(theta_p)
     v_theta = v_t * math.sin(theta_t) - v_p * math.sin(theta_p)
     E = 0.0
     while math.cos(delta_theta) > math.cos(self._psi_p_f - theta_tr):
         E = E + math.pow((self._gain * v_p), 2) * \
                             (v_theta / R) * delta_theta
         R = R * math.exp(v_r / v_theta * delta_theta)
         theta_t = theta_t - delta_theta
         theta_p = theta_p + (self._gain - 1) * delta_theta
         v_r = v_t * math.cos(theta_t) - v_p * math.cos(theta_p)
         v_theta = v_t * math.sin(theta_t) - v_p * math.sin(theta_p)
         theta_tr = theta_tr + delta_theta
     return E
 def _calculate_E(self):
     ''' Calculates the approximated integral of control energy square (E)
     @return calculated value
     '''
     theta_p = gps.normalize(self._xp[2] - self._theta)
     theta_t = gps.normalize(self._xt[2] - self._theta)
     theta_tr = self._theta
     R = self._range
     delta_theta = 0.0025 * gps.signum(self._theta_dot)
     v_p = math.hypot(self._vp[0], self._vp[1])
     v_t = math.hypot(self._vt[0], self._vt[1])
     v_r = v_t * math.cos(theta_t) - v_p * math.cos(theta_p)
     v_theta = v_t * math.sin(theta_t) - v_p * math.sin(theta_p)
     E = 0.0
     while math.cos(delta_theta) > math.cos(self._psi_p_t_f - theta_tr):
         E = E + math.pow((self._gain * v_p), 2) * \
                             (v_theta / R) * delta_theta
         R = R * math.exp(v_r / v_theta * delta_theta)
         theta_t = theta_t - delta_theta
         theta_p = theta_p + (self._gain - 1) * delta_theta
         v_r = v_t * math.cos(theta_t) - v_p * math.cos(theta_p)
         v_theta = v_t * math.sin(theta_t) - v_p * math.sin(theta_p)
         theta_tr = theta_tr + delta_theta
     return E
Beispiel #11
0
    def _offset_state(self):
        ''' Performs calculations and operations required for the "offset" state
        '''
        if not self._state_initialized:
            # Set the virtual waypoint D2 meters along the target's path and
            # offset by D1 meters to the side that the pursuer is currently on
            theta = gps.gps_bearing(self._xp[0], self._xp[1], \
                                     self._xt[0], self._xt[1])
            offset_dir = \
                gps.signum(gps.normalize_pi(theta + math.pi - self._xt[2]))
            tgt_to_VT = self._xt[2] + math.atan2(USMAInterceptor.D1 * offset_dir, \
                                                 USMAInterceptor.D2)
            self._VT = \
                gps.gps_newpos(self._xt[0], self._xt[1], tgt_to_VT, \
                               math.hypot(USMAInterceptor.D1, USMAInterceptor.D2))
            self._set_ctrl_params(self._xp, self._vp, \
                                  (self._VT[0], self._VT[1], self._xt[2]), \
                                  (0.0, 0.0))
            self._theta_VT_init = self._theta
            self._theta_dot_VT_init = self._theta_dot
            self._psi_p_VT_init = self._xp[2]
            self._psi_p_f = gps.normalize(self._xt[2] + math.pi)
            psi_p_f_rel = gps.normalize(self._psi_p_f - self._theta_VT_init)
            if gps.normalize_pi(self._theta_VT_init -
                                self._psi_p_VT_init) > 0.0:
                psi_p_f_rel = gps.normalize(-psi_p_f_rel)
            if angle_between(psi_p_f_rel, 0.0, math.pi):
                self._VT_turn_type = USMAInterceptor.S_TURN
                self._s_turn_state = USMAInterceptor.S1
            elif angle_between(psi_p_f_rel, (self._theta_VT_init - \
                                             self._psi_p_VT_init), 0.0):
                self._VT_turn_type = USMAInterceptor.DIRECT_TURN
            else:
                self._VT_turn_type = USMAInterceptor.DIRECT_2BIAS
            self._state_initialized = True

        self._set_ctrl_params(self._xp, self._vp, \
                              (self._VT[0], self._VT[1], 0.0), (0.0, 0.0))

        if self._range < USMAInterceptor.R_CAP_PARALLEL:
            self._state_initialized = False
            self._state = USMAInterceptor.PARALLEL_1
            self.log_info("Transition to parallel_1 phase")

        # Determine how to maneuver to the virtual target point
        # Direct turn to the virtual target
        if self._VT_turn_type == USMAInterceptor.DIRECT_TURN:
            self._gain = math.fabs((gps.normalize_pi(self._psi_p_f - \
                                                     self._psi_p_VT_init)) / \
                                   (gps.normalize_pi(self._psi_p_f - \
                                                     self._theta_VT_init)))
            self._bias = 0.0

        # Single turn to virtual target with 2 gains
        elif self._VT_turn_type == USMAInterceptor.DIRECT_2BIAS:
            if math.fabs(gps.normalize_pi(self._psi_p_f - self._xp[2]) / \
                         gps.normalize_pi(self._psi_p_f - self._theta)) < 2.0:
                self._gain = gps.normalize_pi(self._psi_p_VT_init) / \
                             gps.normalize_pi(math.pi/2 + self._theta_VT_init)
                self._bias = 0.0
            else:
                self._gain = 2.0
                self._bias = 0.0

        # S-turn to virtual target
        else:
            self._s_turn()
    def _offset_state(self):
        ''' Performs calculations and operations required for the "offset" state
        '''
        if not self._state_initialized:
            # Set the virtual waypoint D2 meters along the target's path and
            # offset by D1 meters to the side that the pursuer is currently on
            theta = gps.gps_bearing(self._xp[0], self._xp[1], \
                                     self._xt[0], self._xt[1])
            offset_dir = \
                gps.signum(gps.normalize_pi(theta + math.pi - self._xt[2]))
            tgt_to_VT = self._xt[2] + math.atan2(PNInterceptor.D1 * offset_dir, \
                                                 PNInterceptor.D2)
            self._VT = \
                gps.gps_newpos(self._xt[0], self._xt[1], tgt_to_VT, \
                               math.hypot(PNInterceptor.D1, PNInterceptor.D2))
            self._set_ctrl_params(self._xp, self._vp, \
                                  (self._VT[0], self._VT[1], 0.0), (0.0, 0.0))
            self._theta_VT_init = self._theta
            self._theta_dot_VT_init = self._theta_dot
            self._psi_p_VT_init = self._xp[2]
            self._psi_p_t_f = gps.normalize(self._xt[2] + math.pi)
            self._state_initialized = True

        self._set_ctrl_params(self._xp, self._vp, \
                              (self._VT[0], self._VT[1], 0.0), (0.0, 0.0))

        if self._range < PNInterceptor.R_CAP_PARALLEL:
            self._state_initialized = False
            self._state = PNInterceptor.PARALLEL_1
            self.log_info("Transition to parallel_1 phase")

        # Determine how to maneuver to the virtual target point
        # Direct turn to the virtual target
        if (self._theta_VT_init <= self._psi_p_t_f) and \
           (self._psi_p_t_f <= ((2.0 * self._theta_VT_init) - self._psi_p_VT_init)):
            self._gain = math.fabs((self._psi_p_t_f - self._psi_p_VT_init) / \
                                   (self._psi_p_t_f - self._theta_VT_init))
            self._bias = 0.0

        # Single turn to virtual target with 2 gains
        elif ((-math.pi  - self._psi_p_VT_init + \
              (2.0 * self._theta_VT_init)) <= self._psi_p_t_f) and \
             (self._psi_p_t_f <= ((2.0 * self._theta_VT_init) - \
                                  self._psi_p_VT_init)):
            if (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                gps.normalize(self._psi_p_t_f - self._theta)) < 2.0:
                self._gain = (2.0 / math.pi) * self._psi_p_VT_init
                self._bias = 0.0

            elif (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                  gps.normalize(self._psi_p_t_f - self._theta)) == 2.0:
                self._gain = 2.0
                self._bias = 0.0

            else: # should never get here
                self.log_warn("Unable to determine phase 2 2-step case")
                self.set_ready_state(False)

        # S-turn to virtual target
        else:
            if gps.signum(self._theta_dot) == \
               gps.signum(self._theta_dot_VT_init):
                self._gain = 2.5
                self._bias = 0.1 * gps.signum(self._theta_dot_VT_init)

            elif (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                  gps.normalize(self._psi_p_t_f - self._theta)) < 2.0:
                self._gain = 2.5
                self._bias = 0.05 * gps.signum(self._theta_dot_VT_init)

            elif (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                  gps.normalize(self._psi_p_t_f - self._theta)) >= 2.0:
                self._gain = math.fabs((self._psi_p_t_f - self._xp[2]) / \
                                      (self._psi_p_t_f - self._theta))
                self._bias = 0.0

            else: # should never get here
                self.log_warn("Unable to determine phase 2 3-step case")
                self.set_ready_state(False)
Beispiel #13
0
    def _offset_state(self):
        ''' Performs calculations and operations required for the "offset" state
        '''
        if not self._state_initialized:
            # Set the virtual waypoint D2 meters along the target's path and
            # offset by D1 meters to the side that the pursuer is currently on
            theta = gps.gps_bearing(self._xp[0], self._xp[1], \
                                     self._xt[0], self._xt[1])
            offset_dir = \
                gps.signum(gps.normalize_pi(theta + math.pi - self._xt[2]))
            tgt_to_VT = self._xt[2] + math.atan2(PNInterceptor.D1 * offset_dir, \
                                                 PNInterceptor.D2)
            self._VT = \
                gps.gps_newpos(self._xt[0], self._xt[1], tgt_to_VT, \
                               math.hypot(PNInterceptor.D1, PNInterceptor.D2))
            self._set_ctrl_params(self._xp, self._vp, \
                                  (self._VT[0], self._VT[1], 0.0), (0.0, 0.0))
            self._theta_VT_init = self._theta
            self._theta_dot_VT_init = self._theta_dot
            self._psi_p_VT_init = self._xp[2]
            self._psi_p_t_f = gps.normalize(self._xt[2] + math.pi)
            self._state_initialized = True

        self._set_ctrl_params(self._xp, self._vp, \
                              (self._VT[0], self._VT[1], 0.0), (0.0, 0.0))

        if self._range < PNInterceptor.R_CAP_PARALLEL:
            self._state_initialized = False
            self._state = PNInterceptor.PARALLEL_1
            self.log_info("Transition to parallel_1 phase")

        # Determine how to maneuver to the virtual target point
        # Direct turn to the virtual target
        if (self._theta_VT_init <= self._psi_p_t_f) and \
           (self._psi_p_t_f <= ((2.0 * self._theta_VT_init) - self._psi_p_VT_init)):
            self._gain = math.fabs((self._psi_p_t_f - self._psi_p_VT_init) / \
                                   (self._psi_p_t_f - self._theta_VT_init))
            self._bias = 0.0

        # Single turn to virtual target with 2 gains
        elif ((-math.pi  - self._psi_p_VT_init + \
              (2.0 * self._theta_VT_init)) <= self._psi_p_t_f) and \
             (self._psi_p_t_f <= ((2.0 * self._theta_VT_init) - \
                                  self._psi_p_VT_init)):
            if (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                gps.normalize(self._psi_p_t_f - self._theta)) < 2.0:
                self._gain = (2.0 / math.pi) * self._psi_p_VT_init
                self._bias = 0.0

            elif (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                  gps.normalize(self._psi_p_t_f - self._theta)) == 2.0:
                self._gain = 2.0
                self._bias = 0.0

            else:  # should never get here
                self.log_warn("Unable to determine phase 2 2-step case")
                self.set_ready_state(False)

        # S-turn to virtual target
        else:
            if gps.signum(self._theta_dot) == \
               gps.signum(self._theta_dot_VT_init):
                self._gain = 2.5
                self._bias = 0.1 * gps.signum(self._theta_dot_VT_init)

            elif (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                  gps.normalize(self._psi_p_t_f - self._theta)) < 2.0:
                self._gain = 2.5
                self._bias = 0.05 * gps.signum(self._theta_dot_VT_init)

            elif (gps.normalize(self._psi_p_t_f - self._xp[2]) / \
                  gps.normalize(self._psi_p_t_f - self._theta)) >= 2.0:
                self._gain = math.fabs((self._psi_p_t_f - self._xp[2]) / \
                                      (self._psi_p_t_f - self._theta))
                self._bias = 0.0

            else:  # should never get here
                self.log_warn("Unable to determine phase 2 3-step case")
                self.set_ready_state(False)