3535X_EGO_COST = 0.
3636V_EGO_COST = 0.
3737A_EGO_COST = 0.
38- J_EGO_COST = 5.0
38+ J_EGO_COST = 5.
3939A_CHANGE_COST = 200.
4040DANGER_ZONE_COST = 100.
4141CRASH_DISTANCE = .25
4242LEAD_DANGER_FACTOR = 0.75
4343LIMIT_COST = 1e6
4444ACADOS_SOLVER_TYPE = 'SQP_RTI'
4545
46-
4746# Fewer timestamps don't hurt performance and lead to
4847# much better convergence of the MPC with low iterations
4948N = 12
5756STOP_DISTANCE = 6.0
5857CRUISE_MIN_ACCEL = - 1.2
5958CRUISE_MAX_ACCEL = 1.6
59+ MIN_X_LEAD_FACTOR = 0.5
6060
6161def get_jerk_factor (personality = log .LongitudinalPersonality .standard ):
6262 if personality == log .LongitudinalPersonality .relaxed :
@@ -85,20 +85,12 @@ def get_stopped_equivalence_factor(v_lead):
8585def get_safe_obstacle_distance (v_ego , t_follow ):
8686 return (v_ego ** 2 ) / (2 * COMFORT_BRAKE ) + t_follow * v_ego + STOP_DISTANCE
8787
88- def desired_follow_distance (v_ego , v_lead , t_follow = None ):
89- if t_follow is None :
90- t_follow = get_T_FOLLOW ()
91- return get_safe_obstacle_distance (v_ego , t_follow ) - get_stopped_equivalence_factor (v_lead )
92-
93-
9488def gen_long_model ():
9589 model = AcadosModel ()
9690 model .name = MODEL_NAME
9791
98- # set up states & controls
99- x_ego = SX .sym ('x_ego' )
100- v_ego = SX .sym ('v_ego' )
101- a_ego = SX .sym ('a_ego' )
92+ # states
93+ x_ego , v_ego , a_ego = SX .sym ('x_ego' ), SX .sym ('v_ego' ), SX .sym ('a_ego' )
10294 model .x = vertcat (x_ego , v_ego , a_ego )
10395
10496 # controls
@@ -126,7 +118,6 @@ def gen_long_model():
126118 model .f_expl_expr = f_expl
127119 return model
128120
129-
130121def gen_long_ocp ():
131122 ocp = AcadosOcp ()
132123 ocp .model = gen_long_model ()
@@ -222,30 +213,31 @@ def gen_long_ocp():
222213
223214
224215class LongitudinalMpc :
225- def __init__ (self , mode = 'acc' , dt = DT_MDL ):
226- self .mode = mode
216+ def __init__ (self , dt = DT_MDL ):
227217 self .dt = dt
228218 self .solver = AcadosOcpSolverCython (MODEL_NAME , ACADOS_SOLVER_TYPE , N )
229219 self .reset ()
230220 self .source = SOURCES [2 ]
231221
232222 def reset (self ):
233- # self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
234223 self .solver .reset ()
235- # self.solver.options_set('print_level', 2)
224+
225+ self .x_sol = np .zeros ((N + 1 , X_DIM ))
226+ self .u_sol = np .zeros ((N , 1 ))
236227 self .v_solution = np .zeros (N + 1 )
237228 self .a_solution = np .zeros (N + 1 )
238- self .prev_a = np .array (self .a_solution )
239229 self .j_solution = np .zeros (N )
230+ self .prev_a = np .array (self .a_solution )
240231 self .yref = np .zeros ((N + 1 , COST_DIM ))
232+
241233 for i in range (N ):
242234 self .solver .cost_set (i , "yref" , self .yref [i ])
243235 self .solver .cost_set (N , "yref" , self .yref [N ][:COST_E_DIM ])
244- self .x_sol = np .zeros ((N + 1 , X_DIM ))
245- self .u_sol = np .zeros ((N ,1 ))
236+
246237 self .params = np .zeros ((N + 1 , PARAM_DIM ))
247238 for i in range (N + 1 ):
248239 self .solver .set (i , 'x' , np .zeros (X_DIM ))
240+
249241 self .last_cloudlog_t = 0
250242 self .status = False
251243 self .crash_cnt = 0.0
@@ -276,16 +268,9 @@ def set_cost_weights(self, cost_weights, constraint_cost_weights):
276268
277269 def set_weights (self , prev_accel_constraint = True , personality = log .LongitudinalPersonality .standard ):
278270 jerk_factor = get_jerk_factor (personality )
279- if self .mode == 'acc' :
280- a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0
281- cost_weights = [X_EGO_OBSTACLE_COST , X_EGO_COST , V_EGO_COST , A_EGO_COST , jerk_factor * a_change_cost , jerk_factor * J_EGO_COST ]
282- constraint_cost_weights = [LIMIT_COST , LIMIT_COST , LIMIT_COST , DANGER_ZONE_COST ]
283- elif self .mode == 'blended' :
284- a_change_cost = 40.0 if prev_accel_constraint else 0
285- cost_weights = [0. , 0.1 , 0.2 , 5.0 , a_change_cost , 1.0 ]
286- constraint_cost_weights = [LIMIT_COST , LIMIT_COST , LIMIT_COST , DANGER_ZONE_COST ]
287- else :
288- raise NotImplementedError (f'Planner mode { self .mode } not recognized in planner cost set' )
271+ a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0
272+ cost_weights = [X_EGO_OBSTACLE_COST , X_EGO_COST , V_EGO_COST , A_EGO_COST , jerk_factor * a_change_cost , jerk_factor * J_EGO_COST ]
273+ constraint_cost_weights = [LIMIT_COST , LIMIT_COST , LIMIT_COST , DANGER_ZONE_COST ]
289274 self .set_cost_weights (cost_weights , constraint_cost_weights )
290275
291276 def set_cur_state (self , v , a ):
@@ -320,14 +305,14 @@ def process_lead(self, lead):
320305
321306 # MPC will not converge if immediate crash is expected
322307 # Clip lead distance to what is still possible to brake for
323- min_x_lead = (( v_ego + v_lead ) / 2 ) * (v_ego - v_lead ) / (- ACCEL_MIN * 2 )
308+ min_x_lead = MIN_X_LEAD_FACTOR * ( v_ego + v_lead ) * (v_ego - v_lead ) / (- ACCEL_MIN * 2 )
324309 x_lead = np .clip (x_lead , min_x_lead , 1e8 )
325310 v_lead = np .clip (v_lead , 0.0 , 1e8 )
326311 a_lead = np .clip (a_lead , - 10. , 5. )
327312 lead_xv = self .extrapolate_lead (x_lead , v_lead , a_lead , a_lead_tau )
328313 return lead_xv
329314
330- def update (self , radarstate , v_cruise , x , v , a , j , personality = log .LongitudinalPersonality .standard ):
315+ def update (self , radarstate , v_cruise , personality = log .LongitudinalPersonality .standard ):
331316 t_follow = get_T_FOLLOW (personality )
332317 v_ego = self .x0 [1 ]
333318 self .status = radarstate .leadOne .status or radarstate .leadTwo .status
@@ -341,56 +326,28 @@ def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalP
341326 lead_0_obstacle = lead_xv_0 [:,0 ] + get_stopped_equivalence_factor (lead_xv_0 [:,1 ])
342327 lead_1_obstacle = lead_xv_1 [:,0 ] + get_stopped_equivalence_factor (lead_xv_1 [:,1 ])
343328
344- self .params [:,0 ] = ACCEL_MIN
345- self .params [:,1 ] = ACCEL_MAX
346-
347- # Update in ACC mode or ACC/e2e blend
348- if self .mode == 'acc' :
349- self .params [:,5 ] = LEAD_DANGER_FACTOR
350-
351- # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
352- # when the leads are no factor.
353- v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05 )
354- # TODO does this make sense when max_a is negative?
355- v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05 )
356- v_cruise_clipped = np .clip (v_cruise * np .ones (N + 1 ),
357- v_lower ,
358- v_upper )
359- cruise_obstacle = np .cumsum (T_DIFFS * v_cruise_clipped ) + get_safe_obstacle_distance (v_cruise_clipped , t_follow )
360- x_obstacles = np .column_stack ([lead_0_obstacle , lead_1_obstacle , cruise_obstacle ])
361- self .source = SOURCES [np .argmin (x_obstacles [0 ])]
362-
363- # These are not used in ACC mode
364- x [:], v [:], a [:], j [:] = 0.0 , 0.0 , 0.0 , 0.0
365-
366- elif self .mode == 'blended' :
367- self .params [:,5 ] = 1.0
368-
369- x_obstacles = np .column_stack ([lead_0_obstacle ,
370- lead_1_obstacle ])
371- cruise_target = T_IDXS * np .clip (v_cruise , v_ego - 2.0 , 1e3 ) + x [0 ]
372- xforward = ((v [1 :] + v [:- 1 ]) / 2 ) * (T_IDXS [1 :] - T_IDXS [:- 1 ])
373- x = np .cumsum (np .insert (xforward , 0 , x [0 ]))
374-
375- x_and_cruise = np .column_stack ([x , cruise_target ])
376- x = np .min (x_and_cruise , axis = 1 )
329+ # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
330+ # when the leads are no factor.
331+ v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05 )
332+ # TODO does this make sense when max_a is negative?
333+ v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05 )
334+ v_cruise_clipped = np .clip (v_cruise * np .ones (N + 1 ), v_lower , v_upper )
335+ cruise_obstacle = np .cumsum (T_DIFFS * v_cruise_clipped ) + get_safe_obstacle_distance (v_cruise_clipped , t_follow )
377336
378- self .source = 'e2e' if x_and_cruise [1 ,0 ] < x_and_cruise [1 ,1 ] else 'cruise'
337+ x_obstacles = np .column_stack ([lead_0_obstacle , lead_1_obstacle , cruise_obstacle ])
338+ self .source = SOURCES [np .argmin (x_obstacles [0 ])]
379339
380- else :
381- raise NotImplementedError (f'Planner mode { self .mode } not recognized in planner update' )
382-
383- self .yref [:,1 ] = x
384- self .yref [:,2 ] = v
385- self .yref [:,3 ] = a
386- self .yref [:,5 ] = j
340+ self .yref [:,:] = 0.0
387341 for i in range (N ):
388342 self .solver .set (i , "yref" , self .yref [i ])
389343 self .solver .set (N , "yref" , self .yref [N ][:COST_E_DIM ])
390344
345+ self .params [:,0 ] = ACCEL_MIN
346+ self .params [:,1 ] = ACCEL_MAX
391347 self .params [:,2 ] = np .min (x_obstacles , axis = 1 )
392348 self .params [:,3 ] = np .copy (self .prev_a )
393349 self .params [:,4 ] = t_follow
350+ self .params [:,5 ] = LEAD_DANGER_FACTOR
394351
395352 self .run ()
396353 if (np .any (lead_xv_0 [FCW_IDXS ,0 ] - self .x_sol [FCW_IDXS ,0 ] < CRASH_DISTANCE ) and
@@ -399,18 +356,7 @@ def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalP
399356 else :
400357 self .crash_cnt = 0
401358
402- # Check if it got within lead comfort range
403- # TODO This should be done cleaner
404- if self .mode == 'blended' :
405- if any ((lead_0_obstacle - get_safe_obstacle_distance (self .x_sol [:,1 ], t_follow ))- self .x_sol [:,0 ] < 0.0 ):
406- self .source = 'lead0'
407- if any ((lead_1_obstacle - get_safe_obstacle_distance (self .x_sol [:,1 ], t_follow ))- self .x_sol [:,0 ] < 0.0 ) and \
408- (lead_1_obstacle [0 ] - lead_0_obstacle [0 ]):
409- self .source = 'lead1'
410-
411359 def run (self ):
412- # t0 = time.monotonic()
413- # reset = 0
414360 for i in range (N + 1 ):
415361 self .solver .set (i , 'p' , self .params [i ])
416362 self .solver .constraints_set (0 , "lbx" , self .x0 )
@@ -422,13 +368,6 @@ def run(self):
422368 self .time_linearization = float (self .solver .get_stats ('time_lin' )[0 ])
423369 self .time_integrator = float (self .solver .get_stats ('time_sim' )[0 ])
424370
425- # qp_iter = self.solver.get_stats('statistics')[-1][-1] # SQP_RTI specific
426- # print(f"long_mpc timings: tot {self.solve_time:.2e}, qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e}, \
427- # integrator {self.time_integrator:.2e}, qp_iter {qp_iter}")
428- # res = self.solver.get_residuals()
429- # print(f"long_mpc residuals: {res[0]:.2e}, {res[1]:.2e}, {res[2]:.2e}, {res[3]:.2e}")
430- # self.solver.print_statistics()
431-
432371 for i in range (N + 1 ):
433372 self .x_sol [i ] = self .solver .get (i , 'x' )
434373 for i in range (N ):
@@ -446,12 +385,8 @@ def run(self):
446385 self .last_cloudlog_t = t
447386 cloudlog .warning (f"Long mpc reset, solution_status: { self .solution_status } " )
448387 self .reset ()
449- # reset = 1
450- # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(time.monotonic() - t0):.2e} qp {self.time_qp_solution:.2e}, \
451- # lin {self.time_linearization:.2e} qp_iter {qp_iter}, reset {reset}")
452388
453389
454390if __name__ == "__main__" :
455391 ocp = gen_long_ocp ()
456392 AcadosOcpSolver .generate (ocp , json_file = JSON_FILE )
457- # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True)
0 commit comments