acados long merged (#22224)

* rebased

* cleaner, seems to drive better?

* more stable

* wrong import

* new way of thinking

* reports look nice

* start move back

* works at leas

* good timestamps

* step by step

* somewhat work

* tests pass

* ALL CARS STOPPED

* should work

* fake a cruise obstacle

* cleaner costs

* pretty good except cruise braking

* works pretty well now!

* cleanup

* add source

* add source

* that is needed for unit tests

* nan recovery

* little cleaner

* stop wasting arrays

* unreasonable without unfair init

* this isnt needed without the exponential

* that works too

* unused

* uses less

* new ref

* long enough

* e2e long api

* DONT PUT IN A VIEW INTO ACADOS

* new ref for outside weights

* remove debug prints
pull/22397/head
HaraldSchafer 4 years ago committed by GitHub
parent f03ee4599e
commit fe983a7b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      SConstruct
  2. 2
      release/files_common
  3. 2
      selfdrive/controls/lib/lead_mpc_lib/.gitignore
  4. 58
      selfdrive/controls/lib/lead_mpc_lib/SConscript
  5. 0
      selfdrive/controls/lib/lead_mpc_lib/__init__.py
  6. 270
      selfdrive/controls/lib/lead_mpc_lib/lead_mpc.py
  7. 12
      selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
  8. 326
      selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
  9. 32
      selfdrive/controls/lib/longitudinal_planner.py
  10. 6
      selfdrive/controls/tests/test_following_distance.py
  11. 32
      selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
  12. 2
      selfdrive/test/process_replay/ref_commit
  13. 4
      selfdrive/test/test_onroad.py

@ -414,7 +414,6 @@ SConscript(['selfdrive/modeld/SConscript'])
SConscript(['selfdrive/controls/lib/cluster/SConscript'])
SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript'])
SConscript(['selfdrive/controls/lib/lead_mpc_lib/SConscript'])
SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript'])
SConscript(['selfdrive/boardd/SConscript'])

@ -240,10 +240,8 @@ selfdrive/controls/lib/fcw.py
selfdrive/controls/lib/cluster/*
selfdrive/controls/lib/lateral_mpc_lib/.gitignore
selfdrive/controls/lib/lead_mpc_lib/.gitignore
selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore
selfdrive/controls/lib/lateral_mpc_lib/*
selfdrive/controls/lib/lead_mpc_lib/*
selfdrive/controls/lib/longitudinal_mpc_lib/*
selfdrive/hardware/__init__.py

@ -1,2 +0,0 @@
acados_ocp_lead.json
c_generated_code/

@ -1,58 +0,0 @@
Import('env', 'arch')
gen = "c_generated_code"
casadi_model = [
f'{gen}/lead_model/lead_expl_ode_fun.c',
f'{gen}/lead_model/lead_expl_vde_forw.c',
]
casadi_cost_y = [
f'{gen}/lead_cost/lead_cost_y_fun.c',
f'{gen}/lead_cost/lead_cost_y_fun_jac_ut_xt.c',
f'{gen}/lead_cost/lead_cost_y_hess.c',
]
casadi_cost_e = [
f'{gen}/lead_cost/lead_cost_y_e_fun.c',
f'{gen}/lead_cost/lead_cost_y_e_fun_jac_ut_xt.c',
f'{gen}/lead_cost/lead_cost_y_e_hess.c',
]
casadi_cost_0 = [
f'{gen}/lead_cost/lead_cost_y_0_fun.c',
f'{gen}/lead_cost/lead_cost_y_0_fun_jac_ut_xt.c',
f'{gen}/lead_cost/lead_cost_y_0_hess.c',
]
build_files = [f'{gen}/acados_solver_lead.c'] + casadi_model + casadi_cost_y + casadi_cost_e + casadi_cost_0
# extra generated files used to trigger a rebuild
generated_files = [
f'{gen}/Makefile',
f'{gen}/main_lead.c',
f'{gen}/acados_solver_lead.h',
f'{gen}/lead_model/lead_expl_vde_adj.c',
f'{gen}/lead_model/lead_model.h',
f'{gen}/lead_cost/lead_cost_y_fun.h',
f'{gen}/lead_cost/lead_cost_y_e_fun.h',
f'{gen}/lead_cost/lead_cost_y_0_fun.h',
] + build_files
lenv = env.Clone()
lenv.Clean(generated_files, Dir(gen))
lenv.Command(generated_files,
["lead_mpc.py"],
f"cd {Dir('.').abspath} && python lead_mpc.py")
lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES")
lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES")
lenv["CCFLAGS"].append("-Wno-unused")
lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags")
lenv.SharedLibrary(f"{gen}/acados_ocp_solver_lead",
build_files,
LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e'])

@ -1,270 +0,0 @@
#!/usr/bin/env python3
import os
import math
import numpy as np
from common.realtime import sec_since_boot
from common.numpy_fast import clip
from selfdrive.swaglog import cloudlog
from selfdrive.modeld.constants import T_IDXS
from selfdrive.controls.lib.drive_helpers import MPC_COST_LONG, CONTROL_N
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU
from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
from casadi import SX, vertcat, sqrt, exp
LEAD_MPC_DIR = os.path.dirname(os.path.abspath(__file__))
EXPORT_DIR = os.path.join(LEAD_MPC_DIR, "c_generated_code")
JSON_FILE = "acados_ocp_lead.json"
MPC_T = list(np.arange(0,1.,.2)) + list(np.arange(1.,10.6,.6))
N = len(MPC_T) - 1
def desired_follow_distance(v_ego, v_lead):
TR = 1.8
G = 9.81
return (v_ego * TR - (v_lead - v_ego) * TR + v_ego * v_ego / (2 * G) - v_lead * v_lead / (2 * G)) + 4.0
def gen_lead_model():
model = AcadosModel()
model.name = 'lead'
# set up states & controls
x_ego = SX.sym('x_ego')
v_ego = SX.sym('v_ego')
a_ego = SX.sym('a_ego')
model.x = vertcat(x_ego, v_ego, a_ego)
# controls
j_ego = SX.sym('j_ego')
model.u = vertcat(j_ego)
# xdot
x_ego_dot = SX.sym('x_ego_dot')
v_ego_dot = SX.sym('v_ego_dot')
a_ego_dot = SX.sym('a_ego_dot')
model.xdot = vertcat(x_ego_dot, v_ego_dot, a_ego_dot)
# live parameters
x_lead = SX.sym('x_lead')
v_lead = SX.sym('v_lead')
model.p = vertcat(x_lead, v_lead)
# dynamics model
f_expl = vertcat(v_ego, a_ego, j_ego)
model.f_impl_expr = model.xdot - f_expl
model.f_expl_expr = f_expl
return model
def gen_lead_mpc_solver():
ocp = AcadosOcp()
ocp.model = gen_lead_model()
Tf = np.array(MPC_T)[-1]
# set dimensions
ocp.dims.N = N
# set cost module
ocp.cost.cost_type = 'NONLINEAR_LS'
ocp.cost.cost_type_e = 'NONLINEAR_LS'
QR = np.diag([0.0, 0.0, 0.0, 0.0])
Q = np.diag([0.0, 0.0, 0.0])
ocp.cost.W = QR
ocp.cost.W_e = Q
x_ego, v_ego, a_ego = ocp.model.x[0], ocp.model.x[1], ocp.model.x[2]
j_ego = ocp.model.u[0]
ocp.cost.yref = np.zeros((4, ))
ocp.cost.yref_e = np.zeros((3, ))
x_lead, v_lead = ocp.model.p[0], ocp.model.p[1]
desired_dist = desired_follow_distance(v_ego, v_lead)
dist_err = (desired_dist - (x_lead - x_ego))/(sqrt(v_ego + 0.5) + 0.1)
# TODO hacky weights to keep behavior the same
ocp.model.cost_y_expr = vertcat(exp(.3 * dist_err) - 1.,
((x_lead - x_ego) - (desired_dist)) / (0.05 * v_ego + 0.5),
a_ego * (.1 * v_ego + 1.0),
j_ego * (.1 * v_ego + 1.0))
ocp.model.cost_y_expr_e = vertcat(exp(.3 * dist_err) - 1.,
((x_lead - x_ego) - (desired_dist)) / (0.05 * v_ego + 0.5),
a_ego * (.1 * v_ego + 1.0))
ocp.parameter_values = np.array([0., .0])
# set constraints
ocp.constraints.constr_type = 'BGH'
ocp.constraints.idxbx = np.array([1,])
ocp.constraints.lbx = np.array([0,])
ocp.constraints.ubx = np.array([100.,])
x0 = np.array([0.0, 0.0, 0.0])
ocp.constraints.x0 = x0
ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM'
ocp.solver_options.hessian_approx = 'GAUSS_NEWTON'
ocp.solver_options.integrator_type = 'ERK'
ocp.solver_options.nlp_solver_type = 'SQP_RTI'
#ocp.solver_options.nlp_solver_tol_stat = 1e-3
#ocp.solver_options.tol = 1e-3
ocp.solver_options.qp_solver_iter_max = 10
#ocp.solver_options.qp_tol = 1e-3
# set prediction horizon
ocp.solver_options.tf = Tf
ocp.solver_options.shooting_nodes = np.array(MPC_T)
ocp.code_export_directory = EXPORT_DIR
return ocp
class LeadMpc():
def __init__(self, lead_id):
self.lead_id = lead_id
self.solver = AcadosOcpSolver('lead', N, EXPORT_DIR)
self.v_solution = [0.0 for i in range(N)]
self.a_solution = [0.0 for i in range(N)]
self.j_solution = [0.0 for i in range(N-1)]
yref = np.zeros((N+1,4))
self.solver.cost_set_slice(0, N, "yref", yref[:N])
self.solver.set(N, "yref", yref[N][:3])
self.x_sol = np.zeros((N+1, 3))
self.u_sol = np.zeros((N,1))
self.lead_xv = np.zeros((N+1,2))
self.reset()
self.set_weights()
def reset(self):
for i in range(N+1):
self.solver.set(i, 'x', np.zeros(3))
self.last_cloudlog_t = 0
self.status = False
self.new_lead = False
self.prev_lead_status = False
self.crashing = False
self.prev_lead_x = 10
self.solution_status = 0
self.x0 = np.zeros(3)
def set_weights(self):
W = np.diag([MPC_COST_LONG.TTC, MPC_COST_LONG.DISTANCE,
MPC_COST_LONG.ACCELERATION, MPC_COST_LONG.JERK])
Ws = np.tile(W[None], reps=(N,1,1))
self.solver.cost_set_slice(0, N, 'W', Ws, api='old')
#TODO hacky weights to keep behavior the same
self.solver.cost_set(N, 'W', (3./5.)*W[:3,:3])
def set_cur_state(self, v, a):
self.x0[1] = v
self.x0[2] = a
def extrapolate_lead(self, x_lead, v_lead, a_lead_0, a_lead_tau):
dt =.2
t = .0
for i in range(N+1):
if i > 4:
dt = .6
self.lead_xv[i, 0], self.lead_xv[i, 1] = x_lead, v_lead
a_lead = a_lead_0 * math.exp(-a_lead_tau * (t**2)/2.)
x_lead += v_lead * dt
v_lead += a_lead * dt
if v_lead < 0.0:
a_lead = 0.0
v_lead = 0.0
t += dt
def init_with_sim(self, v_ego, lead_xv, a_lead_0):
a_ego = min(0.0, -2 * (v_ego - lead_xv[0,1]) * (v_ego - lead_xv[0,1]) / (2.0 * lead_xv[0,0] + 0.01) + a_lead_0)
dt =.2
t = .0
x_ego = 0.0
for i in range(N+1):
if i > 4:
dt = .6
v_ego += a_ego * dt
if v_ego <= 0.0:
v_ego = 0.0
a_ego = 0.0
x_ego += v_ego * dt
t += dt
self.solver.set(i, 'x', np.array([x_ego, v_ego, a_ego]))
def update(self, carstate, radarstate, v_cruise):
self.crashing = False
v_ego = self.x0[1]
if self.lead_id == 0:
lead = radarstate.leadOne
else:
lead = radarstate.leadTwo
self.status = lead.status
if lead is not None and lead.status:
x_lead = lead.dRel
v_lead = max(0.0, lead.vLead)
a_lead = clip(lead.aLeadK, -5.0, 5.0)
# MPC will not converge if immidiate crash is expected
# Clip lead distance to what is still possible to brake for
MIN_ACCEL = -3.5
min_x_lead = ((v_ego + v_lead)/2) * (v_ego - v_lead) / (-MIN_ACCEL * 2)
if x_lead < min_x_lead:
x_lead = min_x_lead
self.crashing = True
if (v_lead < 0.1 or -a_lead / 2.0 > v_lead):
v_lead = 0.0
a_lead = 0.0
self.a_lead_tau = lead.aLeadTau
self.new_lead = False
self.extrapolate_lead(x_lead, v_lead, a_lead, self.a_lead_tau)
if not self.prev_lead_status or abs(x_lead - self.prev_lead_x) > 2.5:
self.init_with_sim(v_ego, self.lead_xv, a_lead)
self.new_lead = True
self.prev_lead_status = True
self.prev_lead_x = x_lead
else:
self.prev_lead_status = False
# Fake a fast lead car, so mpc keeps running
x_lead = 50.0
v_lead = v_ego + 10.0
a_lead = 0.0
self.a_lead_tau = _LEAD_ACCEL_TAU
self.extrapolate_lead(x_lead, v_lead, a_lead, self.a_lead_tau)
self.solver.constraints_set(0, "lbx", self.x0)
self.solver.constraints_set(0, "ubx", self.x0)
for i in range(N+1):
self.solver.set_param(i, self.lead_xv[i])
self.solution_status = self.solver.solve()
self.solver.fill_in_slice(0, N+1, 'x', self.x_sol)
self.solver.fill_in_slice(0, N, 'u', self.u_sol)
#self.solver.print_statistics()
self.v_solution = np.interp(T_IDXS[:CONTROL_N], MPC_T, list(self.x_sol[:,1]))
self.a_solution = np.interp(T_IDXS[:CONTROL_N], MPC_T, list(self.x_sol[:,2]))
self.j_solution = np.interp(T_IDXS[:CONTROL_N], MPC_T[:-1], list(self.u_sol[:,0]))
# Reset if goes through lead car
self.crashing = self.crashing or np.sum(self.lead_xv[:,0] - self.x_sol[:,0] < 0) > 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("Lead mpc %d reset, solution_status: %s" % (
self.lead_id, self.solution_status))
self.prev_lead_status = False
self.reset()
if __name__ == "__main__":
ocp = gen_lead_mpc_solver()
AcadosOcpSolver.generate(ocp, json_file=JSON_FILE, build=False)

@ -25,7 +25,15 @@ casadi_cost_0 = [
f'{gen}/long_cost/long_cost_y_0_hess.c',
]
build_files = [f'{gen}/acados_solver_long.c'] + casadi_model + casadi_cost_y + casadi_cost_e + casadi_cost_0
casadi_constraints = [
f'{gen}/long_constraints/long_constr_h_fun.c',
f'{gen}/long_constraints/long_constr_h_fun_jac_uxt_zt.c',
f'{gen}/long_constraints/long_constr_h_e_fun.c',
f'{gen}/long_constraints/long_constr_h_e_fun_jac_uxt_zt.c',
]
build_files = [f'{gen}/acados_solver_long.c'] + casadi_model + casadi_cost_y + casadi_cost_e + \
casadi_cost_0 + casadi_constraints
# extra generated files used to trigger a rebuild
generated_files = [
@ -37,6 +45,8 @@ generated_files = [
f'{gen}/long_model/long_expl_vde_adj.c',
f'{gen}/long_model/long_model.h',
f'{gen}/long_constraints/long_h_constraint.h',
f'{gen}/long_constraints/long_h_e_constraint.h',
f'{gen}/long_cost/long_cost_y_fun.h',
f'{gen}/long_cost/long_cost_y_e_fun.h',
f'{gen}/long_cost/long_cost_y_0_fun.h',

@ -1,23 +1,52 @@
#!/usr/bin/env python3
import os
import math
import numpy as np
from common.numpy_fast import clip
from common.realtime import sec_since_boot
from common.numpy_fast import clip
from selfdrive.swaglog import cloudlog
from selfdrive.modeld.constants import T_IDXS as T_IDXS_LST
from selfdrive.controls.lib.drive_helpers import LON_MPC_N as N
from selfdrive.modeld.constants import T_IDXS
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU
from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
from casadi import SX, vertcat
LONG_MPC_DIR = os.path.dirname(os.path.abspath(__file__))
EXPORT_DIR = os.path.join(LONG_MPC_DIR, "c_generated_code")
JSON_FILE = "acados_ocp_long.json"
SOURCES = ['lead0', 'lead1', 'cruise']
X_DIM = 3
U_DIM = 1
COST_E_DIM = 3
COST_DIM = COST_E_DIM + 1
MIN_ACCEL = -3.5
X_EGO_COST = 80.
X_EGO_E2E_COST = 100.
A_EGO_COST = .1
J_EGO_COST = .2
DANGER_ZONE_COST = 1e3
CRASH_DISTANCE = 1.5
LIMIT_COST = 1e6
T_IDXS = np.array(T_IDXS_LST)
T_REACT = 1.8
MAX_BRAKE = 9.81
def get_stopped_equivalence_factor(v_lead):
return T_REACT * v_lead + (v_lead*v_lead) / (2 * MAX_BRAKE)
def get_safe_obstacle_distance(v_ego):
return 2 * T_REACT * v_ego + (v_ego*v_ego) / (2 * MAX_BRAKE) + 4.0
def desired_follow_distance(v_ego, v_lead):
return get_safe_obstacle_distance(v_ego) - get_stopped_equivalence_factor(v_lead)
def gen_long_model():
model = AcadosModel()
@ -39,6 +68,12 @@ def gen_long_model():
a_ego_dot = SX.sym('a_ego_dot')
model.xdot = vertcat(x_ego_dot, v_ego_dot, a_ego_dot)
# live parameters
x_obstacle = SX.sym('x_obstacle')
a_min = SX.sym('a_min')
a_max = SX.sym('a_max')
model.p = vertcat(a_min, a_max, x_obstacle)
# dynamics model
f_expl = vertcat(v_ego, a_ego, j_ego)
model.f_impl_expr = model.xdot - f_expl
@ -50,7 +85,7 @@ def gen_long_mpc_solver():
ocp = AcadosOcp()
ocp.model = gen_long_model()
Tf = np.array(T_IDXS)[N]
Tf = T_IDXS[-1]
# set dimensions
ocp.dims.N = N
@ -59,8 +94,8 @@ def gen_long_mpc_solver():
ocp.cost.cost_type = 'NONLINEAR_LS'
ocp.cost.cost_type_e = 'NONLINEAR_LS'
QR = np.diag([0.0, 0.0, 0.0, 0.0])
Q = np.diag([0.0, 0.0, 0.0])
QR = np.zeros((COST_DIM, COST_DIM))
Q = np.zeros((COST_E_DIM, COST_E_DIM))
ocp.cost.W = QR
ocp.cost.W_e = Q
@ -68,110 +103,245 @@ def gen_long_mpc_solver():
x_ego, v_ego, a_ego = ocp.model.x[0], ocp.model.x[1], ocp.model.x[2]
j_ego = ocp.model.u[0]
ocp.cost.yref = np.zeros((4, ))
ocp.cost.yref_e = np.zeros((3, ))
# TODO hacky weights to keep behavior the same
ocp.model.cost_y_expr = vertcat(x_ego, v_ego, a_ego, j_ego)
ocp.model.cost_y_expr_e = vertcat(x_ego, v_ego, a_ego)
# set constraints
ocp.constraints.constr_type = 'BGH'
ocp.constraints.idxbx = np.array([0, 1,2])
ocp.constraints.lbx = np.array([0., 0, -1.2])
ocp.constraints.ubx = np.array([10000, 100., 1.2])
ocp.constraints.Jsbx = np.eye(3)
x0 = np.array([0.0, 0.0, 0.0])
a_min, a_max = ocp.model.p[0], ocp.model.p[1]
x_obstacle = ocp.model.p[2]
ocp.cost.yref = np.zeros((COST_DIM, ))
ocp.cost.yref_e = np.zeros((COST_E_DIM, ))
desired_dist_comfort = get_safe_obstacle_distance(v_ego)
desired_dist_danger = (7/8) * get_safe_obstacle_distance(v_ego)
costs = [((x_obstacle - x_ego) - (desired_dist_comfort)) / (v_ego + 10.),
x_ego,
a_ego * (v_ego + 10.),
j_ego * (v_ego + 10.)]
ocp.model.cost_y_expr = vertcat(*costs)
ocp.model.cost_y_expr_e = vertcat(*costs[:-1])
constraints = vertcat((v_ego),
(a_ego - a_min),
(a_max - a_ego),
((x_obstacle - x_ego) - (desired_dist_danger)) / (v_ego + 10.),
0.0)
ocp.model.con_h_expr = constraints
ocp.model.con_h_expr_e = constraints
x0 = np.zeros(X_DIM)
ocp.constraints.x0 = x0
ocp.parameter_values = np.array([-1.2, 1.2, 0.0])
l2_penalty = 1.0
# These constraints put hard limits on speed and
# acceleration as well as giving an assymetrical
# cost on approaching a lead. We only use lower
# bounds with an L2 cost.
l1_penalty = 0.0
weights = np.array([0.0, 1e4, 1e4])
ocp.cost.Zl = l2_penalty * weights
ocp.cost.Zu = l2_penalty * weights
l2_penalty = 1.0
weights = np.array([1e6, 1e6, 1e6, 0.0, 0.])
ocp.cost.zl = l1_penalty * weights
ocp.cost.zu = l1_penalty * weights
ocp.cost.Zl = l2_penalty * weights
ocp.cost.Zu = 0.0 * weights
ocp.cost.zu = 0.0 * weights
ocp.constraints.lh = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
ocp.constraints.lh_e = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
ocp.constraints.uh = np.array([1e3, 1e3, 1e3, 1e4, 1e4])
ocp.constraints.uh_e = np.array([1e3, 1e3, 1e3, 1e6, 1e6])
ocp.constraints.idxsh = np.array([0,1,2,3,4])
ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM'
ocp.solver_options.hessian_approx = 'GAUSS_NEWTON'
ocp.solver_options.integrator_type = 'ERK'
ocp.solver_options.nlp_solver_type = 'SQP_RTI'
ocp.solver_options.qp_solver_iter_max = 2
ocp.solver_options.qp_solver_iter_max = 3
# set prediction horizon
ocp.solver_options.tf = Tf
ocp.solver_options.shooting_nodes = np.array(T_IDXS)[:N+1]
ocp.solver_options.shooting_nodes = T_IDXS
ocp.code_export_directory = EXPORT_DIR
return ocp
class LongitudinalMpc():
def __init__(self):
self.solver = AcadosOcpSolver('long', N, EXPORT_DIR)
self.x_sol = np.zeros((N+1, 3))
self.u_sol = np.zeros((N, 1))
self.set_weights()
self.v_solution = [0.0 for i in range(len(T_IDXS))]
self.a_solution = [0.0 for i in range(len(T_IDXS))]
self.j_solution = [0.0 for i in range(len(T_IDXS)-1)]
self.yref = np.zeros((N+1, 4))
self.solver.cost_set_slice(0, N, "yref", self.yref[:N])
self.solver.cost_set(N, "yref", self.yref[N][:3])
self.T_IDXS = np.array(T_IDXS[:N+1])
self.min_a = -1.2
self.max_a = 1.2
self.mins = np.tile(np.array([0.0, 0.0, self.min_a])[None], reps=(N-1,1))
self.maxs = np.tile(np.array([0.0, 100.0, self.max_a])[None], reps=(N-1,1))
self.x0 = np.zeros(3)
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 = AcadosOcpSolver('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))
self.solver.cost_set_slice(0, N, "yref", self.yref[:N])
self.solver.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 = True
self.status = False
self.new_lead = False
self.prev_lead_status = False
self.crashing = False
self.prev_lead_x = 10
self.solution_status = 0
for i in range(N+1):
self.solver.set(i, 'x', self.x0)
self.x0 = np.zeros(X_DIM)
self.set_weights()
def set_weights(self):
W = np.diag([0.0, 1.0, 0.0, 50.0])
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.diag([X_EGO_COST, 0.0, A_EGO_COST, J_EGO_COST])
Ws = np.tile(W[None], reps=(N,1,1))
self.solver.cost_set_slice(0, N, 'W', Ws, api='old')
#TODO hacky weights to keep behavior the same
self.solver.cost_set(N, 'W', (3/20.)*W[:3,:3])
self.solver.cost_set(N, 'W', (3./5.)*np.copy(W[:COST_E_DIM, :COST_E_DIM]))
def set_accel_limits(self, min_a, max_a):
self.min_a = min_a
self.max_a = max_a
self.mins[:,2] = self.min_a
self.maxs[:,2] = self.max_a
self.solver.constraints_set_slice(1, N, "lbx", self.mins, api='old')
self.solver.constraints_set_slice(1, N, "ubx", self.maxs, api='old')
Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST, 0.])
Zls = np.tile(Zl[None], reps=(N+1,1,1))
self.solver.cost_set_slice(0, N+1, 'Zl', Zls, api='old')
def set_weights_for_xva_policy(self):
W = np.diag([0.0, X_EGO_E2E_COST, 0., J_EGO_COST])
Ws = np.tile(W[None], reps=(N,1,1))
self.solver.cost_set_slice(0, N, 'W', Ws, api='old')
self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))
Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, 0.0, 0.])
Zls = np.tile(Zl[None], reps=(N+1,1,1))
self.solver.cost_set_slice(0, N+1, 'Zl', Zls, api='old')
def set_cur_state(self, v, a):
self.x0[1] = v
self.x0[2] = 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_0, a_lead_tau):
lead_xv = np.zeros((N+1,2))
lead_xv[0, 0], lead_xv[0, 1] = x_lead, v_lead
for i in range(1, N+1):
dt = T_IDXS[i] - T_IDXS[i-1]
a_lead = a_lead_0 * math.exp(-a_lead_tau * (T_IDXS[i]**2)/2.)
x_lead += v_lead * dt
v_lead += a_lead * dt
if v_lead < 0.0:
a_lead = 0.0
v_lead = 0.0
lead_xv[i, 0], lead_xv[i, 1] = x_lead, v_lead
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 = max(0.0, lead.vLead)
a_lead = clip(lead.aLeadK, -10.0, 5.0)
# MPC will not converge if immidiate 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)
if x_lead < min_x_lead:
x_lead = min_x_lead
self.crashing = True
if (v_lead < 0.1 or -a_lead / 2.0 > v_lead):
v_lead = 0.0
a_lead = 0.0
self.a_lead_tau = lead.aLeadTau
self.new_lead = False
lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, self.a_lead_tau)
if not self.prev_lead_status or abs(x_lead - self.prev_lead_x) > 2.5:
self.new_lead = True
self.prev_lead_status = True
self.prev_lead_x = x_lead
else:
self.prev_lead_status = False
# 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
self.a_lead_tau = _LEAD_ACCEL_TAU
lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, self.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.crashing = False
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)
self.accel_limit_arr[:,0] = np.interp(float(self.status), [0.0, 1.0], [self.cruise_min_a, MIN_ACCEL])
self.accel_limit_arr[:,1] = self.cruise_max_a
# To consider 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
# TODO find cleaner way to write hacky fake cruise obstacle
cruise_lower_bound = v_ego - (1.0 + ((v_ego + 15)/60) * T_IDXS)
cruise_upper_bound = v_ego + (.7 + .7*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])]
x_obstacle = np.min(x_obstacles, axis=1)
self.params = np.concatenate([self.accel_limit_arr,
x_obstacle[:,None]], axis=1)
self.run()
self.crashing = self.crashing or np.sum(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) > 0
def update_with_xva(self, x, v, a):
self.yref[:,1] = x
self.solver.cost_set_slice(0, N, "yref", self.yref[:N], api='old')
self.solver.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_param(i, self.params[i])
self.solver.constraints_set(0, "lbx", self.x0)
self.solver.constraints_set(0, "ubx", self.x0)
def update(self, carstate, model, v_cruise):
v_cruise_clipped = clip(v_cruise, self.x0[1] - 10., self.x0[1] + 10.0)
position = v_cruise_clipped * self.T_IDXS
speed = v_cruise_clipped * np.ones(N+1)
accel = np.zeros(N+1)
self.update_with_xva(position, speed, accel)
def update_with_xva(self, position, speed, accel):
self.yref[:,0] = position
self.yref[:,1] = speed
self.yref[:,2] = accel
self.solver.cost_set_slice(0, N, "yref", self.yref[:N])
self.solver.cost_set(N, "yref", self.yref[N][:3])
self.solution_status = self.solver.solve()
self.solver.fill_in_slice(0, N+1, 'x', self.x_sol)
self.solver.fill_in_slice(0, N, 'u', self.u_sol)
#self.solver.print_statistics()
self.v_solution = list(self.x_sol[:,1])
self.a_solution = list(self.x_sol[:,2])
@ -181,7 +351,9 @@ class LongitudinalMpc():
if self.solution_status != 0:
if t > self.last_cloudlog_t + 5.0:
self.last_cloudlog_t = t
cloudlog.warning(f'Longitudinal model mpc reset, solution status: {self.solution_status}')
cloudlog.warning("Long mpc reset, solution_status: %s" % (
self.solution_status))
self.prev_lead_status = False
self.reset()

@ -11,7 +11,6 @@ from selfdrive.modeld.constants import T_IDXS
from selfdrive.config import Conversions as CV
from selfdrive.controls.lib.fcw import FCWChecker
from selfdrive.controls.lib.longcontrol import LongCtrlState
from selfdrive.controls.lib.lead_mpc_lib.lead_mpc import LeadMpc
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N
from selfdrive.swaglog import cloudlog
@ -47,17 +46,13 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
class Planner():
def __init__(self, CP, init_v=0.0, init_a=0.0):
self.CP = CP
self.mpcs = {}
self.mpcs['lead0'] = LeadMpc(0)
self.mpcs['lead1'] = LeadMpc(1)
self.mpcs['cruise'] = LongitudinalMpc()
self.mpc = LongitudinalMpc()
self.fcw = False
self.fcw_checker = FCWChecker()
self.v_desired = init_v
self.a_desired = init_a
self.longitudinalPlanSource = 'cruise'
self.alpha = np.exp(-DT_MDL/2.0)
self.lead_0 = log.ModelDataV2.LeadDataV3.new_message()
self.lead_1 = log.ModelDataV2.LeadDataV3.new_message()
@ -100,23 +95,18 @@ class Planner():
# clip limits, cannot init MPC outside of bounds
accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05)
accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05)
self.mpcs['cruise'].set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
next_a = np.inf
for key in self.mpcs:
self.mpcs[key].set_cur_state(self.v_desired, self.a_desired)
self.mpcs[key].update(sm['carState'], sm['radarState'], v_cruise)
if self.mpcs[key].status and self.mpcs[key].a_solution[5] < next_a: # picks slowest solution from accel in ~0.2 seconds
self.longitudinalPlanSource = key
self.v_desired_trajectory = self.mpcs[key].v_solution[:CONTROL_N]
self.a_desired_trajectory = self.mpcs[key].a_solution[:CONTROL_N]
self.j_desired_trajectory = self.mpcs[key].j_solution[:CONTROL_N]
next_a = self.mpcs[key].a_solution[5]
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
self.mpc.set_cur_state(self.v_desired, self.a_desired)
self.mpc.update(sm['carState'], sm['radarState'], v_cruise)
self.v_desired_trajectory = self.mpc.v_solution[:CONTROL_N]
self.a_desired_trajectory = self.mpc.a_solution[:CONTROL_N]
self.j_desired_trajectory = self.mpc.j_solution[:CONTROL_N]
# determine fcw
if self.mpcs['lead0'].new_lead:
if self.mpc.new_lead:
self.fcw_checker.reset_lead(cur_time)
blinkers = sm['carState'].leftBlinker or sm['carState'].rightBlinker
self.fcw = self.fcw_checker.update(self.mpcs['lead0'].x_sol[:,2], cur_time,
self.fcw = self.fcw_checker.update(self.mpc.x_sol[:,2], cur_time,
sm['controlsState'].active,
v_ego, sm['carState'].aEgo,
self.lead_1.dRel, self.lead_1.vLead, self.lead_1.aLeadK,
@ -143,8 +133,8 @@ class Planner():
longitudinalPlan.accels = [float(x) for x in self.a_desired_trajectory]
longitudinalPlan.jerks = [float(x) for x in self.j_desired_trajectory]
longitudinalPlan.hasLead = self.mpcs['lead0'].status
longitudinalPlan.longitudinalPlanSource = self.longitudinalPlanSource
longitudinalPlan.hasLead = self.mpc.prev_lead_status
longitudinalPlan.longitudinalPlanSource = self.mpc.source
longitudinalPlan.fcw = self.fcw
pm.send('longitudinalPlan', plan_send)

@ -2,10 +2,10 @@
import unittest
import numpy as np
from selfdrive.controls.lib.lead_mpc_lib.lead_mpc import desired_follow_distance
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance
from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
def run_following_distance_simulation(v_lead, t_end=150.0):
def run_following_distance_simulation(v_lead, t_end=100.0):
man = Maneuver(
'',
duration=t_end,
@ -29,7 +29,7 @@ class TestFollowingDistance(unittest.TestCase):
simulation_steady_state = run_following_distance_simulation(v_lead)
correct_steady_state = desired_follow_distance(v_lead, v_lead)
self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=.2)
self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(correct_steady_state*.1 + .3))
if __name__ == "__main__":

@ -9,9 +9,9 @@ from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
# TODO: make new FCW tests
maneuvers = [
Maneuver(
'approach stopped car at 30m/s',
'approach stopped car at 20m/s',
duration=20.,
initial_speed=30.,
initial_speed=25.,
lead_relevancy=True,
initial_distance_lead=120.,
speed_lead_values=[30., 0.],
@ -22,7 +22,7 @@ maneuvers = [
duration=20.,
initial_speed=20.,
lead_relevancy=True,
initial_distance_lead=60.,
initial_distance_lead=90.,
speed_lead_values=[20., 0.],
breakpoints=[0., 1.],
),
@ -54,22 +54,26 @@ maneuvers = [
breakpoints=[0., 15., 21.66],
),
Maneuver(
'steady state following a car at 20m/s, then lead decel to 0mph at 5m/s^2',
'steady state following a car at 20m/s, then lead decel to 0mph at 4m/s^2',
duration=40.,
initial_speed=20.,
lead_relevancy=True,
initial_distance_lead=35.,
speed_lead_values=[20., 20., 0.],
breakpoints=[0., 15., 19.],
prob_lead_values=[0., 1., 1.],
cruise_values=[20., 20., 20.],
breakpoints=[2., 2.01, 7.01],
),
Maneuver(
"approach stopped car at 20m/s",
duration=30.,
initial_speed=20.,
lead_relevancy=True,
initial_distance_lead=50.,
speed_lead_values=[0., 0.],
breakpoints=[1., 11.],
initial_distance_lead=120.,
speed_lead_values=[0.0, 0., 0.],
prob_lead_values=[0.0, 0., 1.],
cruise_values=[20., 20., 20.],
breakpoints=[0.0, 2., 2.01],
),
Maneuver(
"approach slower cut-in car at 20m/s",
@ -92,6 +96,16 @@ maneuvers = [
breakpoints=[1., 11.],
only_radar=True,
),
Maneuver(
"NaN recovery",
duration=30.,
initial_speed=15.,
lead_relevancy=True,
initial_distance_lead=60.,
speed_lead_values=[0., 0., 0.0],
breakpoints=[1., 1.01, 11.],
cruise_values=[float("nan"), 15., 15.],
),
]
@ -118,7 +132,7 @@ def run_maneuver_worker(k):
man = maneuvers[k]
print(man.title)
valid, _ = man.evaluate()
self.assertTrue(valid)
self.assertTrue(valid, msg=man.title)
return run

@ -1 +1 @@
8c733450bb28bcdb11d6b9991c8784e1f720f7b2
2282e3f208438237fe61d7bf636d6ad6b507c571

@ -23,7 +23,7 @@ PROCS = {
"selfdrive.controls.controlsd": 50.0,
"./loggerd": 45.0,
"./locationd": 9.1,
"selfdrive.controls.plannerd": 33.0,
"selfdrive.controls.plannerd": 27.0,
"./_ui": 15.0,
"selfdrive.locationd.paramsd": 9.1,
"./camerad": 7.07,
@ -55,7 +55,7 @@ if TICI:
"selfdrive.controls.controlsd": 28.0,
"./camerad": 31.0,
"./_ui": 21.0,
"selfdrive.controls.plannerd": 15.0,
"selfdrive.controls.plannerd": 14.0,
"selfdrive.locationd.paramsd": 5.0,
"./_dmonitoringmodeld": 10.0,
"selfdrive.thermald.thermald": 1.5,

Loading…
Cancel
Save