Exemple #1
0
 def reset(self):
     self.solver = AcadosOcpSolverFast('long', N)
     self.v_solution = np.zeros(N + 1)
     self.a_solution = np.zeros(N + 1)
     self.prev_a = np.array(self.a_solution)
     self.j_solution = np.zeros(N)
     self.yref = np.zeros((N + 1, COST_DIM))
     for i in range(N):
         self.solver.cost_set(i, "yref", self.yref[i])
     self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
     self.x_sol = np.zeros((N + 1, X_DIM))
     self.u_sol = np.zeros((N, 1))
     self.params = np.zeros((N + 1, PARAM_DIM))
     for i in range(N + 1):
         self.solver.set(i, 'x', np.zeros(X_DIM))
     self.last_cloudlog_t = 0
     self.status = False
     self.crash_cnt = 0.0
     self.solution_status = 0
     # timers
     self.solve_time = 0.0
     self.time_qp_solution = 0.0
     self.time_linearization = 0.0
     self.time_integrator = 0.0
     self.x0 = np.zeros(X_DIM)
     self.set_weights()
 def reset(self):
     self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR)
     self.v_solution = [0.0 for i in range(N + 1)]
     self.a_solution = [0.0 for i in range(N + 1)]
     self.j_solution = [0.0 for i in range(N)]
     self.yref = np.zeros((N + 1, COST_DIM))
     for i in range(N):
         self.solver.cost_set(i, "yref", self.yref[i])
     self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
     self.x_sol = np.zeros((N + 1, X_DIM))
     self.u_sol = np.zeros((N, 1))
     self.params = np.zeros((N + 1, 3))
     for i in range(N + 1):
         self.solver.set(i, 'x', np.zeros(X_DIM))
     self.last_cloudlog_t = 0
     self.status = False
     self.crash_cnt = 0.0
     self.solution_status = 0
     self.x0 = np.zeros(X_DIM)
     self.set_weights()
class LongitudinalMpc():
    def __init__(self, e2e=False):
        self.e2e = e2e
        self.reset()
        self.accel_limit_arr = np.zeros((N + 1, 2))
        self.accel_limit_arr[:, 0] = -1.2
        self.accel_limit_arr[:, 1] = 1.2
        self.source = SOURCES[2]

    def reset(self):
        self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR)
        self.v_solution = [0.0 for i in range(N + 1)]
        self.a_solution = [0.0 for i in range(N + 1)]
        self.j_solution = [0.0 for i in range(N)]
        self.yref = np.zeros((N + 1, COST_DIM))
        for i in range(N):
            self.solver.cost_set(i, "yref", self.yref[i])
        self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
        self.x_sol = np.zeros((N + 1, X_DIM))
        self.u_sol = np.zeros((N, 1))
        self.params = np.zeros((N + 1, 3))
        for i in range(N + 1):
            self.solver.set(i, 'x', np.zeros(X_DIM))
        self.last_cloudlog_t = 0
        self.status = False
        self.crash_cnt = 0.0
        self.solution_status = 0
        self.x0 = np.zeros(X_DIM)
        self.set_weights()

    def set_weights(self):
        if self.e2e:
            self.set_weights_for_xva_policy()
        else:
            self.set_weights_for_lead_policy()

    def set_weights_for_lead_policy(self):
        W = np.asfortranarray(
            np.diag([
                X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST,
                J_EGO_COST
            ]))
        for i in range(N):
            self.solver.cost_set(i, 'W', W)
        # Setting the slice without the copy make the array not contiguous,
        # causing issues with the C interface.
        self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

        # Set L2 slack cost on lower bound constraints
        Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST])
        for i in range(N):
            self.solver.cost_set(i, 'Zl', Zl)

    def set_weights_for_xva_policy(self):
        W = np.asfortranarray(np.diag([0., 10., 1., 10., 1.]))
        for i in range(N):
            self.solver.cost_set(i, 'W', W)
        # Setting the slice without the copy make the array not contiguous,
        # causing issues with the C interface.
        self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

        # Set L2 slack cost on lower bound constraints
        Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, 0.0])
        for i in range(N):
            self.solver.cost_set(i, 'Zl', Zl)

    def set_cur_state(self, v, a):
        if abs(self.x0[1] - v) > 1.:
            self.x0[1] = v
            self.x0[2] = a
            for i in range(0, N + 1):
                self.solver.set(i, 'x', self.x0)
        else:
            self.x0[1] = v
            self.x0[2] = a

    def extrapolate_lead(self, x_lead, v_lead, a_lead, a_lead_tau):
        a_lead_traj = a_lead * np.exp(-a_lead_tau * (T_IDXS**2) / 2.)
        v_lead_traj = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj), 0.0,
                              1e8)
        x_lead_traj = x_lead + np.cumsum(T_DIFFS * v_lead_traj)
        lead_xv = np.column_stack((x_lead_traj, v_lead_traj))
        return lead_xv

    def process_lead(self, lead):
        v_ego = self.x0[1]
        if lead is not None and lead.status:
            x_lead = lead.dRel
            v_lead = lead.vLead
            a_lead = lead.aLeadK
            a_lead_tau = lead.aLeadTau
        else:
            # Fake a fast lead car, so mpc can keep running in the same mode
            x_lead = 50.0
            v_lead = v_ego + 10.0
            a_lead = 0.0
            a_lead_tau = _LEAD_ACCEL_TAU

        # MPC will not converge if immediate crash is expected
        # Clip lead distance to what is still possible to brake for
        min_x_lead = (
            (v_ego + v_lead) / 2) * (v_ego - v_lead) / (-MIN_ACCEL * 2)
        x_lead = clip(x_lead, min_x_lead, 1e8)
        v_lead = clip(v_lead, 0.0, 1e8)
        a_lead = clip(a_lead, -10., 5.)
        lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau)
        return lead_xv

    def set_accel_limits(self, min_a, max_a):
        self.cruise_min_a = min_a
        self.cruise_max_a = max_a

    def update(self, carstate, radarstate, v_cruise):
        v_ego = self.x0[1]
        self.status = radarstate.leadOne.status or radarstate.leadTwo.status

        lead_xv_0 = self.process_lead(radarstate.leadOne)
        lead_xv_1 = self.process_lead(radarstate.leadTwo)

        # set accel limits in params
        self.params[:, 0] = interp(float(self.status), [0.0, 1.0],
                                   [self.cruise_min_a, MIN_ACCEL])
        self.params[:, 1] = self.cruise_max_a

        # To estimate a safe distance from a moving lead, we calculate how much stopping
        # distance that lead needs as a minimum. We can add that to the current distance
        # and then treat that as a stopped car/obstacle at this new distance.
        lead_0_obstacle = lead_xv_0[:, 0] + get_stopped_equivalence_factor(
            lead_xv_0[:, 1])
        lead_1_obstacle = lead_xv_1[:, 0] + get_stopped_equivalence_factor(
            lead_xv_1[:, 1])

        # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
        # when the leads are no factor.
        cruise_lower_bound = v_ego + (3 / 4) * self.cruise_min_a * T_IDXS
        cruise_upper_bound = v_ego + (3 / 4) * self.cruise_max_a * T_IDXS
        v_cruise_clipped = np.clip(v_cruise * np.ones(N + 1),
                                   cruise_lower_bound, cruise_upper_bound)
        cruise_obstacle = T_IDXS * v_cruise_clipped + get_safe_obstacle_distance(
            v_cruise_clipped)

        x_obstacles = np.column_stack(
            [lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
        self.source = SOURCES[np.argmin(x_obstacles[0])]
        self.params[:, 2] = np.min(x_obstacles, axis=1)

        self.run()
        if (np.any(lead_xv_0[:, 0] - self.x_sol[:, 0] < CRASH_DISTANCE)
                and radarstate.leadOne.modelProb > 0.9):
            self.crash_cnt += 1
        else:
            self.crash_cnt = 0

    def update_with_xva(self, x, v, a):
        self.yref[:, 1] = x
        self.yref[:, 2] = v
        self.yref[:, 3] = a
        for i in range(N):
            self.solver.cost_set(i, "yref", self.yref[i])
        self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
        self.accel_limit_arr[:, 0] = -10.
        self.accel_limit_arr[:, 1] = 10.
        x_obstacle = 1e5 * np.ones((N + 1))
        self.params = np.concatenate(
            [self.accel_limit_arr, x_obstacle[:, None]], axis=1)
        self.run()

    def run(self):
        for i in range(N + 1):
            self.solver.set(i, 'p', self.params[i])
        self.solver.constraints_set(0, "lbx", self.x0)
        self.solver.constraints_set(0, "ubx", self.x0)
        self.solution_status = self.solver.solve()
        for i in range(N + 1):
            self.x_sol[i] = self.solver.get(i, 'x')
        for i in range(N):
            self.u_sol[i] = self.solver.get(i, 'u')

        self.v_solution = self.x_sol[:, 1]
        self.a_solution = self.x_sol[:, 2]
        self.j_solution = self.u_sol[:, 0]

        t = sec_since_boot()
        if self.solution_status != 0:
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Long mpc reset, solution_status: %s" %
                                 (self.solution_status))
            self.reset()
Exemple #4
0
class LongitudinalMpc:
    def __init__(self, e2e=False):
        self.e2e = e2e
        self.reset()
        self.source = SOURCES[2]

    def reset(self):
        self.solver = AcadosOcpSolverFast('long', N)
        self.v_solution = np.zeros(N + 1)
        self.a_solution = np.zeros(N + 1)
        self.prev_a = np.array(self.a_solution)
        self.j_solution = np.zeros(N)
        self.yref = np.zeros((N + 1, COST_DIM))
        for i in range(N):
            self.solver.cost_set(i, "yref", self.yref[i])
        self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
        self.x_sol = np.zeros((N + 1, X_DIM))
        self.u_sol = np.zeros((N, 1))
        self.params = np.zeros((N + 1, PARAM_DIM))
        for i in range(N + 1):
            self.solver.set(i, 'x', np.zeros(X_DIM))
        self.last_cloudlog_t = 0
        self.status = False
        self.crash_cnt = 0.0
        self.solution_status = 0
        # timers
        self.solve_time = 0.0
        self.time_qp_solution = 0.0
        self.time_linearization = 0.0
        self.time_integrator = 0.0
        self.x0 = np.zeros(X_DIM)
        self.set_weights()

    def set_weights(self, prev_accel_constraint=True):
        if self.e2e:
            self.set_weights_for_xva_policy()
            self.params[:, 0] = -10.
            self.params[:, 1] = 10.
            self.params[:, 2] = 1e5
        else:
            self.set_weights_for_lead_policy(prev_accel_constraint)

    def set_weights_for_lead_policy(self, prev_accel_constraint=True):
        a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0
        W = np.asfortranarray(
            np.diag([
                X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST,
                a_change_cost, J_EGO_COST
            ]))
        for i in range(N):
            # reduce the cost on (a-a_prev) later in the horizon.
            W[4, 4] = a_change_cost * np.interp(T_IDXS[i], [0.0, 1.0, 2.0],
                                                [1.0, 1.0, 0.0])
            self.solver.cost_set(i, 'W', W)
        # Setting the slice without the copy make the array not contiguous,
        # causing issues with the C interface.
        self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

        # Set L2 slack cost on lower bound constraints
        Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST])
        for i in range(N):
            self.solver.cost_set(i, 'Zl', Zl)

    def set_weights_for_xva_policy(self):
        W = np.asfortranarray(np.diag([0., 10., 1., 10., 0.0, 1.]))
        for i in range(N):
            self.solver.cost_set(i, 'W', W)
        # Setting the slice without the copy make the array not contiguous,
        # causing issues with the C interface.
        self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

        # Set L2 slack cost on lower bound constraints
        Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, 0.0])
        for i in range(N):
            self.solver.cost_set(i, 'Zl', Zl)

    def set_cur_state(self, v, a):
        v_prev = self.x0[1]
        self.x0[1] = v
        self.x0[2] = a
        if abs(v_prev - v) > 2.:  # probably only helps if v < v_prev
            for i in range(0, N + 1):
                self.solver.set(i, 'x', self.x0)

    @staticmethod
    def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau):
        a_lead_traj = a_lead * np.exp(-a_lead_tau * (T_IDXS**2) / 2.)
        v_lead_traj = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj), 0.0,
                              1e8)
        x_lead_traj = x_lead + np.cumsum(T_DIFFS * v_lead_traj)
        lead_xv = np.column_stack((x_lead_traj, v_lead_traj))
        return lead_xv

    def process_lead(self, lead):
        v_ego = self.x0[1]
        if lead is not None and lead.status:
            x_lead = lead.dRel
            v_lead = lead.vLead
            a_lead = lead.aLeadK
            a_lead_tau = lead.aLeadTau
        else:
            # Fake a fast lead car, so mpc can keep running in the same mode
            x_lead = 50.0
            v_lead = v_ego + 10.0
            a_lead = 0.0
            a_lead_tau = _LEAD_ACCEL_TAU

        # MPC will not converge if immediate crash is expected
        # Clip lead distance to what is still possible to brake for
        min_x_lead = (
            (v_ego + v_lead) / 2) * (v_ego - v_lead) / (-MIN_ACCEL * 2)
        x_lead = clip(x_lead, min_x_lead, 1e8)
        v_lead = clip(v_lead, 0.0, 1e8)
        a_lead = clip(a_lead, -10., 5.)
        lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau)
        return lead_xv

    def set_accel_limits(self, min_a, max_a):
        self.cruise_min_a = min_a
        self.cruise_max_a = max_a

    def update(self, carstate, radarstate, v_cruise):
        v_ego = self.x0[1]
        self.status = radarstate.leadOne.status or radarstate.leadTwo.status

        lead_xv_0 = self.process_lead(radarstate.leadOne)
        lead_xv_1 = self.process_lead(radarstate.leadTwo)

        # set accel limits in params
        self.params[:, 0] = interp(float(self.status), [0.0, 1.0],
                                   [self.cruise_min_a, MIN_ACCEL])
        self.params[:, 1] = self.cruise_max_a

        # To estimate a safe distance from a moving lead, we calculate how much stopping
        # distance that lead needs as a minimum. We can add that to the current distance
        # and then treat that as a stopped car/obstacle at this new distance.
        lead_0_obstacle = lead_xv_0[:, 0] + get_stopped_equivalence_factor(
            lead_xv_0[:, 1])
        lead_1_obstacle = lead_xv_1[:, 0] + get_stopped_equivalence_factor(
            lead_xv_1[:, 1])

        # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
        # when the leads are no factor.
        v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
        v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05)
        v_cruise_clipped = np.clip(v_cruise * np.ones(N + 1), v_lower, v_upper)
        cruise_obstacle = np.cumsum(
            T_DIFFS *
            v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped)

        x_obstacles = np.column_stack(
            [lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
        self.source = SOURCES[np.argmin(x_obstacles[0])]
        self.params[:, 2] = np.min(x_obstacles, axis=1)
        self.params[:, 3] = np.copy(self.prev_a)

        self.run()
        if (np.any(lead_xv_0[:, 0] - self.x_sol[:, 0] < CRASH_DISTANCE)
                and radarstate.leadOne.modelProb > 0.9):
            self.crash_cnt += 1
        else:
            self.crash_cnt = 0

    def update_with_xva(self, x, v, a):
        self.yref[:, 1] = x
        self.yref[:, 2] = v
        self.yref[:, 3] = a
        for i in range(N):
            self.solver.cost_set(i, "yref", self.yref[i])
        self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
        self.params[:, 3] = np.copy(self.prev_a)
        self.run()

    def run(self):
        for i in range(N + 1):
            self.solver.set(i, 'p', self.params[i])
        self.solver.constraints_set(0, "lbx", self.x0)
        self.solver.constraints_set(0, "ubx", self.x0)

        self.solution_status = self.solver.solve()
        self.solve_time = float(self.solver.get_stats('time_tot')[0])
        self.time_qp_solution = float(self.solver.get_stats('time_qp')[0])
        self.time_linearization = float(self.solver.get_stats('time_lin')[0])
        self.time_integrator = float(self.solver.get_stats('time_sim')[0])

        # qp_iter = self.solver.get_stats('statistics')[-1][-1] # SQP_RTI specific
        # print(f"long_mpc timings: tot {self.solve_time:.2e}, qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e}, integrator {self.time_integrator:.2e}, qp_iter {qp_iter}")
        # res = self.solver.get_residuals()
        # print(f"long_mpc residuals: {res[0]:.2e}, {res[1]:.2e}, {res[2]:.2e}, {res[3]:.2e}")
        # self.solver.print_statistics()

        for i in range(N + 1):
            self.x_sol[i] = self.solver.get(i, 'x')
        for i in range(N):
            self.u_sol[i] = self.solver.get(i, 'u')

        self.v_solution = self.x_sol[:, 1]
        self.a_solution = self.x_sol[:, 2]
        self.j_solution = self.u_sol[:, 0]

        self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution)

        t = sec_since_boot()
        if self.solution_status != 0:
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning(
                    f"Long mpc reset, solution_status: {self.solution_status}")
            self.reset()
Exemple #5
0
class LongitudinalMpc:
  def __init__(self, e2e=False):
    self.e2e = e2e
    self.desired_TF = T_FOLLOW
    self.reset()
    self.source = SOURCES[2]

  def reset(self):
    self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR)
    self.v_solution = np.zeros(N+1)
    self.a_solution = np.zeros(N+1)
    self.prev_a = np.array(self.a_solution)
    self.j_solution = np.zeros(N)
    self.yref = np.zeros((N+1, COST_DIM))
    for i in range(N):
      self.solver.cost_set(i, "yref", self.yref[i])
    self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
    self.x_sol = np.zeros((N+1, X_DIM))
    self.u_sol = np.zeros((N,1))
    self.params = np.zeros((N+1, PARAM_DIM))
    for i in range(N+1):
      self.solver.set(i, 'x', np.zeros(X_DIM))
    self.last_cloudlog_t = 0
    self.status = False
    self.crash_cnt = 0.0
    self.solution_status = 0
    self.solve_time = 0.0
    self.x0 = np.zeros(X_DIM)
    self.set_weights()

  def set_weights(self, prev_accel_constraint=True, v_lead0=0, v_lead1=0):
    if self.e2e:
      self.set_weights_for_xva_policy()
      self.params[:,0] = -10.
      self.params[:,1] = 10.
      self.params[:,2] = 1e5
    else:
      self.set_weights_for_lead_policy(prev_accel_constraint)

  def get_cost_multipliers(self, v_lead0, v_lead1):
    v_ego = self.x0[1]
    v_ego_bps = [0, 10]
    TFs = [1.0, 1.25, T_FOLLOW]
    # KRKeegan adjustments to costs for different TFs
    # these were calculated using the test_longitudial.py deceleration tests
    a_change_tf = interp(self.desired_TF, TFs, [.1, .8, 1.])
    j_ego_tf = interp(self.desired_TF, TFs, [.6, .8, 1.])
    d_zone_tf = interp(self.desired_TF, TFs, [1.6, 1.3, 1.])
    # KRKeegan adjustments to improve sluggish acceleration
    # do not apply to deceleration
    j_ego_v_ego = 1
    a_change_v_ego = 1
    if (v_lead0 - v_ego >= 0) and (v_lead1 - v_ego >= 0):
      j_ego_v_ego = interp(v_ego, v_ego_bps, [.05, 1.])
      a_change_v_ego = interp(v_ego, v_ego_bps, [.05, 1.])
    # Select the appropriate min/max of the options
    j_ego = min(j_ego_tf, j_ego_v_ego)
    a_change = min(a_change_tf, a_change_v_ego)
    return (a_change, j_ego, d_zone_tf)

  def set_weights_for_lead_policy(self, prev_accel_constraint=True, v_lead0=0, v_lead1=0):
    a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0
    cost_mulitpliers = self.get_cost_multipliers(v_lead0, v_lead1)
    W = np.asfortranarray(np.diag([X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST,
                                   A_EGO_COST, a_change_cost * cost_mulitpliers[0],
                                   J_EGO_COST * cost_mulitpliers[1]]))
    for i in range(N):
      W[4,4] = A_CHANGE_COST * cost_mulitpliers[0] * np.interp(T_IDXS[i], [0.0, 1.0, 2.0], [1.0, 1.0, 0.0])
      self.solver.cost_set(i, 'W', W)
    # Setting the slice without the copy make the array not contiguous,
    # causing issues with the C interface.
    self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

    # Set L2 slack cost on lower bound constraints
    Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST * cost_mulitpliers[2]])
    for i in range(N):
      self.solver.cost_set(i, 'Zl', Zl)

  def set_weights_for_xva_policy(self):
    W = np.asfortranarray(np.diag([0., 10., 1., 10., 0.0, 1.]))
    for i in range(N):
      self.solver.cost_set(i, 'W', W)
    # Setting the slice without the copy make the array not contiguous,
    # causing issues with the C interface.
    self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

    # Set L2 slack cost on lower bound constraints
    Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, 0.0])
    for i in range(N):
      self.solver.cost_set(i, 'Zl', Zl)

  def set_cur_state(self, v, a):
    if abs(self.x0[1] - v) > 2.:
      self.x0[1] = v
      self.x0[2] = a
      for i in range(0, N+1):
        self.solver.set(i, 'x', self.x0)
    else:
      self.x0[1] = v
      self.x0[2] = a

  @staticmethod
  def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau):
    a_lead_traj = a_lead * np.exp(-a_lead_tau * (T_IDXS**2)/2.)
    v_lead_traj = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj), 0.0, 1e8)
    x_lead_traj = x_lead + np.cumsum(T_DIFFS * v_lead_traj)
    lead_xv = np.column_stack((x_lead_traj, v_lead_traj))
    return lead_xv

  def process_lead(self, lead):
    v_ego = self.x0[1]
    if lead is not None and lead.status:
      x_lead = lead.dRel
      v_lead = lead.vLead
      a_lead = lead.aLeadK
      a_lead_tau = lead.aLeadTau
    else:
      # Fake a fast lead car, so mpc can keep running in the same mode
      x_lead = 50.0
      v_lead = v_ego + 10.0
      a_lead = 0.0
      a_lead_tau = _LEAD_ACCEL_TAU

    # MPC will not converge if immediate crash is expected
    # Clip lead distance to what is still possible to brake for
    min_x_lead = ((v_ego + v_lead)/2) * (v_ego - v_lead) / (-MIN_ACCEL * 2)
    x_lead = clip(x_lead, min_x_lead, 1e8)
    v_lead = clip(v_lead, 0.0, 1e8)
    a_lead = clip(a_lead, -10., 5.)
    lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau)
    return lead_xv

  def set_accel_limits(self, min_a, max_a):
    self.cruise_min_a = min_a
    self.cruise_max_a = max_a

  def update_TF(self, carstate):
    if carstate.distanceLines == 1: # Traffic
      # At slow speeds more time, decrease time up to 60mph
      # in mph ~= 5     10   15   20  25     30    35     40  45     50    55     60  65     70    75     80  85     90
      x_vel = [0, 2.25, 4.5, 6.75, 9, 11.25, 13.5, 15.75, 18, 20.25, 22.5, 24.75, 27, 29.25, 31.5, 33.75, 36, 38.25, 40.5]
      y_dist = [1.25, 1.24, 1.23, 1.22, 1.21, 1.20, 1.18, 1.16, 1.13, 1.11, 1.09, 1.07, 1.05, 1.05, 1.05, 1.05, 1.05, 1.05, 1.05]
      self.desired_TF = np.interp(carstate.vEgo, x_vel, y_dist)
    elif carstate.distanceLines == 2: # Relaxed
      self.desired_TF = 1.25
    else:
      self.desired_TF = T_FOLLOW

  def update(self, carstate, radarstate, v_cruise):
    self.update_TF(carstate)
    v_ego = self.x0[1]
    self.status = radarstate.leadOne.status or radarstate.leadTwo.status

    lead_xv_0 = self.process_lead(radarstate.leadOne)
    lead_xv_1 = self.process_lead(radarstate.leadTwo)

    # Use the processed leads which always have a velocity
    self.set_weights(lead_xv_0[0,1], lead_xv_1[0,1])

    # set accel limits in params
    self.params[:,0] = interp(float(self.status), [0.0, 1.0], [self.cruise_min_a, MIN_ACCEL])
    self.params[:,1] = self.cruise_max_a

    # To estimate a safe distance from a moving lead, we calculate how much stopping
    # distance that lead needs as a minimum. We can add that to the current distance
    # and then treat that as a stopped car/obstacle at this new distance.
    lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1], self.x_sol[:,1], self.desired_TF)
    lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1], self.x_sol[:,1], self.desired_TF)

    # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
    # when the leads are no factor.
    v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
    v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05)
    v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
                               v_lower,
                               v_upper)
    cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, self.desired_TF)

    x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
    self.source = SOURCES[np.argmin(x_obstacles[0])]
    self.params[:,2] = np.min(x_obstacles, axis=1)
    self.params[:,3] = np.copy(self.prev_a)
    self.params[:,4] = self.desired_TF

    self.run()
    if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and
            radarstate.leadOne.modelProb > 0.9):
      self.crash_cnt += 1
    else:
      self.crash_cnt = 0

  def update_with_xva(self, x, v, a):
    self.yref[:,1] = x
    self.yref[:,2] = v
    self.yref[:,3] = a
    for i in range(N):
      self.solver.cost_set(i, "yref", self.yref[i])
    self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
    self.params[:,3] = np.copy(self.prev_a)
    self.run()

  def run(self):
    for i in range(N+1):
      self.solver.set(i, 'p', self.params[i])
    self.solver.constraints_set(0, "lbx", self.x0)
    self.solver.constraints_set(0, "ubx", self.x0)

    t = sec_since_boot()
    self.solution_status = self.solver.solve()
    self.solve_time = sec_since_boot() - t

    for i in range(N+1):
      self.x_sol[i] = self.solver.get(i, 'x')
    for i in range(N):
      self.u_sol[i] = self.solver.get(i, 'u')

    self.v_solution = self.x_sol[:,1]
    self.a_solution = self.x_sol[:,2]
    self.j_solution = self.u_sol[:,0]

    self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution)

    if self.solution_status != 0:
      if t > self.last_cloudlog_t + 5.0:
        self.last_cloudlog_t = t
        cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}")
      self.reset()