@@ -295,150 +295,6 @@ def dynamics_constraints(p):
295295
296296 return x_full , y_full , psi_full , c_full , v_full , eps_ , final_error
297297
298- ######## more dynamics version
299- def trajectory_generation_dynamics (init_state , final_state , N = 30 , Lr = 1.5 ,
300- a_min = - 3.0 , a_max = 3.0 ,
301- eps_min = - 0.2 , eps_max = 0.2 ,
302- v_min = 2.0 , v_max = 11.0 ,
303- T_min = 0.5 , T_max = 1000.0 ,
304- waypoints = None , waypoint_penalty_weight = 10 , waypoint_headings = None ):
305- """
306- Generate a dynamically feasible trajectory between init_state and final_state
307- using curvature-based vehicle dynamics and nonlinear optimization.
308-
309- Now supports multiple waypoints.
310-
311- Parameters:
312- - init_state (dict): Initial vehicle state with keys 'x', 'y', 'psi', 'c', 'v'.
313- - final_state (dict): Target vehicle state with keys 'x', 'y', 'psi', 'c'.
314- - N (int): Number of discrete time steps in the trajectory.
315- - T (float): Duration of each time step (in seconds).
316- - Lr (float): Distance from the vehicle's center to the rear axle.
317- - w_c (float): Weight for penalizing curvature (smoothness of turns).
318- - w_eps (float): Weight for penalizing curvature rate (reduces sharp steering changes).
319- - w_vvar (float): Weight for penalizing speed variance (encourages speed smoothness).
320- - w_terminal (float): Weight for penalizing final state deviation (soft constraint).
321- - v_min (float): Minimum allowed speed (in m/s).
322- - v_max (float): Maximum allowed speed (in m/s).
323- - waypoints (list or None): Optional list of (x, y) coordinates that the trajectory should pass near.
324- - waypoint_penalty_weight (float): Penalty weight for distance from waypoints (soft constraint).
325-
326- Returns:
327- - x, y, psi, c, v, eps (np.ndarray): Arrays of optimized state and control values.
328- - final_error (dict): Final state errors in x, y, psi, and c.
329- """
330- def cost (p ):
331- T_total = p [- 1 ]
332- return T_total
333-
334- def dynamics_constraints (p ):
335- # x_, y_, psi_, c_, v_, eps_ = np.split(p, [N - 1, 2 * (N - 1), 3 * (N - 1), 4 * (N - 1), 5 * (N - 1)])
336- # print("x_: " +str(x_))
337- split_idx = 5 * (N - 1 )
338- x_ , y_ , psi_ , c_ , v_ = np .split (p [:split_idx ], 5 )
339-
340- a = p [split_idx :split_idx + (N - 1 )]
341- eps = p [split_idx + (N - 1 ):- 1 ]
342- T_total = p [- 1 ]
343- T_k = T_total / (N - 1 )
344-
345- constraints = []
346- x_prev , y_prev , psi_prev , c_prev , v_prev = init_state ['x' ], init_state ['y' ], init_state ['psi' ], init_state ['c' ], init_state ['v' ]
347-
348- for k in range (N - 1 ):
349- dx = v_prev * np .cos (psi_prev + c_prev * Lr ) * T_k
350- dy = v_prev * np .sin (psi_prev + c_prev * Lr ) * T_k
351- dpsi = v_prev * c_prev * T_k
352- dv = a [k ] * T_k
353- dc = eps [k ] * T_k
354- constraints .extend ([
355- x_ [k ] - (x_prev + dx ),
356- y_ [k ] - (y_prev + dy ),
357- psi_ [k ] - (psi_prev + dpsi ),
358- v_ [k ] - (v_prev + dv ),
359- c_ [k ] - (c_prev + dc )
360- ])
361- x_prev , y_prev , psi_prev , c_prev , v_prev = x_ [k ], y_ [k ], psi_ [k ], c_ [k ], v_ [k ]
362- return constraints
363-
364- def waypoint_penalty (p ):
365- if waypoints is None or len (waypoints ) == 0 :
366- return 0.0
367-
368- split_idx = 5 * (N - 1 )
369- x_ , y_ , psi_ , c_ , v_ = np .split (p [:split_idx ], 5 )
370- # x_ = p[:N -1]
371- # y_ = p[N - 1:2 * (N-1)]
372- # psi_ = [ 3 * (N - 1)]
373- # Calculate equally spaced indices for each waypoint
374- num_waypoints = len (waypoints )
375- indices = [int ((i + 1 ) * (N - 1 ) / (num_waypoints + 1 )) for i in range (num_waypoints )]
376- penalty = 0.0
377- alignment_threshold = 0.3
378- # Sum penalties for each waypoint at its corresponding index
379- for wp_idx , waypoint in enumerate (waypoints ):
380- traj_idx = indices [wp_idx ]
381- penalty += waypoint_penalty_weight * (
382- (x_ [traj_idx ] - waypoint [0 ])** 2 + (y_ [traj_idx ] - waypoint [1 ])** 2
383- )
384- return penalty
385-
386- # Initial guesses
387- x_vals = np .linspace (init_state ['x' ], final_state ['x' ], N )
388- y_vals = np .linspace (init_state ['y' ], final_state ['y' ], N )
389- psi_vals = np .linspace (init_state ['psi' ], final_state ['psi' ], N )
390- c_vals = np .linspace (init_state ['c' ], final_state ['c' ], N )
391- v_vals = np .ones (N ) * init_state ['v' ]
392-
393- a_vals = np .zeros (N - 1 )
394- eps_vals = np .zeros (N - 1 )
395- T_guess = 60.0
396-
397- p0 = np .concatenate ([x_vals [1 :], y_vals [1 :], psi_vals [1 :], c_vals [1 :], v_vals [1 :],
398- a_vals , eps_vals , [T_guess ]])
399- num_vars = len (p0 )
400- bounds = ([(None , None )] * (4 * (N - 1 )) +
401- [(v_min , v_max )] * (N - 1 ) +
402- [(a_min , a_max )] * (N - 1 ) +
403- [(eps_min , eps_max )] * (N - 1 ) +
404- [(T_min , T_max )]
405- )
406- def total_cost (p ):
407- return cost (p ) + waypoint_penalty (p )
408-
409- result = minimize (total_cost ,
410- p0 ,
411- bounds = bounds ,
412- constraints = {'type' : 'eq' , 'fun' : dynamics_constraints },
413- options = {'maxiter' : 1000 })
414-
415- # print("result.x:" + str(result.x))
416- if not result .success :
417- raise RuntimeError ("Optimization failed" )
418-
419- split_idx = 5 * (N - 1 )
420- x_ , y_ , psi_ , c_ , v_ = np .split (result .x [:split_idx ], 5 )
421-
422- a = result .x [split_idx :split_idx + (N - 1 )]
423- eps = result .x [split_idx + (N - 1 ):- 1 ]
424- T_total = result .x [- 1 ]
425-
426- x_full = np .concatenate (([init_state ['x' ]], x_ ))
427- y_full = np .concatenate (([init_state ['y' ]], y_ ))
428- psi_full = np .concatenate (([init_state ['psi' ]], psi_ ))
429- c_full = np .concatenate (([init_state ['c' ]], c_ ))
430- v_full = np .concatenate (([init_state ['v' ]], v_ ))
431-
432- final_error = {
433- 'x_error' : abs (x_full [- 1 ] - final_state ['x' ]),
434- 'y_error' : abs (y_full [- 1 ] - final_state ['y' ]),
435- 'psi_error' : abs (psi_full [- 1 ] - final_state ['psi' ]),
436- 'c_error' : abs (c_full [- 1 ] - final_state ['c' ]),
437- }
438-
439- return x_full , y_full , psi_full , c_full , v_full , a , eps , T_total , final_error
440- #########
441-
442298def feasibility_check (trajectory , cone_map , car_width = 2.0 , safety_margin = 0.3 , v = 10.0 , Lr = 1.5 , T = 0.1 ):
443299 """
444300 Check if the car trajectory collides with any cones.
@@ -833,127 +689,91 @@ def test_4_cone_slalom():
833689 def plot_trajectory (x , y , v , c , eps , waypoint = None ):
834690 plt .figure (figsize = (12 , 10 ))
835691
836- # Trajectory plot
837- plt .subplot (4 , 1 , 1 )
838- plt .plot (x , y , label = "Trajectory" )
839- plt .scatter ([x [0 ], x [- 1 ]], [y [0 ], y [- 1 ]], color = 'red' , label = "Start/End" )
840-
841- if waypoint is not None :
842- plt .scatter (* waypoint , color = 'purple' , s = 60 , marker = 'X' , label = "Waypoint" )
843- plt .annotate ("Waypoint" , (waypoint [0 ], waypoint [1 ]), textcoords = "offset points" , xytext = (5 ,5 ), color = 'purple' )
692+ # Trajectory plot
693+ plt .subplot (4 , 1 , 1 )
694+ plt .plot (x , y , label = "Trajectory" )
695+ plt .scatter ([x [0 ], x [- 1 ]], [y [0 ], y [- 1 ]], color = 'red' , label = "Start/End" )
696+
697+ if waypoint is not None :
698+ plt .scatter (* waypoint , color = 'purple' , s = 60 , marker = 'X' , label = "Waypoint" )
699+ plt .annotate ("Waypoint" , (waypoint [0 ], waypoint [1 ]), textcoords = "offset points" , xytext = (5 ,5 ), color = 'purple' )
844700
845- plt .axis ('equal' )
846- plt .ylabel ("y (m)" )
847- plt .title ("Trajectory" )
848- plt .grid (True )
849- plt .legend ()
701+ plt .axis ('equal' )
702+ plt .ylabel ("y (m)" )
703+ plt .title ("Trajectory" )
704+ plt .grid (True )
705+ plt .legend ()
850706
851- # Speed plot
852- plt .subplot (4 , 1 , 2 )
853- plt .plot (v , label = "Speed (v)" , color = "blue" )
854- plt .ylabel ("Speed (m/s)" )
855- plt .grid (True )
856- plt .legend ()
707+ # Speed plot
708+ plt .subplot (4 , 1 , 2 )
709+ plt .plot (v , label = "Speed (v)" , color = "blue" )
710+ plt .ylabel ("Speed (m/s)" )
711+ plt .grid (True )
712+ plt .legend ()
857713
858- # Curvature plot
859- plt .subplot (4 , 1 , 3 )
860- plt .plot (c , label = "Curvature (c)" , color = "orange" )
861- plt .ylabel ("Curvature (1/m)" )
862- plt .grid (True )
863- plt .legend ()
714+ # Curvature plot
715+ plt .subplot (4 , 1 , 3 )
716+ plt .plot (c , label = "Curvature (c)" , color = "orange" )
717+ plt .ylabel ("Curvature (1/m)" )
718+ plt .grid (True )
719+ plt .legend ()
864720
865- # Curvature rate plot
866- plt .subplot (4 , 1 , 4 )
867- plt .plot (eps , label = "Curvature Rate (ε)" , color = "green" )
868- plt .xlabel ("Step" )
869- plt .ylabel ("ε (1/m²)" )
870- plt .grid (True )
871- plt .legend ()
721+ # Curvature rate plot
722+ plt .subplot (4 , 1 , 4 )
723+ plt .plot (eps , label = "Curvature Rate (ε)" , color = "green" )
724+ plt .xlabel ("Step" )
725+ plt .ylabel ("ε (1/m²)" )
726+ plt .grid (True )
727+ plt .legend ()
872728
873729 plt .tight_layout ()
874730 plt .show ()
875731
876- ###### Test case 1: pass a cone in slalom
877- def trajectory_generation_test1 ():
878- # Init and final
879- init_state = {'x' : 0.0 , 'y' : 0.0 , 'psi' : 0.0 , 'c' : 0.0 , 'v' : 5.0 }
880- final_state = {'x' : 15.0 , 'y' : 0.0 , 'psi' : np .pi / 20000000 , 'c' : np .pi / 20000000 }
881- waypoint = (8.0 , 6.0 )
882-
883- # Solve
884- x , y , psi , c , v , eps , final_error = trajectory_generation (
885- init_state , final_state , waypoint = waypoint
886- )
887- plot_trajectory (x , y , v , c , eps , waypoint )
732+ # --- Test Case 1: pass a cone in slalom ---
733+ def trajectory_generation_test1 ():
734+ # Init and final
735+ init_state = {'x' : 0.0 , 'y' : 0.0 , 'psi' : 0.0 , 'c' : 0.0 , 'v' : 5.0 }
736+ final_state = {'x' : 15.0 , 'y' : 0.0 , 'psi' : np .pi / 20000000 , 'c' : np .pi / 20000000 }
737+ waypoint = (8.0 , 6.0 )
888738
889- # Error
890- print ("\n Final State Errors:" )
891- for k , e in final_error .items ():
892- print (f"{ k } : { e :.6f} " )
893-
894- ###### Test case 2: 90 degree turn
895- def trajectory_generation_test2 ():
896- # Init and final
897- init_state = {'x' : 0.0 , 'y' : 0.0 , 'psi' : 0.0 , 'c' : 0.0 , 'v' : 5.0 }
898- final_state = {'x' : 15.0 , 'y' : 15.0 , 'psi' : np .pi / 2 , 'c' : np .pi / 2 }
899- waypoint = (13.0 , 3.0 )
900-
901- # Solve
902- x , y , psi , c , v , eps , final_error = trajectory_generation (
903- init_state , final_state , waypoint = waypoint
904- )
905- plot_trajectory (x , y , v , c , eps , waypoint )
906-
907- # Error
908- print ("\n Final State Errors:" )
909- for k , e in final_error .items ():
910- print (f"{ k } : { e :.6f} " )
911-
912-
913-
914- def feasibility_check (trajectory , cone_map , car_width = 2.0 , safety_margin = 0.3 , v = 10.0 , Lr = 1.5 , T = 0.1 ):
915- """
916- Check if the car trajectory collides with any cones.
917-
918- Parameters:
919- - trajectory: list of (y, psi, c) states
920- - cone_map: list of (x, y) cone positions
921- - car_width: width of the vehicle in meters
922- - safety_margin: buffer around the vehicle
923- - v: vehicle constant speed (used for x position estimation)
924- - Lr: distance to rear axle
925- - T: time step
926-
927- Returns:
928- - feasible: True if no collisions
929- - collisions: list of indices of cones that were collided with (for plotting purpose)
930- - x_vals, y_vals: trajectory positions for plotting (for plotting purpose)
931- """
932- y_vals , psi_vals , c_vals = zip (* trajectory )
933- x_vals = [0.0 ]
934- for i in range (1 , len (trajectory )):
935- dx = v * np .cos (psi_vals [i - 1 ] + c_vals [i - 1 ] * Lr ) * T
936- x_vals .append (x_vals [- 1 ] + dx )
937-
938- collision_radius = (car_width / 2.0 ) + safety_margin
939- collisions = []
940-
941- for j , (cone_x , cone_y ) in enumerate (cone_map ):
942- for x , y in zip (x_vals , y_vals ):
943- if np .hypot (x - cone_x , y - cone_y ) < collision_radius :
944- collisions .append (j )
945- break
739+ # Solve
740+ x , y , psi , c , v , eps , final_error = trajectory_generation (
741+ init_state , final_state , waypoint = waypoint
742+ )
743+ plot_trajectory (x , y , v , c , eps , waypoint )
946744
947- feasible = len (collisions ) == 0
948- return feasible , collisions , x_vals , y_vals
745+ # Error
746+ print ("\n Final State Errors:" )
747+ for k , e in final_error .items ():
748+ print (f"{ k } : { e :.6f} " )
749+
750+ # --- Test case 2: 90 degree turn ---
751+ def trajectory_generation_test2 ():
752+ # Init and final
753+ init_state = {'x' : 0.0 , 'y' : 0.0 , 'psi' : 0.0 , 'c' : 0.0 , 'v' : 5.0 }
754+ final_state = {'x' : 15.0 , 'y' : 15.0 , 'psi' : np .pi / 2 , 'c' : np .pi / 2 }
755+ waypoint = (13.0 , 3.0 )
756+
757+ # Solve
758+ x , y , psi , c , v , eps , final_error = trajectory_generation (
759+ init_state , final_state , waypoint = waypoint
760+ )
761+ plot_trajectory (x , y , v , c , eps , waypoint )
949762
950- # --- Test Case ---
951- def test_feasibility_check ():
952- N = 50
953- y_traj = np .linspace (0 , 10 , N )
954- psi_traj = np .linspace (0 , 0.1 , N )
955- c_traj = np .linspace (0 , 0.2 , N )
956- trajectory = list (zip (y_traj , psi_traj , c_traj ))
763+ # Error
764+ print ("\n Final State Errors:" )
765+ for k , e in final_error .items ():
766+ print (f"{ k } : { e :.6f} " )
767+
768+ #############
769+ #########################
770+ # --- Test Case ---
771+ def test_feasibility_check ():
772+ N = 50
773+ y_traj = np .linspace (0 , 10 , N )
774+ psi_traj = np .linspace (0 , 0.1 , N )
775+ c_traj = np .linspace (0 , 0.2 , N )
776+ trajectory = list (zip (y_traj , psi_traj , c_traj ))
957777
958778 # Cone map near the path
959779 cone_map = [(5.0 , 1.0 ), (10.0 , 1.5 ), (15.0 , 2.0 ), (25.0 , 4.0 ), (25.0 , 10.0 ), (16.0 , 9.0 ), (40.0 , 5.0 )]
@@ -1206,8 +1026,8 @@ def test_planning(case='slalom', test_loop=2):
12061026 final_state = {'y' : wpt_fixed [1 ], 'psi' : 0.0 , 'c' : 0.0 }
12071027
12081028 y_traj , psi_traj , c_traj , eps_traj = trajectory_generation (init_state , final_state )
1209- plot_trajectory (y_traj , psi_traj , c_traj , label = "Generated trajectory" )
1210- plot_dynamics (psi_traj , c_traj , eps_traj )
1029+ # plot_trajectory(y_traj, psi_traj, c_traj, label="Generated trajectory")
1030+ # plot_dynamics(psi_traj, c_traj, eps_traj)
12111031
12121032 # Iterate
12131033 vehicle_state = drive (vehicle_state )
0 commit comments