├── .idea ├── .gitignore ├── MotionPlanning.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── Control ├── .gitattributes ├── LQR_Dynamics_Model.py ├── LQR_Kinematic_Model.py ├── MPC_Frenet_Frame.py ├── MPC_XY_Frame.py ├── Pure_Pursuit.py ├── Rear_Wheel_Feedback.py ├── Stanley.py ├── __pycache__ │ ├── config_control.cpython-37.pyc │ ├── draw.cpython-37.pyc │ ├── draw.cpython-38.pyc │ └── draw_lqr.cpython-37.pyc ├── config_control.py ├── draw.py ├── draw_lqr.py ├── fig │ ├── carmodel.png │ ├── frontwheel.png │ ├── ps.png │ ├── purepursuit.jpg │ ├── rearwheel.png │ └── simple car.jpg ├── gif │ ├── FWF.gif │ ├── LQR_Dynamics.gif │ ├── LQR_Kinematic.gif │ ├── MPC.gif │ ├── RWF1.gif │ ├── RWF2.gif │ ├── demo.gif │ ├── purepursuit1.gif │ ├── purepursuit2.gif │ └── stanley.gif └── utils.py ├── CurvesGenerator ├── __pycache__ │ ├── cubic_spline.cpython-37.pyc │ ├── cubic_spline.cpython-38.pyc │ ├── draw.cpython-37.pyc │ ├── quartic_polynomial.cpython-37.pyc │ ├── quintic_polynomial.cpython-37.pyc │ ├── reeds_shepp.cpython-37.pyc │ └── reeds_shepp.cpython-38.pyc ├── cubic_spline.py ├── draw.py ├── dubins_path.py ├── quartic_polynomial.py ├── quintic_polynomial.py └── reeds_shepp.py ├── HybridAstarPlanner ├── __pycache__ │ ├── astar.cpython-37.pyc │ ├── astar.cpython-38.pyc │ ├── draw.cpython-37.pyc │ ├── draw.cpython-38.pyc │ ├── hybrid_astar.cpython-37.pyc │ └── hybrid_astar.cpython-38.pyc ├── astar.py ├── draw.py ├── gif │ ├── hybrid Astar-1.gif │ ├── hybrid Astar-2.gif │ ├── hybrid Astar-t1.gif │ ├── hybrid Astar-t2.gif │ ├── hybrid Astar-t3.gif │ └── hybrid Astar-t5.gif ├── hybrid_astar.py └── hybrid_astar_with_trailer.py ├── LatticePlanner ├── __pycache__ │ ├── draw.cpython-37.pyc │ └── env.cpython-37.pyc ├── draw.py ├── env.py ├── gif │ ├── Crusing.gif │ └── StoppingMode.gif └── lattice_planner.py └── README.md /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/MotionPlanning.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Control/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Control/LQR_Dynamics_Model.py: -------------------------------------------------------------------------------- 1 | """ 2 | LQR and PID Controller 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | from enum import Enum 10 | import matplotlib.pyplot as plt 11 | 12 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 13 | "/../../MotionPlanning/") 14 | 15 | import Control.draw_lqr as draw 16 | from Control.config_control import * 17 | import CurvesGenerator.reeds_shepp as rs 18 | 19 | 20 | class Gear(Enum): 21 | GEAR_DRIVE = 1 22 | GEAR_REVERSE = 2 23 | 24 | 25 | class VehicleState: 26 | def __init__(self, x=0.0, y=0.0, yaw=0.0, 27 | v=0.0, gear=Gear.GEAR_DRIVE): 28 | self.x = x 29 | self.y = y 30 | self.yaw = yaw 31 | self.v = v 32 | self.e_cg = 0.0 33 | self.theta_e = 0.0 34 | 35 | self.gear = gear 36 | self.steer = 0.0 37 | 38 | def UpdateVehicleState(self, delta, a, e_cg, theta_e, 39 | gear=Gear.GEAR_DRIVE): 40 | """ 41 | update states of vehicle 42 | :param theta_e: yaw error to ref trajectory 43 | :param e_cg: lateral error to ref trajectory 44 | :param delta: steering angle [rad] 45 | :param a: acceleration [m / s^2] 46 | :param gear: gear mode [GEAR_DRIVE / GEAR/REVERSE] 47 | """ 48 | 49 | wheelbase_ = wheelbase 50 | delta, a = self.RegulateInput(delta, a) 51 | 52 | self.steer = delta 53 | self.gear = gear 54 | self.x += self.v * math.cos(self.yaw) * ts 55 | self.y += self.v * math.sin(self.yaw) * ts 56 | self.yaw += self.v / wheelbase_ * math.tan(delta) * ts 57 | self.e_cg = e_cg 58 | self.theta_e = theta_e 59 | 60 | if gear == Gear.GEAR_DRIVE: 61 | self.v += a * ts 62 | else: 63 | self.v += -1.0 * a * ts 64 | 65 | self.v = self.RegulateOutput(self.v) 66 | 67 | @staticmethod 68 | def RegulateInput(delta, a): 69 | """ 70 | regulate delta to : - max_steer_angle ~ max_steer_angle 71 | regulate a to : - max_acceleration ~ max_acceleration 72 | :param delta: steering angle [rad] 73 | :param a: acceleration [m / s^2] 74 | :return: regulated delta and acceleration 75 | """ 76 | 77 | if delta < -1.0 * max_steer_angle: 78 | delta = -1.0 * max_steer_angle 79 | 80 | if delta > 1.0 * max_steer_angle: 81 | delta = 1.0 * max_steer_angle 82 | 83 | if a < -1.0 * max_acceleration: 84 | a = -1.0 * max_acceleration 85 | 86 | if a > 1.0 * max_acceleration: 87 | a = 1.0 * max_acceleration 88 | 89 | return delta, a 90 | 91 | @staticmethod 92 | def RegulateOutput(v): 93 | """ 94 | regulate v to : -max_speed ~ max_speed 95 | :param v: calculated speed [m / s] 96 | :return: regulated speed 97 | """ 98 | 99 | max_speed_ = max_speed 100 | 101 | if v < -1.0 * max_speed_: 102 | v = -1.0 * max_speed_ 103 | 104 | if v > 1.0 * max_speed_: 105 | v = 1.0 * max_speed_ 106 | 107 | return v 108 | 109 | 110 | class TrajectoryAnalyzer: 111 | def __init__(self, x, y, yaw, k): 112 | self.x_ = x 113 | self.y_ = y 114 | self.yaw_ = yaw 115 | self.k_ = k 116 | 117 | self.ind_old = 0 118 | self.ind_end = len(x) 119 | 120 | def ToTrajectoryFrame(self, vehicle_state): 121 | """ 122 | errors to trajectory frame 123 | theta_e = yaw_vehicle - yaw_ref_path 124 | e_cg = lateral distance of center of gravity (cg) in frenet frame 125 | :param vehicle_state: vehicle state (class VehicleState) 126 | :return: theta_e, e_cg, yaw_ref, k_ref 127 | """ 128 | 129 | x_cg = vehicle_state.x 130 | y_cg = vehicle_state.y 131 | yaw = vehicle_state.yaw 132 | 133 | # calc nearest point in ref path 134 | dx = [x_cg - ix for ix in self.x_[self.ind_old: self.ind_end]] 135 | dy = [y_cg - iy for iy in self.y_[self.ind_old: self.ind_end]] 136 | 137 | ind_add = int(np.argmin(np.hypot(dx, dy))) 138 | dist = math.hypot(dx[ind_add], dy[ind_add]) 139 | 140 | # calc lateral relative position of vehicle to ref path 141 | vec_axle_rot_90 = np.array([[math.cos(yaw + math.pi / 2.0)], 142 | [math.sin(yaw + math.pi / 2.0)]]) 143 | 144 | vec_path_2_cg = np.array([[dx[ind_add]], 145 | [dy[ind_add]]]) 146 | 147 | if np.dot(vec_axle_rot_90.T, vec_path_2_cg) > 0.0: 148 | e_cg = 1.0 * dist # vehicle on the right of ref path 149 | else: 150 | e_cg = -1.0 * dist # vehicle on the left of ref path 151 | 152 | # calc yaw error: theta_e = yaw_vehicle - yaw_ref 153 | self.ind_old += ind_add 154 | yaw_ref = self.yaw_[self.ind_old] 155 | theta_e = pi_2_pi(yaw - yaw_ref) 156 | 157 | # calc ref curvature 158 | k_ref = self.k_[self.ind_old] 159 | 160 | return theta_e, e_cg, yaw_ref, k_ref 161 | 162 | 163 | class LatController: 164 | """ 165 | Lateral Controller using LQR 166 | """ 167 | 168 | def ComputeControlCommand(self, vehicle_state, ref_trajectory): 169 | """ 170 | calc lateral control command. 171 | :param vehicle_state: vehicle state 172 | :param ref_trajectory: reference trajectory (analyzer) 173 | :return: steering angle (optimal u), theta_e, e_cg 174 | """ 175 | 176 | ts_ = ts 177 | e_cg_old = vehicle_state.e_cg 178 | theta_e_old = vehicle_state.theta_e 179 | 180 | theta_e, e_cg, yaw_ref, k_ref = \ 181 | ref_trajectory.ToTrajectoryFrame(vehicle_state) 182 | 183 | # Calc linearized time-discrete system model 184 | matrix_ad_, matrix_bd_ = self.UpdateMatrix(vehicle_state) 185 | 186 | matrix_state_ = np.zeros((state_size, 1)) 187 | matrix_r_ = np.diag(matrix_r) 188 | matrix_q_ = np.diag(matrix_q) 189 | 190 | # Solve Ricatti equations using value iteration 191 | matrix_k_ = self.SolveLQRProblem(matrix_ad_, matrix_bd_, matrix_q_, 192 | matrix_r_, eps, max_iteration) 193 | 194 | # state vector: 4x1 195 | matrix_state_[0][0] = e_cg 196 | matrix_state_[1][0] = (e_cg - e_cg_old) / ts_ 197 | matrix_state_[2][0] = theta_e 198 | matrix_state_[3][0] = (theta_e - theta_e_old) / ts_ 199 | 200 | # feedback steering angle 201 | steer_angle_feedback = -(matrix_k_ @ matrix_state_)[0][0] 202 | 203 | # calc feedforward term to decrease steady error 204 | steer_angle_feedforward = self.ComputeFeedForward(vehicle_state, k_ref, matrix_k_) 205 | 206 | steer_angle = steer_angle_feedback + steer_angle_feedforward 207 | 208 | return steer_angle, theta_e, e_cg 209 | 210 | @staticmethod 211 | def ComputeFeedForward(vehicle_state, ref_curvature, matrix_k_): 212 | """ 213 | calc feedforward control term to decrease the steady error. 214 | :param vehicle_state: vehicle state 215 | :param ref_curvature: curvature of the target point in ref trajectory 216 | :param matrix_k_: feedback matrix K 217 | :return: feedforward term 218 | """ 219 | 220 | mass_ = m_f + m_r 221 | wheelbase_ = l_f + l_r 222 | 223 | kv = l_r * mass_ / 2.0 / c_f / wheelbase_ - \ 224 | l_f * mass_ / 2.0 / c_r / wheelbase_ 225 | 226 | v = vehicle_state.v 227 | 228 | if vehicle_state.gear == Gear.GEAR_REVERSE: 229 | steer_angle_feedforward = wheelbase_ * ref_curvature 230 | else: 231 | steer_angle_feedforward = wheelbase_ * ref_curvature + kv * v * v * ref_curvature - \ 232 | matrix_k_[0][2] * \ 233 | (l_r * ref_curvature - 234 | l_f * mass_ * v * v * ref_curvature / 2.0 / c_r / wheelbase_) 235 | 236 | return steer_angle_feedforward 237 | 238 | @staticmethod 239 | def SolveLQRProblem(A, B, Q, R, tolerance, max_num_iteration): 240 | """ 241 | iteratively calculating feedback matrix K 242 | :param A: matrix_a_ 243 | :param B: matrix_b_ 244 | :param Q: matrix_q_ 245 | :param R: matrix_r_ 246 | :param tolerance: lqr_eps 247 | :param max_num_iteration: max_iteration 248 | :return: feedback matrix K 249 | """ 250 | 251 | assert np.size(A, 0) == np.size(A, 1) and \ 252 | np.size(B, 0) == np.size(A, 0) and \ 253 | np.size(Q, 0) == np.size(Q, 1) and \ 254 | np.size(Q, 0) == np.size(A, 1) and \ 255 | np.size(R, 0) == np.size(R, 1) and \ 256 | np.size(R, 0) == np.size(B, 1), \ 257 | "LQR solver: one or more matrices have incompatible dimensions." 258 | 259 | M = np.zeros((np.size(Q, 0), np.size(R, 1))) 260 | 261 | AT = A.T 262 | BT = B.T 263 | MT = M.T 264 | 265 | P = Q 266 | num_iteration = 0 267 | diff = math.inf 268 | 269 | while num_iteration < max_num_iteration and diff > tolerance: 270 | num_iteration += 1 271 | P_next = AT @ P @ A - (AT @ P @ B + M) @ \ 272 | np.linalg.pinv(R + BT @ P @ B) @ (BT @ P @ A + MT) + Q 273 | 274 | # check the difference between P and P_next 275 | diff = (abs(P_next - P)).max() 276 | P = P_next 277 | 278 | if num_iteration >= max_num_iteration: 279 | print("LQR solver cannot converge to a solution", 280 | "last consecutive result diff is: ", diff) 281 | 282 | K = np.linalg.inv(BT @ P @ B + R) @ (BT @ P @ A + MT) 283 | 284 | return K 285 | 286 | @staticmethod 287 | def UpdateMatrix(vehicle_state): 288 | """ 289 | calc A and b matrices of linearized, discrete system. 290 | :return: A, b 291 | """ 292 | 293 | ts_ = ts 294 | mass_ = m_f + m_r 295 | 296 | v = vehicle_state.v 297 | 298 | matrix_a_ = np.zeros((state_size, state_size)) # continuous A matrix 299 | 300 | if vehicle_state.gear == Gear.GEAR_REVERSE: 301 | """ 302 | A matrix (Gear Reverse) 303 | [0.0, 0.0, 1.0 * v 0.0; 304 | 0.0, -(c_f + c_r) / m / v, (c_f + c_r) / m, 305 | (l_r * c_r - l_f * c_f) / m / v; 306 | 0.0, 0.0, 0.0, 1.0; 307 | 0.0, (lr * cr - lf * cf) / i_z / v, (l_f * c_f - l_r * c_r) / i_z, 308 | -1.0 * (l_f^2 * c_f + l_r^2 * c_r) / i_z / v;] 309 | """ 310 | 311 | matrix_a_[0][1] = 0.0 312 | matrix_a_[0][2] = 1.0 * v 313 | else: 314 | """ 315 | A matrix (Gear Drive) 316 | [0.0, 1.0, 0.0, 0.0; 317 | 0.0, -(c_f + c_r) / m / v, (c_f + c_r) / m, 318 | (l_r * c_r - l_f * c_f) / m / v; 319 | 0.0, 0.0, 0.0, 1.0; 320 | 0.0, (lr * cr - lf * cf) / i_z / v, (l_f * c_f - l_r * c_r) / i_z, 321 | -1.0 * (l_f^2 * c_f + l_r^2 * c_r) / i_z / v;] 322 | """ 323 | 324 | matrix_a_[0][1] = 1.0 325 | matrix_a_[0][2] = 0.0 326 | 327 | matrix_a_[1][1] = -1.0 * ( + c_r) / mass_ / v 328 | matrix_a_[1][2] = (c_f + c_r) / mass_ 329 | matrix_a_[1][3] = (l_r * c_r - l_f * c_f) / mass_ / v 330 | matrix_a_[2][3] = 1.0 331 | matrix_a_[3][1] = (l_r * c_r - l_f * c_f) / Iz / v 332 | matrix_a_[3][2] = (l_f * c_f - l_r * c_r) / Iz 333 | matrix_a_[3][3] = -1.0 * (l_f ** 2 * c_f + l_r ** 2 * c_r) / Iz / v 334 | 335 | # Tustin's method (bilinear transform) 336 | matrix_i = np.eye(state_size) # identical matrix 337 | matrix_ad_ = np.linalg.pinv(matrix_i - ts_ * 0.5 * matrix_a_) @ \ 338 | (matrix_i + ts_ * 0.5 * matrix_a_) # discrete A matrix 339 | 340 | # b = [0.0, c_f / m, 0.0, l_f * c_f / I_z].T 341 | matrix_b_ = np.zeros((state_size, 1)) # continuous b matrix 342 | matrix_b_[1][0] = c_f / mass_ 343 | matrix_b_[3][0] = l_f * c_f / Iz 344 | matrix_bd_ = matrix_b_ * ts_ # discrete b matrix 345 | 346 | return matrix_ad_, matrix_bd_ 347 | 348 | 349 | class LonController: 350 | """ 351 | Longitudinal Controller using PID. 352 | """ 353 | 354 | @staticmethod 355 | def ComputeControlCommand(target_speed, vehicle_state, dist): 356 | """ 357 | calc acceleration command using PID. 358 | :param target_speed: target speed [m / s] 359 | :param vehicle_state: vehicle state 360 | :param dist: distance to goal [m] 361 | :return: control command (acceleration) [m / s^2] 362 | """ 363 | 364 | if vehicle_state.gear == Gear.GEAR_DRIVE: 365 | direct = 1.0 366 | else: 367 | direct = -1.0 368 | 369 | a = 0.3 * (target_speed - direct * vehicle_state.v) 370 | 371 | if dist < 10.0: 372 | if vehicle_state.v > 2.0: 373 | a = -3.0 374 | elif vehicle_state.v < -2: 375 | a = -1.0 376 | 377 | return a 378 | 379 | 380 | def pi_2_pi(angle): 381 | """ 382 | regulate theta to -pi ~ pi. 383 | :param angle: input angle 384 | :return: regulated angle 385 | """ 386 | 387 | M_PI = math.pi 388 | 389 | if angle > M_PI: 390 | return angle - 2.0 * M_PI 391 | 392 | if angle < -M_PI: 393 | return angle + 2.0 * M_PI 394 | 395 | return angle 396 | 397 | 398 | def generate_path(s): 399 | """ 400 | design path using reeds-shepp path generator. 401 | divide paths into sections, in each section the direction is the same. 402 | :param s: objective positions and directions. 403 | :return: paths 404 | """ 405 | wheelbase_ = wheelbase 406 | 407 | max_c = math.tan(0.5 * max_steer_angle) / wheelbase_ 408 | path_x, path_y, yaw, direct, rc = [], [], [], [], [] 409 | x_rec, y_rec, yaw_rec, direct_rec, rc_rec = [], [], [], [], [] 410 | direct_flag = 1.0 411 | 412 | for i in range(len(s) - 1): 413 | s_x, s_y, s_yaw = s[i][0], s[i][1], np.deg2rad(s[i][2]) 414 | g_x, g_y, g_yaw = s[i + 1][0], s[i + 1][1], np.deg2rad(s[i + 1][2]) 415 | 416 | path_i = rs.calc_optimal_path(s_x, s_y, s_yaw, 417 | g_x, g_y, g_yaw, max_c) 418 | 419 | irc, rds = rs.calc_curvature(path_i.x, path_i.y, path_i.yaw, path_i.directions) 420 | 421 | ix = path_i.x 422 | iy = path_i.y 423 | iyaw = path_i.yaw 424 | idirect = path_i.directions 425 | 426 | for j in range(len(ix)): 427 | if idirect[j] == direct_flag: 428 | x_rec.append(ix[j]) 429 | y_rec.append(iy[j]) 430 | yaw_rec.append(iyaw[j]) 431 | direct_rec.append(idirect[j]) 432 | rc_rec.append(irc[j]) 433 | else: 434 | if len(x_rec) == 0 or direct_rec[0] != direct_flag: 435 | direct_flag = idirect[j] 436 | continue 437 | 438 | path_x.append(x_rec) 439 | path_y.append(y_rec) 440 | yaw.append(yaw_rec) 441 | direct.append(direct_rec) 442 | rc.append(rc_rec) 443 | x_rec, y_rec, yaw_rec, direct_rec, rc_rec = \ 444 | [x_rec[-1]], [y_rec[-1]], [yaw_rec[-1]], [-direct_rec[-1]], [rc_rec[-1]] 445 | 446 | path_x.append(x_rec) 447 | path_y.append(y_rec) 448 | yaw.append(yaw_rec) 449 | direct.append(direct_rec) 450 | rc.append(rc_rec) 451 | 452 | x_all, y_all = [], [] 453 | for ix, iy in zip(path_x, path_y): 454 | x_all += ix 455 | y_all += iy 456 | 457 | return path_x, path_y, yaw, direct, rc, x_all, y_all 458 | 459 | 460 | def main(): 461 | # generate path 462 | states = [(0, 0, 0), (20, 15, 0), (35, 20, 90), (40, 0, 180), 463 | (20, 0, 120), (5, -10, 180), (15, 5, 30)] 464 | # 465 | # states = [(-3, 3, 120), (10, -7, 30), (10, 13, 30), (20, 5, -25), 466 | # (35, 10, 180), (30, -10, 160), (5, -12, 90)] 467 | 468 | x_ref, y_ref, yaw_ref, direct, curv, x_all, y_all = generate_path(states) 469 | 470 | maxTime = 100.0 471 | yaw_old = 0.0 472 | x0, y0, yaw0, direct0 = \ 473 | x_ref[0][0], y_ref[0][0], yaw_ref[0][0], direct[0][0] 474 | 475 | x_rec, y_rec, yaw_rec, direct_rec = [], [], [], [] 476 | 477 | lat_controller = LatController() 478 | lon_controller = LonController() 479 | 480 | for x, y, yaw, gear, k in zip(x_ref, y_ref, yaw_ref, direct, curv): 481 | t = 0.0 482 | 483 | if gear[0] == 1.0: 484 | direct = Gear.GEAR_DRIVE 485 | else: 486 | direct = Gear.GEAR_REVERSE 487 | 488 | ref_trajectory = TrajectoryAnalyzer(x, y, yaw, k) 489 | 490 | vehicle_state = VehicleState(x=x0, y=y0, yaw=yaw0, v=0.1, gear=direct) 491 | 492 | while t < maxTime: 493 | 494 | dist = math.hypot(vehicle_state.x - x[-1], vehicle_state.y - y[-1]) 495 | 496 | if gear[0] > 0: 497 | target_speed = 25.0 / 3.6 498 | else: 499 | target_speed = 15.0 / 3.6 500 | 501 | delta_opt, theta_e, e_cg = \ 502 | lat_controller.ComputeControlCommand(vehicle_state, ref_trajectory) 503 | 504 | a_opt = lon_controller.ComputeControlCommand(target_speed, vehicle_state, dist) 505 | 506 | vehicle_state.UpdateVehicleState(pi_2_pi(delta_opt), a_opt, e_cg, theta_e, direct) 507 | 508 | t += ts 509 | 510 | if dist <= 0.5: 511 | break 512 | 513 | x_rec.append(vehicle_state.x) 514 | y_rec.append(vehicle_state.y) 515 | yaw_rec.append(vehicle_state.yaw) 516 | 517 | x0 = x_rec[-1] 518 | y0 = y_rec[-1] 519 | yaw0 = yaw_rec[-1] 520 | 521 | plt.cla() 522 | plt.plot(x_all, y_all, color='gray', linewidth=2.0) 523 | plt.plot(x_rec, y_rec, linewidth=2.0, color='darkviolet') 524 | draw.draw_car(x0, y0, yaw0, -vehicle_state.steer) 525 | plt.axis("equal") 526 | plt.title("LQR (Dynamics): v=" + str(vehicle_state.v * 3.6)[:4] + "km/h") 527 | plt.gcf().canvas.mpl_connect('key_release_event', 528 | lambda event: 529 | [exit(0) if event.key == 'escape' else None]) 530 | plt.pause(0.001) 531 | 532 | plt.show() 533 | 534 | 535 | if __name__ == '__main__': 536 | main() 537 | -------------------------------------------------------------------------------- /Control/LQR_Kinematic_Model.py: -------------------------------------------------------------------------------- 1 | """ 2 | LQR and PID Controller 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import numpy as np 10 | from enum import Enum 11 | import matplotlib.pyplot as plt 12 | 13 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 14 | "/../../MotionPlanning/") 15 | 16 | import Control.draw_lqr as draw 17 | from Control.config_control import * 18 | import CurvesGenerator.reeds_shepp as rs 19 | 20 | 21 | # class C: 22 | # # PID config 23 | # Kp = 1.0 24 | # 25 | # # System config 26 | # dt = 0.1 27 | # dist_stop = 0.5 28 | # Q = np.eye(4) 29 | # R = np.eye(1) 30 | # 31 | # # vehicle config 32 | # RF = 3.3 # [m] distance from rear to vehicle front end of vehicle 33 | # RB = 0.8 # [m] distance from rear to vehicle back end of vehicle 34 | # W = 2.4 # [m] width of vehicle 35 | # WD = 0.7 * W # [m] distance between left-right wheels 36 | # WB = 2.5 # [m] Wheel base 37 | # TR = 0.44 # [m] Tyre radius 38 | # TW = 0.7 # [m] Tyre width 39 | # MAX_STEER = 0.30 40 | 41 | 42 | # Controller Config 43 | ts = 0.1 # [s] 44 | l_f = 1.165 # [m] 45 | l_r = 1.165 # [m] 46 | max_iteration = 150 47 | eps = 0.01 48 | 49 | matrix_q = [0.5, 0.0, 1.0, 0.0] 50 | matrix_r = [1.0] 51 | 52 | state_size = 4 53 | 54 | max_acceleration = 5.0 # [m / s^2] 55 | max_steer_angle = np.deg2rad(40) # [rad] 56 | max_speed = 35 / 3.6 # [m / s] 57 | 58 | 59 | class Gear(Enum): 60 | GEAR_DRIVE = 1 61 | GEAR_REVERSE = 2 62 | 63 | 64 | class VehicleState: 65 | def __init__(self, x=0.0, y=0.0, yaw=0.0, 66 | v=0.0, gear=Gear.GEAR_DRIVE): 67 | self.x = x 68 | self.y = y 69 | self.yaw = yaw 70 | self.v = v 71 | self.e_cg = 0.0 72 | self.theta_e = 0.0 73 | 74 | self.gear = gear 75 | self.steer = 0.0 76 | 77 | def UpdateVehicleState(self, delta, a, e_cg, theta_e, 78 | gear=Gear.GEAR_DRIVE): 79 | """ 80 | update states of vehicle 81 | :param theta_e: yaw error to ref trajectory 82 | :param e_cg: lateral error to ref trajectory 83 | :param delta: steering angle [rad] 84 | :param a: acceleration [m / s^2] 85 | :param gear: gear mode [GEAR_DRIVE / GEAR/REVERSE] 86 | """ 87 | 88 | wheelbase_ = l_r + l_f 89 | delta, a = self.RegulateInput(delta, a) 90 | 91 | self.gear = gear 92 | self.steer = delta 93 | self.x += self.v * math.cos(self.yaw) * ts 94 | self.y += self.v * math.sin(self.yaw) * ts 95 | self.yaw += self.v / wheelbase_ * math.tan(delta) * ts 96 | self.e_cg = e_cg 97 | self.theta_e = theta_e 98 | 99 | if gear == Gear.GEAR_DRIVE: 100 | self.v += a * ts 101 | else: 102 | self.v += -1.0 * a * ts 103 | 104 | self.v = self.RegulateOutput(self.v) 105 | 106 | @staticmethod 107 | def RegulateInput(delta, a): 108 | """ 109 | regulate delta to : - max_steer_angle ~ max_steer_angle 110 | regulate a to : - max_acceleration ~ max_acceleration 111 | :param delta: steering angle [rad] 112 | :param a: acceleration [m / s^2] 113 | :return: regulated delta and acceleration 114 | """ 115 | 116 | if delta < -1.0 * max_steer_angle: 117 | delta = -1.0 * max_steer_angle 118 | 119 | if delta > 1.0 * max_steer_angle: 120 | delta = 1.0 * max_steer_angle 121 | 122 | if a < -1.0 * max_acceleration: 123 | a = -1.0 * max_acceleration 124 | 125 | if a > 1.0 * max_acceleration: 126 | a = 1.0 * max_acceleration 127 | 128 | return delta, a 129 | 130 | @staticmethod 131 | def RegulateOutput(v): 132 | """ 133 | regulate v to : -max_speed ~ max_speed 134 | :param v: calculated speed [m / s] 135 | :return: regulated speed 136 | """ 137 | 138 | max_speed_ = max_speed 139 | 140 | if v < -1.0 * max_speed_: 141 | v = -1.0 * max_speed_ 142 | 143 | if v > 1.0 * max_speed_: 144 | v = 1.0 * max_speed_ 145 | 146 | return v 147 | 148 | 149 | class TrajectoryAnalyzer: 150 | def __init__(self, x, y, yaw, k): 151 | self.x_ = x 152 | self.y_ = y 153 | self.yaw_ = yaw 154 | self.k_ = k 155 | 156 | self.ind_old = 0 157 | self.ind_end = len(x) 158 | 159 | def ToTrajectoryFrame(self, vehicle_state): 160 | """ 161 | errors to trajectory frame 162 | theta_e = yaw_vehicle - yaw_ref_path 163 | e_cg = lateral distance of center of gravity (cg) in frenet frame 164 | :param vehicle_state: vehicle state (class VehicleState) 165 | :return: theta_e, e_cg, yaw_ref, k_ref 166 | """ 167 | 168 | x_cg = vehicle_state.x 169 | y_cg = vehicle_state.y 170 | yaw = vehicle_state.yaw 171 | 172 | # calc nearest point in ref path 173 | dx = [x_cg - ix for ix in self.x_[self.ind_old: self.ind_end]] 174 | dy = [y_cg - iy for iy in self.y_[self.ind_old: self.ind_end]] 175 | 176 | ind_add = int(np.argmin(np.hypot(dx, dy))) 177 | dist = math.hypot(dx[ind_add], dy[ind_add]) 178 | 179 | # calc lateral relative position of vehicle to ref path 180 | vec_axle_rot_90 = np.array([[math.cos(yaw + math.pi / 2.0)], 181 | [math.sin(yaw + math.pi / 2.0)]]) 182 | 183 | vec_path_2_cg = np.array([[dx[ind_add]], 184 | [dy[ind_add]]]) 185 | 186 | if np.dot(vec_axle_rot_90.T, vec_path_2_cg) > 0.0: 187 | e_cg = 1.0 * dist # vehicle on the right of ref path 188 | else: 189 | e_cg = -1.0 * dist # vehicle on the left of ref path 190 | 191 | # calc yaw error: theta_e = yaw_vehicle - yaw_ref 192 | self.ind_old += ind_add 193 | yaw_ref = self.yaw_[self.ind_old] 194 | theta_e = pi_2_pi(yaw - yaw_ref) 195 | 196 | # calc ref curvature 197 | k_ref = self.k_[self.ind_old] 198 | 199 | return theta_e, e_cg, yaw_ref, k_ref 200 | 201 | 202 | class LatController: 203 | """ 204 | Lateral Controller using LQR 205 | """ 206 | 207 | def ComputeControlCommand(self, vehicle_state, ref_trajectory): 208 | """ 209 | calc lateral control command. 210 | :param vehicle_state: vehicle state 211 | :param ref_trajectory: reference trajectory (analyzer) 212 | :return: steering angle (optimal u), theta_e, e_cg 213 | """ 214 | 215 | ts_ = ts 216 | e_cg_old = vehicle_state.e_cg 217 | theta_e_old = vehicle_state.theta_e 218 | 219 | theta_e, e_cg, yaw_ref, k_ref = \ 220 | ref_trajectory.ToTrajectoryFrame(vehicle_state) 221 | 222 | matrix_ad_, matrix_bd_ = self.UpdateMatrix(vehicle_state) 223 | 224 | matrix_state_ = np.zeros((state_size, 1)) 225 | matrix_r_ = np.diag(matrix_r) 226 | matrix_q_ = np.diag(matrix_q) 227 | 228 | matrix_k_ = self.SolveLQRProblem(matrix_ad_, matrix_bd_, matrix_q_, 229 | matrix_r_, eps, max_iteration) 230 | 231 | matrix_state_[0][0] = e_cg 232 | matrix_state_[1][0] = (e_cg - e_cg_old) / ts_ 233 | matrix_state_[2][0] = theta_e 234 | matrix_state_[3][0] = (theta_e - theta_e_old) / ts_ 235 | 236 | steer_angle_feedback = -(matrix_k_ @ matrix_state_)[0][0] 237 | 238 | steer_angle_feedforward = self.ComputeFeedForward(k_ref) 239 | 240 | steer_angle = steer_angle_feedback + steer_angle_feedforward 241 | 242 | return steer_angle, theta_e, e_cg 243 | 244 | @staticmethod 245 | def ComputeFeedForward(ref_curvature): 246 | """ 247 | calc feedforward control term to decrease the steady error. 248 | :param ref_curvature: curvature of the target point in ref trajectory 249 | :return: feedforward term 250 | """ 251 | 252 | wheelbase_ = l_f + l_r 253 | 254 | steer_angle_feedforward = wheelbase_ * ref_curvature 255 | 256 | return steer_angle_feedforward 257 | 258 | @staticmethod 259 | def SolveLQRProblem(A, B, Q, R, tolerance, max_num_iteration): 260 | """ 261 | iteratively calculating feedback matrix K 262 | :param A: matrix_a_ 263 | :param B: matrix_b_ 264 | :param Q: matrix_q_ 265 | :param R: matrix_r_ 266 | :param tolerance: lqr_eps 267 | :param max_num_iteration: max_iteration 268 | :return: feedback matrix K 269 | """ 270 | 271 | assert np.size(A, 0) == np.size(A, 1) and \ 272 | np.size(B, 0) == np.size(A, 0) and \ 273 | np.size(Q, 0) == np.size(Q, 1) and \ 274 | np.size(Q, 0) == np.size(A, 1) and \ 275 | np.size(R, 0) == np.size(R, 1) and \ 276 | np.size(R, 0) == np.size(B, 1), \ 277 | "LQR solver: one or more matrices have incompatible dimensions." 278 | 279 | M = np.zeros((np.size(Q, 0), np.size(R, 1))) 280 | 281 | AT = A.T 282 | BT = B.T 283 | MT = M.T 284 | 285 | P = Q 286 | num_iteration = 0 287 | diff = math.inf 288 | 289 | while num_iteration < max_num_iteration and diff > tolerance: 290 | num_iteration += 1 291 | P_next = AT @ P @ A - (AT @ P @ B + M) @ \ 292 | np.linalg.pinv(R + BT @ P @ B) @ (BT @ P @ A + MT) + Q 293 | 294 | # check the difference between P and P_next 295 | diff = (abs(P_next - P)).max() 296 | P = P_next 297 | 298 | if num_iteration >= max_num_iteration: 299 | print("LQR solver cannot converge to a solution", 300 | "last consecutive result diff is: ", diff) 301 | 302 | K = np.linalg.inv(BT @ P @ B + R) @ (BT @ P @ A + MT) 303 | 304 | return K 305 | 306 | @staticmethod 307 | def UpdateMatrix(vehicle_state): 308 | """ 309 | calc A and b matrices of linearized, discrete system. 310 | :return: A, b 311 | """ 312 | 313 | ts_ = ts 314 | wheelbase_ = l_f + l_r 315 | 316 | v = vehicle_state.v 317 | 318 | matrix_ad_ = np.zeros((state_size, state_size)) # time discrete A matrix 319 | 320 | matrix_ad_[0][0] = 1.0 321 | matrix_ad_[0][1] = ts_ 322 | matrix_ad_[1][2] = v 323 | matrix_ad_[2][2] = 1.0 324 | matrix_ad_[2][3] = ts_ 325 | 326 | # b = [0.0, 0.0, 0.0, v / L].T 327 | matrix_bd_ = np.zeros((state_size, 1)) # time discrete b matrix 328 | matrix_bd_[3][0] = v / wheelbase_ 329 | 330 | return matrix_ad_, matrix_bd_ 331 | 332 | 333 | class LonController: 334 | """ 335 | Longitudinal Controller using PID. 336 | """ 337 | 338 | @staticmethod 339 | def ComputeControlCommand(target_speed, vehicle_state, dist): 340 | """ 341 | calc acceleration command using PID. 342 | :param target_speed: target speed [m / s] 343 | :param vehicle_state: vehicle state 344 | :param dist: distance to goal [m] 345 | :return: control command (acceleration) [m / s^2] 346 | """ 347 | 348 | if vehicle_state.gear == Gear.GEAR_DRIVE: 349 | direct = 1.0 350 | else: 351 | direct = -1.0 352 | 353 | a = 0.3 * (target_speed - direct * vehicle_state.v) 354 | 355 | if dist < 10.0: 356 | if vehicle_state.v > 2.0: 357 | a = -3.0 358 | elif vehicle_state.v < -2: 359 | a = -1.0 360 | 361 | return a 362 | 363 | 364 | def pi_2_pi(angle): 365 | """ 366 | regulate theta to -pi ~ pi. 367 | :param angle: input angle 368 | :return: regulated angle 369 | """ 370 | 371 | M_PI = math.pi 372 | 373 | if angle > M_PI: 374 | return angle - 2.0 * M_PI 375 | 376 | if angle < -M_PI: 377 | return angle + 2.0 * M_PI 378 | 379 | return angle 380 | 381 | 382 | def generate_path(s): 383 | """ 384 | design path using reeds-shepp path generator. 385 | divide paths into sections, in each section the direction is the same. 386 | :param s: objective positions and directions. 387 | :return: paths 388 | """ 389 | wheelbase_ = l_f + l_r 390 | 391 | max_c = math.tan(0.5 * max_steer_angle) / wheelbase_ 392 | path_x, path_y, yaw, direct, rc = [], [], [], [], [] 393 | x_rec, y_rec, yaw_rec, direct_rec, rc_rec = [], [], [], [], [] 394 | direct_flag = 1.0 395 | 396 | for i in range(len(s) - 1): 397 | s_x, s_y, s_yaw = s[i][0], s[i][1], np.deg2rad(s[i][2]) 398 | g_x, g_y, g_yaw = s[i + 1][0], s[i + 1][1], np.deg2rad(s[i + 1][2]) 399 | 400 | path_i = rs.calc_optimal_path(s_x, s_y, s_yaw, 401 | g_x, g_y, g_yaw, max_c) 402 | 403 | irc, rds = rs.calc_curvature(path_i.x, path_i.y, path_i.yaw, path_i.directions) 404 | 405 | ix = path_i.x 406 | iy = path_i.y 407 | iyaw = path_i.yaw 408 | idirect = path_i.directions 409 | 410 | for j in range(len(ix)): 411 | if idirect[j] == direct_flag: 412 | x_rec.append(ix[j]) 413 | y_rec.append(iy[j]) 414 | yaw_rec.append(iyaw[j]) 415 | direct_rec.append(idirect[j]) 416 | rc_rec.append(irc[j]) 417 | else: 418 | if len(x_rec) == 0 or direct_rec[0] != direct_flag: 419 | direct_flag = idirect[j] 420 | continue 421 | 422 | path_x.append(x_rec) 423 | path_y.append(y_rec) 424 | yaw.append(yaw_rec) 425 | direct.append(direct_rec) 426 | rc.append(rc_rec) 427 | x_rec, y_rec, yaw_rec, direct_rec, rc_rec = \ 428 | [x_rec[-1]], [y_rec[-1]], [yaw_rec[-1]], [-direct_rec[-1]], [rc_rec[-1]] 429 | 430 | path_x.append(x_rec) 431 | path_y.append(y_rec) 432 | yaw.append(yaw_rec) 433 | direct.append(direct_rec) 434 | rc.append(rc_rec) 435 | 436 | x_all, y_all = [], [] 437 | for ix, iy in zip(path_x, path_y): 438 | x_all += ix 439 | y_all += iy 440 | 441 | return path_x, path_y, yaw, direct, rc, x_all, y_all 442 | 443 | 444 | def main(): 445 | # generate path 446 | states = [(0, 0, 0), (20, 15, 0), (35, 20, 90), (40, 0, 180), 447 | (20, 0, 120), (5, -10, 180), (15, 5, 30)] 448 | # 449 | # states = [(-3, 3, 120), (10, -7, 30), (10, 13, 30), (20, 5, -25), 450 | # (35, 10, 180), (30, -10, 160), (5, -12, 90)] 451 | 452 | x_ref, y_ref, yaw_ref, direct, curv, x_all, y_all = generate_path(states) 453 | 454 | wheelbase_ = l_f + l_r 455 | 456 | maxTime = 100.0 457 | yaw_old = 0.0 458 | x0, y0, yaw0, direct0 = \ 459 | x_ref[0][0], y_ref[0][0], yaw_ref[0][0], direct[0][0] 460 | 461 | x_rec, y_rec, yaw_rec, direct_rec = [], [], [], [] 462 | 463 | lat_controller = LatController() 464 | lon_controller = LonController() 465 | 466 | for x, y, yaw, gear, k in zip(x_ref, y_ref, yaw_ref, direct, curv): 467 | t = 0.0 468 | 469 | if gear[0] == 1.0: 470 | direct = Gear.GEAR_DRIVE 471 | else: 472 | direct = Gear.GEAR_REVERSE 473 | 474 | ref_trajectory = TrajectoryAnalyzer(x, y, yaw, k) 475 | 476 | vehicle_state = VehicleState(x=x0, y=y0, yaw=yaw0, v=0.1, gear=direct) 477 | 478 | while t < maxTime: 479 | 480 | dist = math.hypot(vehicle_state.x - x[-1], vehicle_state.y - y[-1]) 481 | 482 | if gear[0] > 0: 483 | target_speed = 25.0 / 3.6 484 | else: 485 | target_speed = 15.0 / 3.6 486 | 487 | delta_opt, theta_e, e_cg = \ 488 | lat_controller.ComputeControlCommand(vehicle_state, ref_trajectory) 489 | 490 | a_opt = lon_controller.ComputeControlCommand(target_speed, vehicle_state, dist) 491 | 492 | vehicle_state.UpdateVehicleState(delta_opt, a_opt, e_cg, theta_e, direct) 493 | 494 | t += ts 495 | 496 | if dist <= 0.5: 497 | break 498 | 499 | x_rec.append(vehicle_state.x) 500 | y_rec.append(vehicle_state.y) 501 | yaw_rec.append(vehicle_state.yaw) 502 | 503 | dy = (vehicle_state.yaw - yaw_old) / (vehicle_state.v * ts) 504 | # steer = rs.pi_2_pi(-math.atan(wheelbase_ * dy)) 505 | 506 | yaw_old = vehicle_state.yaw 507 | x0 = x_rec[-1] 508 | y0 = y_rec[-1] 509 | yaw0 = yaw_rec[-1] 510 | 511 | plt.cla() 512 | plt.plot(x_all, y_all, color='gray', linewidth=2.0) 513 | plt.plot(x_rec, y_rec, linewidth=2.0, color='darkviolet') 514 | # plt.plot(x[ind], y[ind], '.r') 515 | draw.draw_car(x0, y0, yaw0, -vehicle_state.steer) 516 | plt.axis("equal") 517 | plt.title("LQR (Kinematic): v=" + str(vehicle_state.v * 3.6)[:4] + "km/h") 518 | plt.gcf().canvas.mpl_connect('key_release_event', 519 | lambda event: 520 | [exit(0) if event.key == 'escape' else None]) 521 | plt.pause(0.001) 522 | 523 | plt.show() 524 | 525 | 526 | if __name__ == '__main__': 527 | main() 528 | -------------------------------------------------------------------------------- /Control/MPC_Frenet_Frame.py: -------------------------------------------------------------------------------- 1 | """ 2 | Linear MPC controller (Frenet frame) 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import cvxpy 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | 13 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 14 | "/../../MotionPlanning/") 15 | 16 | import Control.draw as draw 17 | import CurvesGenerator.reeds_shepp as rs 18 | import CurvesGenerator.cubic_spline as cs 19 | 20 | 21 | class P: 22 | # System config 23 | NX = 5 # state vector: z = [e, e_dot, theta_e, theta_e_dot, v] 24 | NU = 2 # input vector: u = [acceleration, steer] 25 | T = 6 # finite time horizon length 26 | 27 | # MPC config 28 | Q = np.diag([1.0, 1.0, 1.0, 1.0, 1.0]) # penalty for states 29 | Qf = np.diag([1.0, 1.0, 1.0, 1.0, 1.0]) # penalty for end state 30 | R = np.diag([0.01, 0.1]) # penalty for inputs 31 | Rd = np.diag([0.01, 0.1]) # penalty for change of inputs 32 | 33 | dist_stop = 1.5 # stop permitted when dist to goal < dist_stop 34 | speed_stop = 0.5 / 3.6 # stop permitted when speed < speed_stop 35 | time_max = 500.0 # max simulation time 36 | iter_max = 5 # max iteration 37 | target_speed = 10.0 / 3.6 # target speed 38 | N_IND = 10 # search index number 39 | dt = 0.2 # time step 40 | d_dist = 1.0 # dist step 41 | du_res = 0.1 # threshold for stopping iteration 42 | 43 | # vehicle config 44 | RF = 3.3 # [m] distance from rear to vehicle front end of vehicle 45 | RB = 0.8 # [m] distance from rear to vehicle back end of vehicle 46 | W = 2.4 # [m] width of vehicle 47 | WD = 0.7 * W # [m] distance between left-right wheels 48 | WB = 2.5 # [m] Wheel base 49 | TR = 0.44 # [m] Tyre radius 50 | TW = 0.7 # [m] Tyre width 51 | 52 | steer_max = np.deg2rad(45.0) # max steering angle [rad] 53 | steer_change_max = np.deg2rad(30.0) # maximum steering speed [rad/s] 54 | speed_max = 55.0 / 3.6 # maximum speed [m/s] 55 | speed_min = -20.0 / 3.6 # minimum speed [m/s] 56 | acceleration_max = 1.0 # maximum acceleration [m/s2] 57 | 58 | 59 | class Node: 60 | def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0, direct=1.0): 61 | self.x = x 62 | self.y = y 63 | self.yaw = yaw 64 | self.v = v 65 | self.direct = direct 66 | 67 | def update(self, a, delta, direct): 68 | delta = self.limit_input_delta(delta) 69 | self.x += self.v * math.cos(self.yaw) * P.dt 70 | self.y += self.v * math.sin(self.yaw) * P.dt 71 | self.yaw += self.v / P.WB * math.tan(delta) * P.dt 72 | self.direct = direct 73 | self.v += self.direct * a * P.dt 74 | self.v = self.limit_speed(self.v) 75 | 76 | @staticmethod 77 | def limit_input_delta(delta): 78 | if delta >= P.steer_max: 79 | return P.steer_max 80 | 81 | if delta <= -P.steer_max: 82 | return -P.steer_max 83 | 84 | return delta 85 | 86 | @staticmethod 87 | def limit_speed(v): 88 | if v >= P.speed_max: 89 | return P.speed_max 90 | 91 | if v <= P.speed_min: 92 | return P.speed_min 93 | 94 | return v 95 | 96 | 97 | class PATH: 98 | def __init__(self, cx, cy, cyaw, ck): 99 | self.cx = cx 100 | self.cy = cy 101 | self.cyaw = cyaw 102 | self.ck = ck 103 | self.length = len(cx) 104 | self.ind_old = 0 105 | 106 | def calc_theta_e_and_er(self, node): 107 | dx = [node.x - x for x in self.cx[self.ind_old: (self.ind_old + P.N_IND)]] 108 | dy = [node.y - y for y in self.cy[self.ind_old: (self.ind_old + P.N_IND)]] 109 | dist = np.hypot(dx, dy) 110 | 111 | ind_in_N = int(np.argmin(dist)) 112 | ind = self.ind_old + ind_in_N 113 | self.ind_old = ind 114 | 115 | rear_axle_vec_rot_90 = np.array([[math.cos(node.yaw + math.pi / 2.0)], 116 | [math.sin(node.yaw + math.pi / 2.0)]]) 117 | 118 | vec_target_2_rear = np.array([[dx[ind_in_N]], 119 | [dy[ind_in_N]]]) 120 | 121 | er = np.dot(vec_target_2_rear.T, rear_axle_vec_rot_90) 122 | er = er[0][0] 123 | 124 | theta = node.yaw 125 | theta_p = self.cyaw[ind] 126 | theta_e = pi_2_pi(theta - theta_p) 127 | 128 | return theta_e, er, ind 129 | 130 | 131 | def calc_ref_trajectory_in_T_step(node, ref_path, sp): 132 | z_ref = np.zeros((P.NX, P.T + 1)) 133 | length = ref_path.length 134 | 135 | theta_e, er, ind = ref_path.calc_theta_e_and_er(node) 136 | 137 | z_ref[4, 0] = sp[ind] 138 | dist_move = 0.0 139 | 140 | for i in range(1, P.T + 1): 141 | dist_move += abs(node.v) * P.dt 142 | ind_move = int(round(dist_move / P.d_dist)) 143 | index = min(ind + ind_move, length - 1) 144 | 145 | z_ref[4, i] = sp[index] 146 | 147 | return z_ref, ind, theta_e, er 148 | 149 | 150 | def linear_mpc_control(z_ref, node0, z0, a_old, delta_old): 151 | if a_old is None or delta_old is None: 152 | a_old = [0.0] * P.T 153 | delta_old = [0.0] * P.T 154 | 155 | for k in range(P.iter_max): 156 | v_bar = predict_states_in_T_step(node0, a_old, delta_old) 157 | a_rec, delta_rec = a_old[:], delta_old[:] 158 | a_old, delta_old = solve_linear_mpc(z_ref, v_bar, z0) 159 | 160 | du_a_max = max([abs(ia - iao) for ia, iao in zip(a_old, a_rec)]) 161 | du_d_max = max([abs(ide - ido) for ide, ido in zip(delta_old, delta_rec)]) 162 | 163 | if max(du_a_max, du_d_max) < P.du_res: 164 | break 165 | 166 | return a_old, delta_old 167 | 168 | 169 | def solve_linear_mpc(z_ref, v_bar, z0): 170 | z = cvxpy.Variable((P.NX, P.T + 1)) 171 | u = cvxpy.Variable((P.NU, P.T)) 172 | 173 | cost = 0.0 174 | constrains = [] 175 | 176 | for t in range(P.T): 177 | cost += cvxpy.quad_form(u[:, t], P.R) 178 | cost += cvxpy.quad_form(z[:, t] - z_ref[:, t], P.Q) 179 | 180 | A, B = calc_linear_discrete_model(v_bar[t]) 181 | 182 | constrains += [z[:, t + 1] == A @ z[:, t] + B @ u[:, t]] 183 | 184 | if t < P.T - 1: 185 | cost += cvxpy.quad_form(u[:, t + 1] - u[:, t], P.Rd) 186 | constrains += [cvxpy.abs(u[1, t + 1] - u[1, t]) <= P.steer_change_max * P.dt] 187 | 188 | cost += cvxpy.quad_form(z_ref[:, P.T] - z[:, P.T], P.Qf) 189 | 190 | constrains += [z[:, 0] == z0] 191 | constrains += [z[4, :] <= P.speed_max] 192 | constrains += [z[4, :] >= P.speed_min] 193 | constrains += [cvxpy.abs(u[0, :]) <= P.acceleration_max] 194 | constrains += [cvxpy.abs(u[1, :]) <= P.steer_max] 195 | 196 | prob = cvxpy.Problem(cvxpy.Minimize(cost), constrains) 197 | prob.solve(solver=cvxpy.OSQP) 198 | 199 | a, delta = None, None 200 | 201 | if prob.status == cvxpy.OPTIMAL or \ 202 | prob.status == cvxpy.OPTIMAL_INACCURATE: 203 | a = u.value[0, :] 204 | delta = u.value[1, :] 205 | else: 206 | print("Cannot solve linear mpc!") 207 | 208 | print(delta) 209 | 210 | return a, delta 211 | 212 | 213 | def predict_states_in_T_step(node0, a, delta): 214 | v_bar = [0.0] * (P.T + 1) 215 | v_bar[0] = node0.v 216 | 217 | for ai, di, i in zip(a, delta, range(1, P.T + 1)): 218 | node0.update(ai, di, 1.0) 219 | v_bar[i] = node0.v 220 | 221 | return v_bar 222 | 223 | 224 | def calc_linear_discrete_model(v): 225 | A = np.array([[1.0, P.dt, 0.0, 0.0, 0.0], 226 | [0.0, 0.0, v, 0.0, 0.0], 227 | [0.0, 0.0, 1.0, P.dt, 0.0], 228 | [0.0, 0.0, 0.0, 0.0, 0.0], 229 | [0.0, 0.0, 0.0, 0.0, 1.0]]) 230 | 231 | B = np.array([[0.0, 0.0], 232 | [0.0, 0.0], 233 | [0.0, 0.0], 234 | [v / P.WB, 0.0], 235 | [0.0, P.dt]]) 236 | 237 | return A, B 238 | 239 | 240 | def calc_speed_profile(cx, cy, cyaw, target_speed): 241 | speed_profile = [target_speed] * len(cx) 242 | direction = 1.0 # forward 243 | 244 | # Set stop point 245 | for i in range(len(cx) - 1): 246 | dx = cx[i + 1] - cx[i] 247 | dy = cy[i + 1] - cy[i] 248 | 249 | move_direction = math.atan2(dy, dx) 250 | 251 | if dx != 0.0 and dy != 0.0: 252 | dangle = abs(pi_2_pi(move_direction - cyaw[i])) 253 | if dangle >= math.pi / 4.0: 254 | direction = -1.0 255 | else: 256 | direction = 1.0 257 | 258 | if direction != 1.0: 259 | speed_profile[i] = - target_speed 260 | else: 261 | speed_profile[i] = target_speed 262 | 263 | speed_profile[-1] = 0.0 264 | 265 | return speed_profile 266 | 267 | 268 | def pi_2_pi(angle): 269 | if angle > math.pi: 270 | return angle - 2.0 * math.pi 271 | 272 | if angle < -math.pi: 273 | return angle + 2.0 * math.pi 274 | 275 | return angle 276 | 277 | 278 | def main(): 279 | ax = [0.0, 20.0, 40.0, 55.0, 70.0, 85.0] 280 | ay = [0.0, 50.0, 20.0, 35.0, 0.0, 10.0] 281 | 282 | cx, cy, cyaw, ck, s = \ 283 | cs.calc_spline_course(ax, ay, ds=P.d_dist) 284 | 285 | sp = calc_speed_profile(cx, cy, cyaw, P.target_speed) 286 | 287 | ref_path = PATH(cx, cy, cyaw, ck) 288 | node = Node(x=cx[0], y=cy[0], yaw=cyaw[0], v=0.0) 289 | 290 | time = 0.0 291 | x = [node.x] 292 | y = [node.y] 293 | yaw = [node.yaw] 294 | v = [node.v] 295 | t = [0.0] 296 | d = [0.0] 297 | a = [0.0] 298 | 299 | delta_opt, a_opt = None, None 300 | a_exc, delta_exc = 0.0, 0.0 301 | 302 | while time < P.time_max: 303 | z_ref, target_ind, theta_e, er = \ 304 | calc_ref_trajectory_in_T_step(node, ref_path, sp) 305 | 306 | node0 = Node(x=node.x, y=node.y, yaw=node.yaw, v=node.v) 307 | z0 = [er, 0.0, theta_e, 0.0, node.v] 308 | 309 | a_opt, delta_opt = \ 310 | linear_mpc_control(z_ref, node0, z0, a_opt, delta_opt) 311 | 312 | # node_opt = Node(x=node.x, y=node.y, yaw=node.yaw, v=node.v) 313 | # x_opt, y_opt = [node_opt.x], [node_opt.y] 314 | 315 | if delta_opt is not None: 316 | delta_exc, a_exc = delta_opt[0], a_opt[0] 317 | 318 | # for ao, do in zip(a_opt, delta_opt): 319 | # node_opt.update(ao, do, 1.0) 320 | # x_opt.append(node_opt.x) 321 | # y_opt.append(node_opt.y) 322 | 323 | node.update(a_exc, delta_exc, 1.0) 324 | time += P.dt 325 | 326 | x.append(node.x) 327 | y.append(node.y) 328 | yaw.append(node.yaw) 329 | v.append(node.v) 330 | t.append(time) 331 | d.append(delta_exc) 332 | a.append(a_exc) 333 | 334 | dist = math.hypot(node.x - cx[-1], node.y - cy[-1]) 335 | 336 | if dist < P.dist_stop and \ 337 | abs(node.v) < P.speed_stop: 338 | break 339 | 340 | plt.cla() 341 | plt.gcf().canvas.mpl_connect('key_release_event', 342 | lambda event: 343 | [exit(0) if event.key == 'escape' else None]) 344 | 345 | # if x_opt is not None: 346 | # plt.plot(x_opt, y_opt, 'xr') 347 | 348 | plt.plot(cx, cy, '-r') 349 | plt.plot(x, y, '-b') 350 | plt.plot(z_ref[0, :], z_ref[1, :], 'xk') 351 | plt.plot(cx[target_ind], cy[target_ind], 'xg') 352 | plt.axis("equal") 353 | plt.title("Linear MPC, " + "v = " + str(round(node.v * 3.6, 2))) 354 | plt.pause(0.001) 355 | 356 | plt.show() 357 | 358 | 359 | if __name__ == '__main__': 360 | main() -------------------------------------------------------------------------------- /Control/MPC_XY_Frame.py: -------------------------------------------------------------------------------- 1 | """ 2 | Linear MPC controller (X-Y frame) 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import cvxpy 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | 13 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 14 | "/../../MotionPlanning/") 15 | 16 | import Control.draw as draw 17 | import CurvesGenerator.reeds_shepp as rs 18 | import CurvesGenerator.cubic_spline as cs 19 | 20 | 21 | class P: 22 | # System config 23 | NX = 4 # state vector: z = [x, y, v, phi] 24 | NU = 2 # input vector: u = [acceleration, steer] 25 | T = 6 # finite time horizon length 26 | 27 | # MPC config 28 | Q = np.diag([1.0, 1.0, 1.0, 1.0]) # penalty for states 29 | Qf = np.diag([1.0, 1.0, 1.0, 1.0]) # penalty for end state 30 | R = np.diag([0.01, 0.1]) # penalty for inputs 31 | Rd = np.diag([0.01, 0.1]) # penalty for change of inputs 32 | 33 | dist_stop = 1.5 # stop permitted when dist to goal < dist_stop 34 | speed_stop = 0.5 / 3.6 # stop permitted when speed < speed_stop 35 | time_max = 500.0 # max simulation time 36 | iter_max = 5 # max iteration 37 | target_speed = 10.0 / 3.6 # target speed 38 | N_IND = 10 # search index number 39 | dt = 0.2 # time step 40 | d_dist = 1.0 # dist step 41 | du_res = 0.1 # threshold for stopping iteration 42 | 43 | # vehicle config 44 | RF = 3.3 # [m] distance from rear to vehicle front end of vehicle 45 | RB = 0.8 # [m] distance from rear to vehicle back end of vehicle 46 | W = 2.4 # [m] width of vehicle 47 | WD = 0.7 * W # [m] distance between left-right wheels 48 | WB = 2.5 # [m] Wheel base 49 | TR = 0.44 # [m] Tyre radius 50 | TW = 0.7 # [m] Tyre width 51 | 52 | steer_max = np.deg2rad(45.0) # max steering angle [rad] 53 | steer_change_max = np.deg2rad(30.0) # maximum steering speed [rad/s] 54 | speed_max = 55.0 / 3.6 # maximum speed [m/s] 55 | speed_min = -20.0 / 3.6 # minimum speed [m/s] 56 | acceleration_max = 1.0 # maximum acceleration [m/s2] 57 | 58 | 59 | class Node: 60 | def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0, direct=1.0): 61 | self.x = x 62 | self.y = y 63 | self.yaw = yaw 64 | self.v = v 65 | self.direct = direct 66 | 67 | def update(self, a, delta, direct): 68 | delta = self.limit_input_delta(delta) 69 | self.x += self.v * math.cos(self.yaw) * P.dt 70 | self.y += self.v * math.sin(self.yaw) * P.dt 71 | self.yaw += self.v / P.WB * math.tan(delta) * P.dt 72 | self.direct = direct 73 | self.v += self.direct * a * P.dt 74 | self.v = self.limit_speed(self.v) 75 | 76 | @staticmethod 77 | def limit_input_delta(delta): 78 | if delta >= P.steer_max: 79 | return P.steer_max 80 | 81 | if delta <= -P.steer_max: 82 | return -P.steer_max 83 | 84 | return delta 85 | 86 | @staticmethod 87 | def limit_speed(v): 88 | if v >= P.speed_max: 89 | return P.speed_max 90 | 91 | if v <= P.speed_min: 92 | return P.speed_min 93 | 94 | return v 95 | 96 | 97 | class PATH: 98 | def __init__(self, cx, cy, cyaw, ck): 99 | self.cx = cx 100 | self.cy = cy 101 | self.cyaw = cyaw 102 | self.ck = ck 103 | self.length = len(cx) 104 | self.ind_old = 0 105 | 106 | def nearest_index(self, node): 107 | """ 108 | calc index of the nearest node in N steps 109 | :param node: current information 110 | :return: nearest index, lateral distance to ref point 111 | """ 112 | 113 | dx = [node.x - x for x in self.cx[self.ind_old: (self.ind_old + P.N_IND)]] 114 | dy = [node.y - y for y in self.cy[self.ind_old: (self.ind_old + P.N_IND)]] 115 | dist = np.hypot(dx, dy) 116 | 117 | ind_in_N = int(np.argmin(dist)) 118 | ind = self.ind_old + ind_in_N 119 | self.ind_old = ind 120 | 121 | rear_axle_vec_rot_90 = np.array([[math.cos(node.yaw + math.pi / 2.0)], 122 | [math.sin(node.yaw + math.pi / 2.0)]]) 123 | 124 | vec_target_2_rear = np.array([[dx[ind_in_N]], 125 | [dy[ind_in_N]]]) 126 | 127 | er = np.dot(vec_target_2_rear.T, rear_axle_vec_rot_90) 128 | er = er[0][0] 129 | 130 | return ind, er 131 | 132 | 133 | def calc_ref_trajectory_in_T_step(node, ref_path, sp): 134 | """ 135 | calc referent trajectory in T steps: [x, y, v, yaw] 136 | using the current velocity, calc the T points along the reference path 137 | :param node: current information 138 | :param ref_path: reference path: [x, y, yaw] 139 | :param sp: speed profile (designed speed strategy) 140 | :return: reference trajectory 141 | """ 142 | 143 | z_ref = np.zeros((P.NX, P.T + 1)) 144 | length = ref_path.length 145 | 146 | ind, _ = ref_path.nearest_index(node) 147 | 148 | z_ref[0, 0] = ref_path.cx[ind] 149 | z_ref[1, 0] = ref_path.cy[ind] 150 | z_ref[2, 0] = sp[ind] 151 | z_ref[3, 0] = ref_path.cyaw[ind] 152 | 153 | dist_move = 0.0 154 | 155 | for i in range(1, P.T + 1): 156 | dist_move += abs(node.v) * P.dt 157 | ind_move = int(round(dist_move / P.d_dist)) 158 | index = min(ind + ind_move, length - 1) 159 | 160 | z_ref[0, i] = ref_path.cx[index] 161 | z_ref[1, i] = ref_path.cy[index] 162 | z_ref[2, i] = sp[index] 163 | z_ref[3, i] = ref_path.cyaw[index] 164 | 165 | return z_ref, ind 166 | 167 | 168 | def linear_mpc_control(z_ref, z0, a_old, delta_old): 169 | """ 170 | linear mpc controller 171 | :param z_ref: reference trajectory in T steps 172 | :param z0: initial state vector 173 | :param a_old: acceleration of T steps of last time 174 | :param delta_old: delta of T steps of last time 175 | :return: acceleration and delta strategy based on current information 176 | """ 177 | 178 | if a_old is None or delta_old is None: 179 | a_old = [0.0] * P.T 180 | delta_old = [0.0] * P.T 181 | 182 | x, y, yaw, v = None, None, None, None 183 | 184 | for k in range(P.iter_max): 185 | z_bar = predict_states_in_T_step(z0, a_old, delta_old, z_ref) 186 | a_rec, delta_rec = a_old[:], delta_old[:] 187 | a_old, delta_old, x, y, yaw, v = solve_linear_mpc(z_ref, z_bar, z0, delta_old) 188 | 189 | du_a_max = max([abs(ia - iao) for ia, iao in zip(a_old, a_rec)]) 190 | du_d_max = max([abs(ide - ido) for ide, ido in zip(delta_old, delta_rec)]) 191 | 192 | if max(du_a_max, du_d_max) < P.du_res: 193 | break 194 | 195 | return a_old, delta_old, x, y, yaw, v 196 | 197 | 198 | def predict_states_in_T_step(z0, a, delta, z_ref): 199 | """ 200 | given the current state, using the acceleration and delta strategy of last time, 201 | predict the states of vehicle in T steps. 202 | :param z0: initial state 203 | :param a: acceleration strategy of last time 204 | :param delta: delta strategy of last time 205 | :param z_ref: reference trajectory 206 | :return: predict states in T steps (z_bar, used for calc linear motion model) 207 | """ 208 | 209 | z_bar = z_ref * 0.0 210 | 211 | for i in range(P.NX): 212 | z_bar[i, 0] = z0[i] 213 | 214 | node = Node(x=z0[0], y=z0[1], v=z0[2], yaw=z0[3]) 215 | 216 | for ai, di, i in zip(a, delta, range(1, P.T + 1)): 217 | node.update(ai, di, 1.0) 218 | z_bar[0, i] = node.x 219 | z_bar[1, i] = node.y 220 | z_bar[2, i] = node.v 221 | z_bar[3, i] = node.yaw 222 | 223 | return z_bar 224 | 225 | 226 | def calc_linear_discrete_model(v, phi, delta): 227 | """ 228 | calc linear and discrete time dynamic model. 229 | :param v: speed: v_bar 230 | :param phi: angle of vehicle: phi_bar 231 | :param delta: steering angle: delta_bar 232 | :return: A, B, C 233 | """ 234 | 235 | A = np.array([[1.0, 0.0, P.dt * math.cos(phi), - P.dt * v * math.sin(phi)], 236 | [0.0, 1.0, P.dt * math.sin(phi), P.dt * v * math.cos(phi)], 237 | [0.0, 0.0, 1.0, 0.0], 238 | [0.0, 0.0, P.dt * math.tan(delta) / P.WB, 1.0]]) 239 | 240 | B = np.array([[0.0, 0.0], 241 | [0.0, 0.0], 242 | [P.dt, 0.0], 243 | [0.0, P.dt * v / (P.WB * math.cos(delta) ** 2)]]) 244 | 245 | C = np.array([P.dt * v * math.sin(phi) * phi, 246 | -P.dt * v * math.cos(phi) * phi, 247 | 0.0, 248 | -P.dt * v * delta / (P.WB * math.cos(delta) ** 2)]) 249 | 250 | return A, B, C 251 | 252 | 253 | def solve_linear_mpc(z_ref, z_bar, z0, d_bar): 254 | """ 255 | solve the quadratic optimization problem using cvxpy, solver: OSQP 256 | :param z_ref: reference trajectory (desired trajectory: [x, y, v, yaw]) 257 | :param z_bar: predicted states in T steps 258 | :param z0: initial state 259 | :param d_bar: delta_bar 260 | :return: optimal acceleration and steering strategy 261 | """ 262 | 263 | z = cvxpy.Variable((P.NX, P.T + 1)) 264 | u = cvxpy.Variable((P.NU, P.T)) 265 | 266 | cost = 0.0 267 | constrains = [] 268 | 269 | for t in range(P.T): 270 | cost += cvxpy.quad_form(u[:, t], P.R) 271 | cost += cvxpy.quad_form(z_ref[:, t] - z[:, t], P.Q) 272 | 273 | A, B, C = calc_linear_discrete_model(z_bar[2, t], z_bar[3, t], d_bar[t]) 274 | 275 | constrains += [z[:, t + 1] == A @ z[:, t] + B @ u[:, t] + C] 276 | 277 | if t < P.T - 1: 278 | cost += cvxpy.quad_form(u[:, t + 1] - u[:, t], P.Rd) 279 | constrains += [cvxpy.abs(u[1, t + 1] - u[1, t]) <= P.steer_change_max * P.dt] 280 | 281 | cost += cvxpy.quad_form(z_ref[:, P.T] - z[:, P.T], P.Qf) 282 | 283 | constrains += [z[:, 0] == z0] 284 | constrains += [z[2, :] <= P.speed_max] 285 | constrains += [z[2, :] >= P.speed_min] 286 | constrains += [cvxpy.abs(u[0, :]) <= P.acceleration_max] 287 | constrains += [cvxpy.abs(u[1, :]) <= P.steer_max] 288 | 289 | prob = cvxpy.Problem(cvxpy.Minimize(cost), constrains) 290 | prob.solve(solver=cvxpy.OSQP) 291 | 292 | a, delta, x, y, yaw, v = None, None, None, None, None, None 293 | 294 | if prob.status == cvxpy.OPTIMAL or \ 295 | prob.status == cvxpy.OPTIMAL_INACCURATE: 296 | x = z.value[0, :] 297 | y = z.value[1, :] 298 | v = z.value[2, :] 299 | yaw = z.value[3, :] 300 | a = u.value[0, :] 301 | delta = u.value[1, :] 302 | else: 303 | print("Cannot solve linear mpc!") 304 | 305 | return a, delta, x, y, yaw, v 306 | 307 | 308 | def calc_speed_profile(cx, cy, cyaw, target_speed): 309 | """ 310 | design appropriate speed strategy 311 | :param cx: x of reference path [m] 312 | :param cy: y of reference path [m] 313 | :param cyaw: yaw of reference path [m] 314 | :param target_speed: target speed [m/s] 315 | :return: speed profile 316 | """ 317 | 318 | speed_profile = [target_speed] * len(cx) 319 | direction = 1.0 # forward 320 | 321 | # Set stop point 322 | for i in range(len(cx) - 1): 323 | dx = cx[i + 1] - cx[i] 324 | dy = cy[i + 1] - cy[i] 325 | 326 | move_direction = math.atan2(dy, dx) 327 | 328 | if dx != 0.0 and dy != 0.0: 329 | dangle = abs(pi_2_pi(move_direction - cyaw[i])) 330 | if dangle >= math.pi / 4.0: 331 | direction = -1.0 332 | else: 333 | direction = 1.0 334 | 335 | if direction != 1.0: 336 | speed_profile[i] = - target_speed 337 | else: 338 | speed_profile[i] = target_speed 339 | 340 | speed_profile[-1] = 0.0 341 | 342 | return speed_profile 343 | 344 | 345 | def pi_2_pi(angle): 346 | if angle > math.pi: 347 | return angle - 2.0 * math.pi 348 | 349 | if angle < -math.pi: 350 | return angle + 2.0 * math.pi 351 | 352 | return angle 353 | 354 | 355 | def main(): 356 | ax = [0.0, 15.0, 30.0, 50.0, 60.0] 357 | ay = [0.0, 40.0, 15.0, 30.0, 0.0] 358 | cx, cy, cyaw, ck, s = cs.calc_spline_course( 359 | ax, ay, ds=P.d_dist) 360 | 361 | sp = calc_speed_profile(cx, cy, cyaw, P.target_speed) 362 | 363 | ref_path = PATH(cx, cy, cyaw, ck) 364 | node = Node(x=cx[0], y=cy[0], yaw=cyaw[0], v=0.0) 365 | 366 | time = 0.0 367 | x = [node.x] 368 | y = [node.y] 369 | yaw = [node.yaw] 370 | v = [node.v] 371 | t = [0.0] 372 | d = [0.0] 373 | a = [0.0] 374 | 375 | delta_opt, a_opt = None, None 376 | a_exc, delta_exc = 0.0, 0.0 377 | 378 | while time < P.time_max: 379 | z_ref, target_ind = \ 380 | calc_ref_trajectory_in_T_step(node, ref_path, sp) 381 | 382 | z0 = [node.x, node.y, node.v, node.yaw] 383 | 384 | a_opt, delta_opt, x_opt, y_opt, yaw_opt, v_opt = \ 385 | linear_mpc_control(z_ref, z0, a_opt, delta_opt) 386 | 387 | if delta_opt is not None: 388 | delta_exc, a_exc = delta_opt[0], a_opt[0] 389 | 390 | node.update(a_exc, delta_exc, 1.0) 391 | time += P.dt 392 | 393 | x.append(node.x) 394 | y.append(node.y) 395 | yaw.append(node.yaw) 396 | v.append(node.v) 397 | t.append(time) 398 | d.append(delta_exc) 399 | a.append(a_exc) 400 | 401 | dist = math.hypot(node.x - cx[-1], node.y - cy[-1]) 402 | 403 | if dist < P.dist_stop and \ 404 | abs(node.v) < P.speed_stop: 405 | break 406 | 407 | dy = (node.yaw - yaw[-2]) / (node.v * P.dt) 408 | steer = rs.pi_2_pi(-math.atan(P.WB * dy)) 409 | 410 | plt.cla() 411 | draw.draw_car(node.x, node.y, node.yaw, steer, P) 412 | plt.gcf().canvas.mpl_connect('key_release_event', 413 | lambda event: 414 | [exit(0) if event.key == 'escape' else None]) 415 | 416 | if x_opt is not None: 417 | plt.plot(x_opt, y_opt, color='darkviolet', marker='*') 418 | 419 | plt.plot(cx, cy, color='gray') 420 | plt.plot(x, y, '-b') 421 | plt.plot(cx[target_ind], cy[target_ind]) 422 | plt.axis("equal") 423 | plt.title("Linear MPC, " + "v = " + str(round(node.v * 3.6, 2))) 424 | plt.pause(0.001) 425 | 426 | plt.show() 427 | 428 | 429 | if __name__ == '__main__': 430 | main() 431 | -------------------------------------------------------------------------------- /Control/Pure_Pursuit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pure Pursuit 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 13 | "/../../MotionPlanning/") 14 | 15 | import Control.draw as draw 16 | import CurvesGenerator.reeds_shepp as rs 17 | 18 | 19 | class C: 20 | # PID config 21 | Kp = 0.3 # proportional gain 22 | 23 | # system config 24 | Ld = 2.6 # look ahead distance 25 | kf = 0.1 # look forward gain 26 | dt = 0.1 # T step 27 | dist_stop = 0.7 # stop distance 28 | dc = 0.0 29 | 30 | # vehicle config 31 | RF = 3.3 # [m] distance from rear to vehicle front end of vehicle 32 | RB = 0.8 # [m] distance from rear to vehicle back end of vehicle 33 | W = 2.4 # [m] width of vehicle 34 | WD = 0.7 * W # [m] distance between left-right wheels 35 | WB = 2.5 # [m] Wheel base 36 | TR = 0.44 # [m] Tyre radius 37 | TW = 0.7 # [m] Tyre width 38 | MAX_STEER = 0.30 39 | MAX_ACCELERATION = 5.0 40 | 41 | 42 | class Node: 43 | def __init__(self, x, y, yaw, v, direct): 44 | self.x = x 45 | self.y = y 46 | self.yaw = yaw 47 | self.v = v 48 | self.direct = direct 49 | 50 | def update(self, a, delta, direct): 51 | # delta = self.limit_input(delta) 52 | self.x += self.v * math.cos(self.yaw) * C.dt 53 | self.y += self.v * math.sin(self.yaw) * C.dt 54 | self.yaw += self.v / C.WB * math.tan(delta) * C.dt 55 | self.direct = direct 56 | self.v += self.direct * a * C.dt 57 | 58 | @staticmethod 59 | def limit_input(delta): 60 | if delta > 1.2 * C.MAX_STEER: 61 | return 1.2 * C.MAX_STEER 62 | 63 | if delta < -1.2 * C.MAX_STEER: 64 | return -1.2 * C.MAX_STEER 65 | 66 | return delta 67 | 68 | 69 | class Nodes: 70 | def __init__(self): 71 | self.x = [] 72 | self.y = [] 73 | self.yaw = [] 74 | self.v = [] 75 | self.t = [] 76 | self.direct = [] 77 | 78 | def add(self, t, node): 79 | self.x.append(node.x) 80 | self.y.append(node.y) 81 | self.yaw.append(node.yaw) 82 | self.v.append(node.v) 83 | self.t.append(t) 84 | self.direct.append(node.direct) 85 | 86 | 87 | class PATH: 88 | def __init__(self, cx, cy): 89 | self.cx = cx 90 | self.cy = cy 91 | self.ind_end = len(self.cx) - 1 92 | self.index_old = None 93 | 94 | def target_index(self, node): 95 | """ 96 | search index of target point in the reference path. 97 | the distance between target point and current position is ld 98 | :param node: current information 99 | :return: index of target point 100 | """ 101 | 102 | if self.index_old is None: 103 | self.calc_nearest_ind(node) 104 | 105 | Lf = C.kf * node.v + C.Ld 106 | 107 | for ind in range(self.index_old, self.ind_end + 1): 108 | if self.calc_distance(node, ind) > Lf: 109 | self.index_old = ind 110 | return ind, Lf 111 | 112 | self.index_old = self.ind_end 113 | 114 | return self.ind_end, Lf 115 | 116 | def calc_nearest_ind(self, node): 117 | """ 118 | calc index of the nearest point to current position 119 | :param node: current information 120 | :return: index of nearest point 121 | """ 122 | 123 | dx = [node.x - x for x in self.cx] 124 | dy = [node.y - y for y in self.cy] 125 | ind = np.argmin(np.hypot(dx, dy)) 126 | self.index_old = ind 127 | 128 | def calc_distance(self, node, ind): 129 | return math.hypot(node.x - self.cx[ind], node.y - self.cy[ind]) 130 | 131 | 132 | def pure_pursuit(node, ref_path, index_old): 133 | """ 134 | pure pursuit controller 135 | :param node: current information 136 | :param ref_path: reference path: x, y, yaw, curvature 137 | :param index_old: target index of last time 138 | :return: optimal steering angle 139 | """ 140 | 141 | ind, Lf = ref_path.target_index(node) # target point and pursuit distance 142 | ind = max(ind, index_old) 143 | 144 | tx = ref_path.cx[ind] 145 | ty = ref_path.cy[ind] 146 | 147 | alpha = math.atan2(ty - node.y, tx - node.x) - node.yaw 148 | delta = math.atan2(2.0 * C.WB * math.sin(alpha), Lf) 149 | 150 | return delta, ind 151 | 152 | 153 | def pid_control(target_v, v, dist, direct): 154 | """ 155 | PID controller and design speed profile. 156 | :param target_v: target speed (forward and backward are different) 157 | :param v: current speed 158 | :param dist: distance from current position to end position 159 | :param direct: current direction 160 | :return: desired acceleration 161 | """ 162 | 163 | a = 0.3 * (target_v - direct * v) 164 | 165 | if dist < 10.0: 166 | if v > 3.0: 167 | a = -2.5 168 | elif v < -2.0: 169 | a = -1.0 170 | 171 | return a 172 | 173 | 174 | def generate_path(s): 175 | """ 176 | divide paths into some sections, in each section, the direction is the same. 177 | :param s: target position and yaw 178 | :return: sections 179 | """ 180 | 181 | max_c = math.tan(C.MAX_STEER) / C.WB # max curvature 182 | 183 | path_x, path_y, yaw, direct = [], [], [], [] 184 | x_rec, y_rec, yaw_rec, direct_rec = [], [], [], [] 185 | direct_flag = 1.0 186 | 187 | for i in range(len(s) - 1): 188 | s_x, s_y, s_yaw = s[i][0], s[i][1], np.deg2rad(s[i][2]) 189 | g_x, g_y, g_yaw = s[i + 1][0], s[i + 1][1], np.deg2rad(s[i + 1][2]) 190 | 191 | path_i = rs.calc_optimal_path(s_x, s_y, s_yaw, 192 | g_x, g_y, g_yaw, max_c) 193 | 194 | ix = path_i.x 195 | iy = path_i.y 196 | iyaw = path_i.yaw 197 | idirect = path_i.directions 198 | 199 | for j in range(len(ix)): 200 | if idirect[j] == direct_flag: 201 | x_rec.append(ix[j]) 202 | y_rec.append(iy[j]) 203 | yaw_rec.append(iyaw[j]) 204 | direct_rec.append(idirect[j]) 205 | else: 206 | if len(x_rec) == 0 or direct_rec[0] != direct_flag: 207 | direct_flag = idirect[j] 208 | continue 209 | 210 | path_x.append(x_rec) 211 | path_y.append(y_rec) 212 | yaw.append(yaw_rec) 213 | direct.append(direct_rec) 214 | x_rec, y_rec, yaw_rec, direct_rec = \ 215 | [x_rec[-1]], [y_rec[-1]], [yaw_rec[-1]], [-direct_rec[-1]] 216 | 217 | path_x.append(x_rec) 218 | path_y.append(y_rec) 219 | yaw.append(yaw_rec) 220 | direct.append(direct_rec) 221 | 222 | x_all, y_all = [], [] 223 | 224 | for ix, iy in zip(path_x, path_y): 225 | x_all += ix 226 | y_all += iy 227 | 228 | return path_x, path_y, yaw, direct, x_all, y_all 229 | 230 | 231 | def main(): 232 | # generate path: [x, y, yaw] 233 | states = [(0, 0, 0), (20, 15, 0), (35, 20, 90), (40, 0, 180), 234 | (20, 0, 120), (5, -10, 180), (15, 5, 30)] 235 | 236 | # states = [(-3, 3, 120), (10, -7, 30), (10, 13, 30), (20, 5, -25), 237 | # (35, 10, 180), (30, -10, 160), (5, -12, 90)] 238 | 239 | x, y, yaw, direct, path_x, path_y = generate_path(states) 240 | 241 | # simulation 242 | maxTime = 100.0 243 | yaw_old = 0.0 244 | x0, y0, yaw0, direct0 = x[0][0], y[0][0], yaw[0][0], direct[0][0] 245 | x_rec, y_rec = [], [] 246 | 247 | for cx, cy, cyaw, cdirect in zip(x, y, yaw, direct): 248 | t = 0.0 249 | node = Node(x=x0, y=y0, yaw=yaw0, v=0.0, direct=direct0) 250 | nodes = Nodes() 251 | nodes.add(t, node) 252 | ref_trajectory = PATH(cx, cy) 253 | target_ind, _ = ref_trajectory.target_index(node) 254 | 255 | while t <= maxTime: 256 | if cdirect[0] > 0: 257 | target_speed = 30.0 / 3.6 258 | C.Ld = 4.0 259 | C.dist_stop = 1.5 260 | C.dc = -1.1 261 | else: 262 | target_speed = 20.0 / 3.6 263 | C.Ld = 2.5 264 | C.dist_stop = 0.2 265 | C.dc = 0.2 266 | 267 | xt = node.x + C.dc * math.cos(node.yaw) 268 | yt = node.y + C.dc * math.sin(node.yaw) 269 | dist = math.hypot(xt - cx[-1], yt - cy[-1]) 270 | 271 | if dist < C.dist_stop: 272 | break 273 | 274 | acceleration = pid_control(target_speed, node.v, dist, cdirect[0]) 275 | delta, target_ind = pure_pursuit(node, ref_trajectory, target_ind) 276 | 277 | t += C.dt 278 | 279 | node.update(acceleration, delta, cdirect[0]) 280 | nodes.add(t, node) 281 | x_rec.append(node.x) 282 | y_rec.append(node.y) 283 | 284 | dy = (node.yaw - yaw_old) / (node.v * C.dt) 285 | steer = rs.pi_2_pi(-math.atan(C.WB * dy)) 286 | 287 | yaw_old = node.yaw 288 | x0 = nodes.x[-1] 289 | y0 = nodes.y[-1] 290 | yaw0 = nodes.yaw[-1] 291 | direct0 = nodes.direct[-1] 292 | 293 | # animation 294 | plt.cla() 295 | plt.plot(node.x, node.y, marker='.', color='k') 296 | plt.plot(path_x, path_y, color='gray', linewidth=2) 297 | plt.plot(x_rec, y_rec, color='darkviolet', linewidth=2) 298 | plt.plot(cx[target_ind], cy[target_ind], ".r") 299 | draw.draw_car(node.x, node.y, yaw_old, steer, C) 300 | 301 | # for m in range(len(states)): 302 | # draw.Arrow(states[m][0], states[m][1], np.deg2rad(states[m][2]), 2, 'blue') 303 | 304 | plt.axis("equal") 305 | plt.title("PurePursuit: v=" + str(node.v * 3.6)[:4] + "km/h") 306 | plt.gcf().canvas.mpl_connect('key_release_event', 307 | lambda event: 308 | [exit(0) if event.key == 'escape' else None]) 309 | plt.pause(0.001) 310 | 311 | plt.show() 312 | 313 | 314 | if __name__ == '__main__': 315 | main() 316 | -------------------------------------------------------------------------------- /Control/Rear_Wheel_Feedback.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rear-Wheel Feedback Controller 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 13 | "/../../MotionPlanning/") 14 | 15 | import Control.draw as draw 16 | import CurvesGenerator.reeds_shepp as rs 17 | 18 | 19 | class C: 20 | # PID config 21 | Kp = 1.0 22 | 23 | # System config 24 | K_theta = 1.0 25 | K_e = 0.5 26 | dt = 0.1 27 | dist_stop = 0.2 28 | 29 | # vehicle config 30 | RF = 3.3 # [m] distance from rear to vehicle front end of vehicle 31 | RB = 0.8 # [m] distance from rear to vehicle back end of vehicle 32 | W = 2.4 # [m] width of vehicle 33 | WD = 0.7 * W # [m] distance between left-right wheels 34 | WB = 2.5 # [m] Wheel base 35 | TR = 0.44 # [m] Tyre radius 36 | TW = 0.7 # [m] Tyre width 37 | MAX_STEER = 0.25 38 | 39 | 40 | class Node: 41 | def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0, direct=1.0): 42 | self.x = x 43 | self.y = y 44 | self.yaw = yaw 45 | self.v = v 46 | self.direct = direct 47 | 48 | def update(self, a, delta, direct): 49 | self.x += self.v * math.cos(self.yaw) * C.dt 50 | self.y += self.v * math.sin(self.yaw) * C.dt 51 | self.yaw += self.v / C.WB * math.tan(delta) * C.dt 52 | self.direct = direct 53 | self.v += self.direct * a * C.dt 54 | 55 | 56 | class PATH: 57 | def __init__(self, cx, cy, cyaw, ccurv): 58 | self.cx = cx 59 | self.cy = cy 60 | self.cyaw = cyaw 61 | self.ccurv = ccurv 62 | self.len = len(self.cx) 63 | self.s0 = 1 64 | 65 | def calc_theta_e_and_er(self, node): 66 | """ 67 | calc theta_e and er. 68 | theta_e = theta_car - theta_path 69 | er = lateral distance in frenet frame 70 | 71 | :param node: current information of vehicle 72 | :return: theta_e and er 73 | """ 74 | 75 | ind = self.nearest_index(node) 76 | 77 | k = self.ccurv[ind] 78 | yaw = self.cyaw[ind] 79 | 80 | rear_axle_vec_rot_90 = np.array([[math.cos(node.yaw + math.pi / 2.0)], 81 | [math.sin(node.yaw + math.pi / 2.0)]]) 82 | 83 | vec_target_2_rear = np.array([[node.x - self.cx[ind]], 84 | [node.y - self.cy[ind]]]) 85 | 86 | er = np.dot(vec_target_2_rear.T, rear_axle_vec_rot_90) 87 | theta_e = pi_2_pi(node.yaw - self.cyaw[ind]) 88 | 89 | return theta_e, er, k, yaw, ind 90 | 91 | def nearest_index(self, node): 92 | """ 93 | find the index of the nearest point to current position. 94 | :param node: current information 95 | :return: nearest index 96 | """ 97 | 98 | dx = [node.x - x for x in self.cx] 99 | dy = [node.y - y for y in self.cy] 100 | dist = np.hypot(dx, dy) 101 | self.s0 += np.argmin(dist[self.s0:self.len]) 102 | 103 | return self.s0 104 | 105 | 106 | def rear_wheel_feedback_control(node, ref_path): 107 | """ 108 | rear wheel feedback controller 109 | :param node: current information 110 | :param ref_path: reference path: x, y, yaw, curvature 111 | :return: optimal steering angle 112 | """ 113 | 114 | theta_e, er, k, yaw, ind = ref_path.calc_theta_e_and_er(node) 115 | vr = node.v 116 | 117 | omega = vr * k * math.cos(theta_e) / (1.0 - k * er) - \ 118 | C.K_theta * abs(vr) * theta_e - C.K_e * vr * math.sin(theta_e) * er / theta_e 119 | 120 | delta = math.atan2(C.WB * omega, vr) 121 | 122 | return delta, ind 123 | 124 | 125 | def pi_2_pi(angle): 126 | if angle > math.pi: 127 | return angle - 2.0 * math.pi 128 | if angle < -math.pi: 129 | return angle + 2.0 * math.pi 130 | 131 | return angle 132 | 133 | 134 | def pid_control(target_v, v, dist, direct): 135 | """ 136 | using LQR as lateral controller, PID as longitudinal controller (speed control) 137 | :param target_v: target speed 138 | :param v: current speed 139 | :param dist: distance to end point 140 | :param direct: current direction of vehicle, 1.0: forward, -1.0: backward 141 | :return: acceleration 142 | """ 143 | 144 | a = 0.3 * (target_v - direct * v) 145 | 146 | if dist < 10.0: 147 | if v > 2: 148 | a = -3.0 149 | elif v < -2: 150 | a = -1.0 151 | 152 | return a 153 | 154 | 155 | def generate_path(s): 156 | """ 157 | design path using reeds-shepp path generator. 158 | divide paths into sections, in each section the direction is the same. 159 | :param s: objective positions and directions. 160 | :return: paths 161 | """ 162 | 163 | max_c = math.tan(C.MAX_STEER) / C.WB 164 | path_x, path_y, yaw, direct, rc = [], [], [], [], [] 165 | x_rec, y_rec, yaw_rec, direct_rec, rc_rec = [], [], [], [], [] 166 | direct_flag = 1.0 167 | 168 | for i in range(len(s) - 1): 169 | s_x, s_y, s_yaw = s[i][0], s[i][1], np.deg2rad(s[i][2]) 170 | g_x, g_y, g_yaw = s[i + 1][0], s[i + 1][1], np.deg2rad(s[i + 1][2]) 171 | 172 | path_i = rs.calc_optimal_path(s_x, s_y, s_yaw, 173 | g_x, g_y, g_yaw, max_c) 174 | 175 | irc, rds = rs.calc_curvature(path_i.x, path_i.y, path_i.yaw, path_i.directions) 176 | 177 | ix = path_i.x 178 | iy = path_i.y 179 | iyaw = path_i.yaw 180 | idirect = path_i.directions 181 | 182 | for j in range(len(ix)): 183 | if idirect[j] == direct_flag: 184 | x_rec.append(ix[j]) 185 | y_rec.append(iy[j]) 186 | yaw_rec.append(iyaw[j]) 187 | direct_rec.append(idirect[j]) 188 | rc_rec.append(irc[j]) 189 | else: 190 | if len(x_rec) == 0 or direct_rec[0] != direct_flag: 191 | direct_flag = idirect[j] 192 | continue 193 | 194 | path_x.append(x_rec) 195 | path_y.append(y_rec) 196 | yaw.append(yaw_rec) 197 | direct.append(direct_rec) 198 | rc.append(rc_rec) 199 | x_rec, y_rec, yaw_rec, direct_rec, rc_rec = \ 200 | [x_rec[-1]], [y_rec[-1]], [yaw_rec[-1]], [-direct_rec[-1]], [rc_rec[-1]] 201 | 202 | path_x.append(x_rec) 203 | path_y.append(y_rec) 204 | yaw.append(yaw_rec) 205 | direct.append(direct_rec) 206 | rc.append(rc_rec) 207 | 208 | x_all, y_all = [], [] 209 | for ix, iy in zip(path_x, path_y): 210 | x_all += ix 211 | y_all += iy 212 | 213 | return path_x, path_y, yaw, direct, rc, x_all, y_all 214 | 215 | 216 | def main(): 217 | # generate path 218 | states = [(0, 0, 0), (20, 15, 0), (35, 20, 90), (40, 0, 180), 219 | (20, 0, 120), (5, -10, 180), (15, 5, 30)] 220 | # 221 | # states = [(-3, 3, 120), (10, -7, 30), (10, 13, 30), (20, 5, -25), 222 | # (35, 10, 180), (30, -10, 160), (5, -12, 90)] 223 | 224 | x_ref, y_ref, yaw_ref, direct, curv, x_all, y_all = generate_path(states) 225 | 226 | maxTime = 100.0 227 | yaw_old = 0.0 228 | x0, y0, yaw0, direct0 = \ 229 | x_ref[0][0], y_ref[0][0], yaw_ref[0][0], direct[0][0] 230 | 231 | x_rec, y_rec, yaw_rec, direct_rec = [], [], [], [] 232 | 233 | for cx, cy, cyaw, cdirect, ccurv in zip(x_ref, y_ref, yaw_ref, direct, curv): 234 | t = 0.0 235 | node = Node(x=x0, y=y0, yaw=yaw0, v=0.0, direct=cdirect[0]) 236 | ref_trajectory = PATH(cx, cy, cyaw, ccurv) 237 | 238 | while t < maxTime: 239 | if cdirect[0] > 0: 240 | speed_ref = 30.0 / 3.6 241 | C.Ld = 3.5 242 | else: 243 | speed_ref = 15.0 / 3.6 244 | C.Ld = 2.5 245 | 246 | delta, ind = rear_wheel_feedback_control(node, ref_trajectory) 247 | 248 | dist = math.hypot(node.x - cx[-1], node.y - cy[-1]) 249 | 250 | acceleration = pid_control(speed_ref, node.v, dist, node.direct) 251 | node.update(acceleration, delta, node.direct) 252 | t += C.dt 253 | 254 | if dist <= C.dist_stop: 255 | break 256 | 257 | x_rec.append(node.x) 258 | y_rec.append(node.y) 259 | yaw_rec.append(node.yaw) 260 | direct_rec.append(node.direct) 261 | 262 | dy = (node.yaw - yaw_old) / (node.v * C.dt) 263 | steer = rs.pi_2_pi(-math.atan(C.WB * dy)) 264 | 265 | yaw_old = node.yaw 266 | x0 = x_rec[-1] 267 | y0 = y_rec[-1] 268 | yaw0 = yaw_rec[-1] 269 | 270 | plt.cla() 271 | plt.plot(x_all, y_all, color='gray', linewidth=2.0) 272 | plt.plot(x_rec, y_rec, linewidth=2.0, color='darkviolet') 273 | plt.plot(cx[ind], cy[ind], '.r') 274 | draw.draw_car(node.x, node.y, node.yaw, steer, C) 275 | plt.axis("equal") 276 | plt.title("RearWheelFeedback: v=" + str(node.v * 3.6)[:4] + "km/h") 277 | plt.gcf().canvas.mpl_connect('key_release_event', 278 | lambda event: 279 | [exit(0) if event.key == 'escape' else None]) 280 | plt.pause(0.001) 281 | 282 | plt.show() 283 | 284 | 285 | if __name__ == '__main__': 286 | main() 287 | -------------------------------------------------------------------------------- /Control/Stanley.py: -------------------------------------------------------------------------------- 1 | """ 2 | Front-Wheel Feedback Controller (Stanley) 3 | author: huiming zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 13 | "/../../MotionPlanning/") 14 | 15 | import Control.draw as draw 16 | import CurvesGenerator.reeds_shepp as rs 17 | import CurvesGenerator.cubic_spline as cs 18 | 19 | 20 | class C: 21 | # PID config 22 | Kp = 1.0 23 | 24 | # System config 25 | k = 0.5 26 | dt = 0.1 27 | dref = 0.5 28 | 29 | # vehicle config 30 | RF = 3.3 # [m] distance from rear to vehicle front end of vehicle 31 | RB = 0.8 # [m] distance from rear to vehicle back end of vehicle 32 | W = 2.4 # [m] width of vehicle 33 | WD = 0.7 * W # [m] distance between left-right wheels 34 | WB = 2.5 # [m] Wheel base 35 | TR = 0.44 # [m] Tyre radius 36 | TW = 0.7 # [m] Tyre width 37 | MAX_STEER = 0.65 38 | 39 | 40 | class Node: 41 | def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): 42 | self.x = x 43 | self.y = y 44 | self.yaw = yaw 45 | self.v = v 46 | 47 | def update(self, a, delta): 48 | delta = self.limit_input(delta) 49 | self.x += self.v * math.cos(self.yaw) * C.dt 50 | self.y += self.v * math.sin(self.yaw) * C.dt 51 | self.yaw += self.v / C.WB * math.tan(delta) * C.dt 52 | self.v += a * C.dt 53 | 54 | @staticmethod 55 | def limit_input(delta): 56 | if delta > C.MAX_STEER: 57 | return C.MAX_STEER 58 | 59 | if delta < -C.MAX_STEER: 60 | return -C.MAX_STEER 61 | 62 | return delta 63 | 64 | 65 | class Trajectory: 66 | def __init__(self, cx, cy, cyaw): 67 | self.cx = cx 68 | self.cy = cy 69 | self.cyaw = cyaw 70 | self.ind_old = 0 71 | 72 | def calc_theta_e_and_ef(self, node): 73 | """ 74 | calc theta_e and ef. 75 | theta_e = theta_car - theta_path 76 | ef = lateral distance in frenet frame (front wheel) 77 | 78 | :param node: current information of vehicle 79 | :return: theta_e and ef 80 | """ 81 | 82 | fx = node.x + C.WB * math.cos(node.yaw) 83 | fy = node.y + C.WB * math.sin(node.yaw) 84 | 85 | dx = [fx - x for x in self.cx] 86 | dy = [fy - y for y in self.cy] 87 | 88 | target_index = int(np.argmin(np.hypot(dx, dy))) 89 | target_index = max(self.ind_old, target_index) 90 | self.ind_old = max(self.ind_old, target_index) 91 | 92 | front_axle_vec_rot_90 = np.array([[math.cos(node.yaw - math.pi / 2.0)], 93 | [math.sin(node.yaw - math.pi / 2.0)]]) 94 | 95 | vec_target_2_front = np.array([[dx[target_index]], 96 | [dy[target_index]]]) 97 | 98 | ef = np.dot(vec_target_2_front.T, front_axle_vec_rot_90) 99 | 100 | theta = node.yaw 101 | theta_p = self.cyaw[target_index] 102 | theta_e = pi_2_pi(theta_p - theta) 103 | 104 | return theta_e, ef, target_index 105 | 106 | 107 | def front_wheel_feedback_control(node, ref_path): 108 | """ 109 | front wheel feedback controller 110 | :param node: current information 111 | :param ref_path: reference path: x, y, yaw, curvature 112 | :return: optimal steering angle 113 | """ 114 | 115 | theta_e, ef, target_index = ref_path.calc_theta_e_and_ef(node) 116 | delta = theta_e + math.atan2(C.k * ef, node.v) 117 | 118 | return delta, target_index 119 | 120 | 121 | def pi_2_pi(angle): 122 | if angle > math.pi: 123 | return angle - 2.0 * math.pi 124 | if angle < -math.pi: 125 | return angle + 2.0 * math.pi 126 | 127 | return angle 128 | 129 | 130 | def pid_control(target_v, v, dist): 131 | """ 132 | PID controller and design speed profile. 133 | :param target_v: target speed 134 | :param v: current speed 135 | :param dist: distance to end point 136 | :return: acceleration 137 | """ 138 | 139 | a = 0.3 * (target_v - v) 140 | 141 | if dist < 10.0: 142 | if v > 3.0: 143 | a = -2.5 144 | elif v < -2.0: 145 | a = -1.0 146 | 147 | return a 148 | 149 | 150 | def main(): 151 | # generate path 152 | ax = np.arange(0, 50, 0.5) 153 | ay = [math.sin(ix / 5.0) * ix / 2.0 for ix in ax] 154 | 155 | cx, cy, cyaw, _, _ = cs.calc_spline_course(ax, ay, ds=C.dt) 156 | 157 | t = 0.0 158 | maxTime = 100.0 159 | yaw_old = 0.0 160 | x0, y0, yaw0 = cx[0], cy[0], cyaw[0] 161 | xrec, yrec, yawrec = [], [], [] 162 | 163 | node = Node(x=x0, y=y0, yaw=yaw0, v=0.0) 164 | ref_path = Trajectory(cx, cy, cyaw) 165 | 166 | while t < maxTime: 167 | speed_ref = 25.0 / 3.6 168 | C.Ld = 3.5 169 | 170 | di, target_index = front_wheel_feedback_control(node, ref_path) 171 | 172 | dist = math.hypot(node.x - cx[-1], node.y - cy[-1]) 173 | ai = pid_control(speed_ref, node.v, dist) 174 | node.update(ai, di) 175 | t += C.dt 176 | 177 | if dist <= C.dref: 178 | break 179 | 180 | dy = (node.yaw - yaw_old) / (node.v * C.dt) 181 | steer = rs.pi_2_pi(-math.atan(C.WB * dy)) 182 | yaw_old = node.yaw 183 | 184 | xrec.append(node.x) 185 | yrec.append(node.y) 186 | yawrec.append(node.yaw) 187 | 188 | plt.cla() 189 | plt.plot(cx, cy, color='gray', linewidth=2.0) 190 | plt.plot(xrec, yrec, linewidth=2.0, color='darkviolet') 191 | plt.plot(cx[target_index], cy[target_index], '.r') 192 | draw.draw_car(node.x, node.y, node.yaw, steer, C) 193 | plt.axis("equal") 194 | plt.title("FrontWheelFeedback: v=" + str(node.v * 3.6)[:4] + "km/h") 195 | plt.gcf().canvas.mpl_connect('key_release_event', 196 | lambda event: 197 | [exit(0) if event.key == 'escape' else None]) 198 | plt.pause(0.001) 199 | 200 | plt.show() 201 | 202 | 203 | if __name__ == '__main__': 204 | main() 205 | -------------------------------------------------------------------------------- /Control/__pycache__/config_control.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/__pycache__/config_control.cpython-37.pyc -------------------------------------------------------------------------------- /Control/__pycache__/draw.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/__pycache__/draw.cpython-37.pyc -------------------------------------------------------------------------------- /Control/__pycache__/draw.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/__pycache__/draw.cpython-38.pyc -------------------------------------------------------------------------------- /Control/__pycache__/draw_lqr.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/__pycache__/draw_lqr.cpython-37.pyc -------------------------------------------------------------------------------- /Control/config_control.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # Vehicle config 4 | wheelbase = 2.33 # wheel base: front to rear axle [m] 5 | wheeldist = 1.85 # wheel dist: left to right wheel [m] 6 | v_w = 2.33 # vehicle width [m] 7 | r_b = 0.80 # rear to back [m] 8 | r_f = 3.15 # rear to front [m] 9 | t_r = 0.40 # tire radius [m] 10 | t_w = 0.30 # tire width [m] 11 | 12 | c_f = 155494.663 # [N / rad] 13 | c_r = 155494.663 # [N / rad] 14 | m_f = 570 # [kg] 15 | m_r = 570 # [kg] 16 | l_f = 1.165 # [m] 17 | l_r = 1.165 # [m] 18 | Iz = 1436.24 # [kg m2] 19 | 20 | # Controller Config 21 | ts = 0.10 # [s] 22 | max_iteration = 150 23 | eps = 0.01 24 | 25 | matrix_q = [1.0, 0.0, 1.0, 0.0] 26 | matrix_r = [1.0] 27 | 28 | state_size = 4 29 | 30 | max_acceleration = 5.0 # [m / s^2] 31 | max_steer_angle = np.deg2rad(40) # [rad] 32 | max_speed = 30 / 3.6 # [km/h] -------------------------------------------------------------------------------- /Control/draw.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | class Arrow: 7 | def __init__(self, x, y, theta, L, c): 8 | angle = np.deg2rad(30) 9 | d = 0.4 * L 10 | w = 2 11 | 12 | x_start = x 13 | y_start = y 14 | x_end = x + L * np.cos(theta) 15 | y_end = y + L * np.sin(theta) 16 | 17 | theta_hat_L = theta + math.pi - angle 18 | theta_hat_R = theta + math.pi + angle 19 | 20 | x_hat_start = x_end 21 | x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L) 22 | x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R) 23 | 24 | y_hat_start = y_end 25 | y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L) 26 | y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R) 27 | 28 | plt.plot([x_start, x_end], [y_start, y_end], color=c, linewidth=w) 29 | plt.plot([x_hat_start, x_hat_end_L], 30 | [y_hat_start, y_hat_end_L], color=c, linewidth=w) 31 | plt.plot([x_hat_start, x_hat_end_R], 32 | [y_hat_start, y_hat_end_R], color=c, linewidth=w) 33 | 34 | 35 | def draw_car(x, y, yaw, steer, C, color='black'): 36 | car = np.array([[-C.RB, -C.RB, C.RF, C.RF, -C.RB], 37 | [C.W / 2, -C.W / 2, -C.W / 2, C.W / 2, C.W / 2]]) 38 | 39 | wheel = np.array([[-C.TR, -C.TR, C.TR, C.TR, -C.TR], 40 | [C.TW / 4, -C.TW / 4, -C.TW / 4, C.TW / 4, C.TW / 4]]) 41 | 42 | rlWheel = wheel.copy() 43 | rrWheel = wheel.copy() 44 | frWheel = wheel.copy() 45 | flWheel = wheel.copy() 46 | 47 | Rot1 = np.array([[math.cos(yaw), -math.sin(yaw)], 48 | [math.sin(yaw), math.cos(yaw)]]) 49 | 50 | Rot2 = np.array([[math.cos(steer), math.sin(steer)], 51 | [-math.sin(steer), math.cos(steer)]]) 52 | 53 | frWheel = np.dot(Rot2, frWheel) 54 | flWheel = np.dot(Rot2, flWheel) 55 | 56 | frWheel += np.array([[C.WB], [-C.WD / 2]]) 57 | flWheel += np.array([[C.WB], [C.WD / 2]]) 58 | rrWheel[1, :] -= C.WD / 2 59 | rlWheel[1, :] += C.WD / 2 60 | 61 | frWheel = np.dot(Rot1, frWheel) 62 | flWheel = np.dot(Rot1, flWheel) 63 | 64 | rrWheel = np.dot(Rot1, rrWheel) 65 | rlWheel = np.dot(Rot1, rlWheel) 66 | car = np.dot(Rot1, car) 67 | 68 | frWheel += np.array([[x], [y]]) 69 | flWheel += np.array([[x], [y]]) 70 | rrWheel += np.array([[x], [y]]) 71 | rlWheel += np.array([[x], [y]]) 72 | car += np.array([[x], [y]]) 73 | 74 | plt.plot(car[0, :], car[1, :], color) 75 | plt.plot(frWheel[0, :], frWheel[1, :], color) 76 | plt.plot(rrWheel[0, :], rrWheel[1, :], color) 77 | plt.plot(flWheel[0, :], flWheel[1, :], color) 78 | plt.plot(rlWheel[0, :], rlWheel[1, :], color) 79 | Arrow(x, y, yaw, C.WB * 0.6, color) 80 | -------------------------------------------------------------------------------- /Control/draw_lqr.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from Control.config_control import * 3 | 4 | PI = np.pi 5 | 6 | 7 | class Arrow: 8 | def __init__(self, x, y, theta, L, c): 9 | angle = np.deg2rad(30) 10 | d = 0.3 * L 11 | w = 2 12 | 13 | x_start = x 14 | y_start = y 15 | x_end = x + L * np.cos(theta) 16 | y_end = y + L * np.sin(theta) 17 | 18 | theta_hat_L = theta + PI - angle 19 | theta_hat_R = theta + PI + angle 20 | 21 | x_hat_start = x_end 22 | x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L) 23 | x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R) 24 | 25 | y_hat_start = y_end 26 | y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L) 27 | y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R) 28 | 29 | plt.plot([x_start, x_end], [y_start, y_end], color=c, linewidth=w) 30 | plt.plot([x_hat_start, x_hat_end_L], 31 | [y_hat_start, y_hat_end_L], color=c, linewidth=w) 32 | plt.plot([x_hat_start, x_hat_end_R], 33 | [y_hat_start, y_hat_end_R], color=c, linewidth=w) 34 | 35 | 36 | def draw_car(x, y, yaw, steer, color='black'): 37 | car = np.array([[-r_b, -r_b, r_f, r_f, -r_b], 38 | [v_w / 2, -v_w / 2, -v_w / 2, v_w / 2, v_w / 2]]) 39 | 40 | wheel = np.array([[-t_r, -t_r, t_r, t_r, -t_r], 41 | [t_w / 2, -t_w / 2, -t_w / 2, t_w / 2, t_w / 2]]) 42 | 43 | rlWheel = wheel.copy() 44 | rrWheel = wheel.copy() 45 | frWheel = wheel.copy() 46 | flWheel = wheel.copy() 47 | 48 | Rot1 = np.array([[np.cos(yaw), -np.sin(yaw)], 49 | [np.sin(yaw), np.cos(yaw)]]) 50 | 51 | Rot2 = np.array([[np.cos(steer), np.sin(steer)], 52 | [-np.sin(steer), np.cos(steer)]]) 53 | 54 | frWheel = np.dot(Rot2, frWheel) 55 | flWheel = np.dot(Rot2, flWheel) 56 | 57 | frWheel += np.array([[wheelbase], [-wheeldist / 2]]) 58 | flWheel += np.array([[wheelbase], [wheeldist / 2]]) 59 | rrWheel[1, :] -= wheeldist / 2 60 | rlWheel[1, :] += wheeldist / 2 61 | 62 | frWheel = np.dot(Rot1, frWheel) 63 | flWheel = np.dot(Rot1, flWheel) 64 | 65 | rrWheel = np.dot(Rot1, rrWheel) 66 | rlWheel = np.dot(Rot1, rlWheel) 67 | car = np.dot(Rot1, car) 68 | 69 | frWheel += np.array([[x], [y]]) 70 | flWheel += np.array([[x], [y]]) 71 | rrWheel += np.array([[x], [y]]) 72 | rlWheel += np.array([[x], [y]]) 73 | car += np.array([[x], [y]]) 74 | 75 | plt.plot(car[0, :], car[1, :], color) 76 | plt.plot(frWheel[0, :], frWheel[1, :], color) 77 | plt.plot(rrWheel[0, :], rrWheel[1, :], color) 78 | plt.plot(flWheel[0, :], flWheel[1, :], color) 79 | plt.plot(rlWheel[0, :], rlWheel[1, :], color) 80 | Arrow(x, y, yaw, 0.8 * wheelbase, color) 81 | plt.axis("equal") 82 | # plt.show() 83 | 84 | 85 | if __name__ == '__main__': 86 | draw_car(0, 0, 0, 0.2) -------------------------------------------------------------------------------- /Control/fig/carmodel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/fig/carmodel.png -------------------------------------------------------------------------------- /Control/fig/frontwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/fig/frontwheel.png -------------------------------------------------------------------------------- /Control/fig/ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/fig/ps.png -------------------------------------------------------------------------------- /Control/fig/purepursuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/fig/purepursuit.jpg -------------------------------------------------------------------------------- /Control/fig/rearwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/fig/rearwheel.png -------------------------------------------------------------------------------- /Control/fig/simple car.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/fig/simple car.jpg -------------------------------------------------------------------------------- /Control/gif/FWF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/FWF.gif -------------------------------------------------------------------------------- /Control/gif/LQR_Dynamics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/LQR_Dynamics.gif -------------------------------------------------------------------------------- /Control/gif/LQR_Kinematic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/LQR_Kinematic.gif -------------------------------------------------------------------------------- /Control/gif/MPC.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/MPC.gif -------------------------------------------------------------------------------- /Control/gif/RWF1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/RWF1.gif -------------------------------------------------------------------------------- /Control/gif/RWF2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/RWF2.gif -------------------------------------------------------------------------------- /Control/gif/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/demo.gif -------------------------------------------------------------------------------- /Control/gif/purepursuit1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/purepursuit1.gif -------------------------------------------------------------------------------- /Control/gif/purepursuit2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/purepursuit2.gif -------------------------------------------------------------------------------- /Control/gif/stanley.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/Control/gif/stanley.gif -------------------------------------------------------------------------------- /Control/utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | 4 | 5 | def calc_curvature_point(s1, s2, s3): 6 | ta = math.hypot(s2[0] - s1[0], s2[1] - s1[1]) 7 | tb = math.hypot(s3[0] - s2[0], s3[1] - s2[1]) 8 | M = np.array([[1, -ta, ta ** 2], 9 | [1, 0, 0], 10 | [1, tb, tb ** 2]]) 11 | X = np.array([[s1[0]], [s2[0]], [s3[0]]]) 12 | Y = np.array([[s1[1]], [s2[1]], [s3[1]]]) 13 | A = np.linalg.solve(M, X) 14 | B = np.linalg.solve(M, Y) 15 | k = 2 * (A[2][0] * B[1][0] - A[1][0] * B[2][0]) / (A[1][0] ** 2 + B[1][0] ** 2) ** (3 / 2) 16 | 17 | return k 18 | 19 | 20 | def calc_curvature(x, y): 21 | K = [0.0] 22 | x, y = map(np.asarray, (x, y)) 23 | ta = (np.diff(x[0:-1]) ** 2 + np.diff(y[0:-1]) ** 2) ** 0.5 24 | tb = (np.diff(x[1:len(x)]) ** 2 + np.diff(y[1:len(y)]) ** 2) ** 0.5 25 | for i in range(len(ta) - 2): 26 | M = np.array([[1, -ta[i], ta[i] ** 2], 27 | [1, 0, 0], 28 | [1, tb[i], tb[i] ** 2]]) 29 | X = np.array([[x[i]], [x[i + 1]], [x[i + 2]]]) 30 | Y = np.array([[y[i]], [y[i + 1]], [y[i + 2]]]) 31 | A = np.linalg.solve(M, X) 32 | B = np.linalg.solve(M, Y) 33 | k = 2 * (A[2][0] * B[1][0] - A[1][0] * B[2][0]) / \ 34 | (A[1][0] ** 2 + B[1][0] ** 2) ** (3 / 2) 35 | K.append(k) 36 | K.append(0.0) 37 | K.append(0.0) 38 | K.append(0.0) 39 | 40 | return K 41 | 42 | 43 | def main(): 44 | R = 2 45 | theta = np.arange(0, 2 * math.pi, 0.1 * math.pi) 46 | x = R * np.cos(theta) 47 | y = R * np.sin(theta) 48 | 49 | K = calc_curvature(x, y) 50 | 51 | 52 | if __name__ == '__main__': 53 | main() 54 | -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/cubic_spline.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/cubic_spline.cpython-37.pyc -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/cubic_spline.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/cubic_spline.cpython-38.pyc -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/draw.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/draw.cpython-37.pyc -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/quartic_polynomial.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/quartic_polynomial.cpython-37.pyc -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/quintic_polynomial.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/quintic_polynomial.cpython-37.pyc -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/reeds_shepp.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/reeds_shepp.cpython-37.pyc -------------------------------------------------------------------------------- /CurvesGenerator/__pycache__/reeds_shepp.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/CurvesGenerator/__pycache__/reeds_shepp.cpython-38.pyc -------------------------------------------------------------------------------- /CurvesGenerator/cubic_spline.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | u""" 4 | Cubic Spline library on python 5 | 6 | author Atsushi Sakai 7 | 8 | usage: see test codes as below 9 | 10 | license: MIT 11 | """ 12 | import math 13 | import numpy as np 14 | import bisect 15 | 16 | 17 | class Spline: 18 | u""" 19 | Cubic Spline class 20 | """ 21 | 22 | def __init__(self, x, y): 23 | self.b, self.c, self.d, self.w = [], [], [], [] 24 | 25 | self.x = x 26 | self.y = y 27 | 28 | self.nx = len(x) # dimension of x 29 | h = np.diff(x) 30 | 31 | # calc coefficient c 32 | self.a = [iy for iy in y] 33 | 34 | # calc coefficient c 35 | A = self.__calc_A(h) 36 | B = self.__calc_B(h) 37 | self.c = np.linalg.solve(A, B) 38 | # print(self.c1) 39 | 40 | # calc spline coefficient b and d 41 | for i in range(self.nx - 1): 42 | self.d.append((self.c[i + 1] - self.c[i]) / (3.0 * h[i])) 43 | tb = (self.a[i + 1] - self.a[i]) / h[i] - h[i] * \ 44 | (self.c[i + 1] + 2.0 * self.c[i]) / 3.0 45 | self.b.append(tb) 46 | 47 | def calc(self, t): 48 | u""" 49 | Calc position 50 | 51 | if t is outside of the input x, return None 52 | 53 | """ 54 | 55 | if t < self.x[0]: 56 | return None 57 | elif t > self.x[-1]: 58 | return None 59 | 60 | i = self.__search_index(t) 61 | dx = t - self.x[i] 62 | result = self.a[i] + self.b[i] * dx + \ 63 | self.c[i] * dx ** 2.0 + self.d[i] * dx ** 3.0 64 | 65 | return result 66 | 67 | def calcd(self, t): 68 | u""" 69 | Calc first derivative 70 | 71 | if t is outside of the input x, return None 72 | """ 73 | 74 | if t < self.x[0]: 75 | return None 76 | elif t > self.x[-1]: 77 | return None 78 | 79 | i = self.__search_index(t) 80 | dx = t - self.x[i] 81 | result = self.b[i] + 2.0 * self.c[i] * dx + 3.0 * self.d[i] * dx ** 2.0 82 | return result 83 | 84 | def calcdd(self, t): 85 | u""" 86 | Calc second derivative 87 | """ 88 | 89 | if t < self.x[0]: 90 | return None 91 | elif t > self.x[-1]: 92 | return None 93 | 94 | i = self.__search_index(t) 95 | dx = t - self.x[i] 96 | result = 2.0 * self.c[i] + 6.0 * self.d[i] * dx 97 | return result 98 | 99 | def __search_index(self, x): 100 | u""" 101 | search data segment index 102 | """ 103 | return bisect.bisect(self.x, x) - 1 104 | 105 | def __calc_A(self, h): 106 | u""" 107 | calc matrix A for spline coefficient c 108 | """ 109 | A = np.zeros((self.nx, self.nx)) 110 | A[0, 0] = 1.0 111 | for i in range(self.nx - 1): 112 | if i != (self.nx - 2): 113 | A[i + 1, i + 1] = 2.0 * (h[i] + h[i + 1]) 114 | A[i + 1, i] = h[i] 115 | A[i, i + 1] = h[i] 116 | 117 | A[0, 1] = 0.0 118 | A[self.nx - 1, self.nx - 2] = 0.0 119 | A[self.nx - 1, self.nx - 1] = 1.0 120 | # print(A) 121 | return A 122 | 123 | def __calc_B(self, h): 124 | u""" 125 | calc matrix B for spline coefficient c 126 | """ 127 | B = np.zeros(self.nx) 128 | for i in range(self.nx - 2): 129 | B[i + 1] = 3.0 * (self.a[i + 2] - self.a[i + 1]) / \ 130 | h[i + 1] - 3.0 * (self.a[i + 1] - self.a[i]) / h[i] 131 | # print(B) 132 | return B 133 | 134 | 135 | class Spline2D: 136 | u""" 137 | 2D Cubic Spline class 138 | 139 | """ 140 | 141 | def __init__(self, x, y): 142 | self.s = self.__calc_s(x, y) 143 | self.sx = Spline(self.s, x) 144 | self.sy = Spline(self.s, y) 145 | 146 | def __calc_s(self, x, y): 147 | dx = np.diff(x) 148 | dy = np.diff(y) 149 | self.ds = [math.sqrt(idx ** 2 + idy ** 2) 150 | for (idx, idy) in zip(dx, dy)] 151 | s = [0] 152 | s.extend(np.cumsum(self.ds)) 153 | return s 154 | 155 | def calc_position(self, s): 156 | u""" 157 | calc position 158 | """ 159 | x = self.sx.calc(s) 160 | y = self.sy.calc(s) 161 | 162 | return x, y 163 | 164 | def calc_curvature(self, s): 165 | u""" 166 | calc curvature 167 | """ 168 | dx = self.sx.calcd(s) 169 | ddx = self.sx.calcdd(s) 170 | dy = self.sy.calcd(s) 171 | ddy = self.sy.calcdd(s) 172 | k = (ddy * dx - ddx * dy) / (dx ** 2 + dy ** 2) 173 | return k 174 | 175 | def calc_yaw(self, s): 176 | u""" 177 | calc yaw 178 | """ 179 | dx = self.sx.calcd(s) 180 | dy = self.sy.calcd(s) 181 | yaw = math.atan2(dy, dx) 182 | return yaw 183 | 184 | 185 | def calc_spline_course(x, y, ds=0.1): 186 | sp = Spline2D(x, y) 187 | s = np.arange(0, sp.s[-1], ds) 188 | 189 | rx, ry, ryaw, rk = [], [], [], [] 190 | for i_s in s: 191 | ix, iy = sp.calc_position(i_s) 192 | rx.append(ix) 193 | ry.append(iy) 194 | ryaw.append(sp.calc_yaw(i_s)) 195 | rk.append(sp.calc_curvature(i_s)) 196 | 197 | return rx, ry, ryaw, rk, s 198 | 199 | 200 | def test_spline2d(): 201 | print("Spline 2D test") 202 | import matplotlib.pyplot as plt 203 | x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0] 204 | y = [0.7, -6, 5, 6.5, 0.0, 5.0, -2.0] 205 | 206 | sp = Spline2D(x, y) 207 | s = np.arange(0, sp.s[-1], 0.1) 208 | 209 | rx, ry, ryaw, rk = [], [], [], [] 210 | for i_s in s: 211 | ix, iy = sp.calc_position(i_s) 212 | rx.append(ix) 213 | ry.append(iy) 214 | ryaw.append(sp.calc_yaw(i_s)) 215 | rk.append(sp.calc_curvature(i_s)) 216 | 217 | flg, ax = plt.subplots(1) 218 | plt.plot(x, y, "xb", label="input") 219 | plt.plot(rx, ry, "-r", label="spline") 220 | plt.grid(True) 221 | plt.axis("equal") 222 | plt.xlabel("x[m]") 223 | plt.ylabel("y[m]") 224 | plt.legend() 225 | 226 | flg, ax = plt.subplots(1) 227 | plt.plot(s, [math.degrees(iyaw) for iyaw in ryaw], "-r", label="yaw") 228 | plt.grid(True) 229 | plt.legend() 230 | plt.xlabel("line length[m]") 231 | plt.ylabel("yaw angle[deg]") 232 | 233 | flg, ax = plt.subplots(1) 234 | plt.plot(s, rk, "-r", label="curvature") 235 | plt.grid(True) 236 | plt.legend() 237 | plt.xlabel("line length[m]") 238 | plt.ylabel("curvature [1/m]") 239 | 240 | plt.show() 241 | 242 | 243 | def test_spline(): 244 | print("Spline test") 245 | import matplotlib.pyplot as plt 246 | x = [-0.5, 0.0, 0.5, 1.0, 1.5] 247 | y = [3.2, 2.7, 6, 5, 6.5] 248 | 249 | spline = Spline(x, y) 250 | rx = np.arange(-2.0, 4, 0.01) 251 | ry = [spline.calc(i) for i in rx] 252 | 253 | plt.plot(x, y, "xb") 254 | plt.plot(rx, ry, "-r") 255 | plt.grid(True) 256 | plt.axis("equal") 257 | plt.show() 258 | 259 | 260 | if __name__ == '__main__': 261 | test_spline() 262 | test_spline2d() 263 | -------------------------------------------------------------------------------- /CurvesGenerator/draw.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | PI = np.pi 4 | 5 | 6 | class Arrow: 7 | def __init__(self, x, y, theta, L, c): 8 | angle = np.deg2rad(30) 9 | d = 0.5 * L 10 | w = 2 11 | 12 | x_start = x 13 | y_start = y 14 | x_end = x + L * np.cos(theta) 15 | y_end = y + L * np.sin(theta) 16 | 17 | theta_hat_L = theta + PI - angle 18 | theta_hat_R = theta + PI + angle 19 | 20 | x_hat_start = x_end 21 | x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L) 22 | x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R) 23 | 24 | y_hat_start = y_end 25 | y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L) 26 | y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R) 27 | 28 | plt.plot([x_start, x_end], [y_start, y_end], color=c, linewidth=w) 29 | plt.plot([x_hat_start, x_hat_end_L], 30 | [y_hat_start, y_hat_end_L], color=c, linewidth=w) 31 | plt.plot([x_hat_start, x_hat_end_R], 32 | [y_hat_start, y_hat_end_R], color=c, linewidth=w) 33 | 34 | 35 | class Car: 36 | def __init__(self, x, y, yaw, w, L): 37 | theta_B = PI + yaw 38 | 39 | xB = x + L / 4 * np.cos(theta_B) 40 | yB = y + L / 4 * np.sin(theta_B) 41 | 42 | theta_BL = theta_B + PI / 2 43 | theta_BR = theta_B - PI / 2 44 | 45 | x_BL = xB + w / 2 * np.cos(theta_BL) # Bottom-Left vertex 46 | y_BL = yB + w / 2 * np.sin(theta_BL) 47 | x_BR = xB + w / 2 * np.cos(theta_BR) # Bottom-Right vertex 48 | y_BR = yB + w / 2 * np.sin(theta_BR) 49 | 50 | x_FL = x_BL + L * np.cos(yaw) # Front-Left vertex 51 | y_FL = y_BL + L * np.sin(yaw) 52 | x_FR = x_BR + L * np.cos(yaw) # Front-Right vertex 53 | y_FR = y_BR + L * np.sin(yaw) 54 | 55 | plt.plot([x_BL, x_BR, x_FR, x_FL, x_BL], 56 | [y_BL, y_BR, y_FR, y_FL, y_BL], 57 | linewidth=1, color='black') 58 | 59 | Arrow(x, y, yaw, L / 2, 'black') 60 | # plt.axis("equal") 61 | # plt.show() 62 | 63 | 64 | if __name__ == '__main__': 65 | # Arrow(-1, 2, 60) 66 | Car(0, 0, 1, 2, 60) -------------------------------------------------------------------------------- /CurvesGenerator/dubins_path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dubins Path 3 | """ 4 | 5 | import math 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from scipy.spatial.transform import Rotation as Rot 9 | 10 | from CurvesGenerator import draw 11 | 12 | 13 | # class for PATH element 14 | class PATH: 15 | def __init__(self, L, mode, x, y, yaw): 16 | self.L = L # total path length [float] 17 | self.mode = mode # type of each part of the path [string] 18 | self.x = x # final x positions [m] 19 | self.y = y # final y positions [m] 20 | self.yaw = yaw # final yaw angles [rad] 21 | 22 | 23 | # utils 24 | def pi_2_pi(theta): 25 | while theta > math.pi: 26 | theta -= 2.0 * math.pi 27 | 28 | while theta < -math.pi: 29 | theta += 2.0 * math.pi 30 | 31 | return theta 32 | 33 | 34 | def mod2pi(theta): 35 | return theta - 2.0 * math.pi * math.floor(theta / math.pi / 2.0) 36 | 37 | 38 | def LSL(alpha, beta, dist): 39 | sin_a = math.sin(alpha) 40 | sin_b = math.sin(beta) 41 | cos_a = math.cos(alpha) 42 | cos_b = math.cos(beta) 43 | cos_a_b = math.cos(alpha - beta) 44 | 45 | p_lsl = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_a - sin_b) 46 | 47 | if p_lsl < 0: 48 | return None, None, None, ["WB", "S", "WB"] 49 | else: 50 | p_lsl = math.sqrt(p_lsl) 51 | 52 | denominate = dist + sin_a - sin_b 53 | t_lsl = mod2pi(-alpha + math.atan2(cos_b - cos_a, denominate)) 54 | q_lsl = mod2pi(beta - math.atan2(cos_b - cos_a, denominate)) 55 | 56 | return t_lsl, p_lsl, q_lsl, ["WB", "S", "WB"] 57 | 58 | 59 | def RSR(alpha, beta, dist): 60 | sin_a = math.sin(alpha) 61 | sin_b = math.sin(beta) 62 | cos_a = math.cos(alpha) 63 | cos_b = math.cos(beta) 64 | cos_a_b = math.cos(alpha - beta) 65 | 66 | p_rsr = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_b - sin_a) 67 | 68 | if p_rsr < 0: 69 | return None, None, None, ["R", "S", "R"] 70 | else: 71 | p_rsr = math.sqrt(p_rsr) 72 | 73 | denominate = dist - sin_a + sin_b 74 | t_rsr = mod2pi(alpha - math.atan2(cos_a - cos_b, denominate)) 75 | q_rsr = mod2pi(-beta + math.atan2(cos_a - cos_b, denominate)) 76 | 77 | return t_rsr, p_rsr, q_rsr, ["R", "S", "R"] 78 | 79 | 80 | def LSR(alpha, beta, dist): 81 | sin_a = math.sin(alpha) 82 | sin_b = math.sin(beta) 83 | cos_a = math.cos(alpha) 84 | cos_b = math.cos(beta) 85 | cos_a_b = math.cos(alpha - beta) 86 | 87 | p_lsr = -2 + dist ** 2 + 2 * cos_a_b + 2 * dist * (sin_a + sin_b) 88 | 89 | if p_lsr < 0: 90 | return None, None, None, ["WB", "S", "R"] 91 | else: 92 | p_lsr = math.sqrt(p_lsr) 93 | 94 | rec = math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr) 95 | t_lsr = mod2pi(-alpha + rec) 96 | q_lsr = mod2pi(-mod2pi(beta) + rec) 97 | 98 | return t_lsr, p_lsr, q_lsr, ["WB", "S", "R"] 99 | 100 | 101 | def RSL(alpha, beta, dist): 102 | sin_a = math.sin(alpha) 103 | sin_b = math.sin(beta) 104 | cos_a = math.cos(alpha) 105 | cos_b = math.cos(beta) 106 | cos_a_b = math.cos(alpha - beta) 107 | 108 | p_rsl = -2 + dist ** 2 + 2 * cos_a_b - 2 * dist * (sin_a + sin_b) 109 | 110 | if p_rsl < 0: 111 | return None, None, None, ["R", "S", "WB"] 112 | else: 113 | p_rsl = math.sqrt(p_rsl) 114 | 115 | rec = math.atan2(cos_a + cos_b, dist - sin_a - sin_b) - math.atan2(2.0, p_rsl) 116 | t_rsl = mod2pi(alpha - rec) 117 | q_rsl = mod2pi(beta - rec) 118 | 119 | return t_rsl, p_rsl, q_rsl, ["R", "S", "WB"] 120 | 121 | 122 | def RLR(alpha, beta, dist): 123 | sin_a = math.sin(alpha) 124 | sin_b = math.sin(beta) 125 | cos_a = math.cos(alpha) 126 | cos_b = math.cos(beta) 127 | cos_a_b = math.cos(alpha - beta) 128 | 129 | rec = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0 130 | 131 | if abs(rec) > 1.0: 132 | return None, None, None, ["R", "WB", "R"] 133 | 134 | p_rlr = mod2pi(2 * math.pi - math.acos(rec)) 135 | t_rlr = mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b) + mod2pi(p_rlr / 2.0)) 136 | q_rlr = mod2pi(alpha - beta - t_rlr + mod2pi(p_rlr)) 137 | 138 | return t_rlr, p_rlr, q_rlr, ["R", "WB", "R"] 139 | 140 | 141 | def LRL(alpha, beta, dist): 142 | sin_a = math.sin(alpha) 143 | sin_b = math.sin(beta) 144 | cos_a = math.cos(alpha) 145 | cos_b = math.cos(beta) 146 | cos_a_b = math.cos(alpha - beta) 147 | 148 | rec = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_b - sin_a)) / 8.0 149 | 150 | if abs(rec) > 1.0: 151 | return None, None, None, ["WB", "R", "WB"] 152 | 153 | p_lrl = mod2pi(2 * math.pi - math.acos(rec)) 154 | t_lrl = mod2pi(-alpha - math.atan2(cos_a - cos_b, dist + sin_a - sin_b) + p_lrl / 2.0) 155 | q_lrl = mod2pi(mod2pi(beta) - alpha - t_lrl + mod2pi(p_lrl)) 156 | 157 | return t_lrl, p_lrl, q_lrl, ["WB", "R", "WB"] 158 | 159 | 160 | def interpolate(ind, l, m, maxc, ox, oy, oyaw, px, py, pyaw, directions): 161 | if m == "S": 162 | px[ind] = ox + l / maxc * math.cos(oyaw) 163 | py[ind] = oy + l / maxc * math.sin(oyaw) 164 | pyaw[ind] = oyaw 165 | else: 166 | ldx = math.sin(l) / maxc 167 | if m == "WB": 168 | ldy = (1.0 - math.cos(l)) / maxc 169 | elif m == "R": 170 | ldy = (1.0 - math.cos(l)) / (-maxc) 171 | 172 | gdx = math.cos(-oyaw) * ldx + math.sin(-oyaw) * ldy 173 | gdy = -math.sin(-oyaw) * ldx + math.cos(-oyaw) * ldy 174 | px[ind] = ox + gdx 175 | py[ind] = oy + gdy 176 | 177 | if m == "WB": 178 | pyaw[ind] = oyaw + l 179 | elif m == "R": 180 | pyaw[ind] = oyaw - l 181 | 182 | if l > 0.0: 183 | directions[ind] = 1 184 | else: 185 | directions[ind] = -1 186 | 187 | return px, py, pyaw, directions 188 | 189 | 190 | def generate_local_course(L, lengths, mode, maxc, step_size): 191 | point_num = int(L / step_size) + len(lengths) + 3 192 | 193 | px = [0.0 for _ in range(point_num)] 194 | py = [0.0 for _ in range(point_num)] 195 | pyaw = [0.0 for _ in range(point_num)] 196 | directions = [0 for _ in range(point_num)] 197 | ind = 1 198 | 199 | if lengths[0] > 0.0: 200 | directions[0] = 1 201 | else: 202 | directions[0] = -1 203 | 204 | if lengths[0] > 0.0: 205 | d = step_size 206 | else: 207 | d = -step_size 208 | 209 | ll = 0.0 210 | 211 | for m, l, i in zip(mode, lengths, range(len(mode))): 212 | if l > 0.0: 213 | d = step_size 214 | else: 215 | d = -step_size 216 | 217 | ox, oy, oyaw = px[ind], py[ind], pyaw[ind] 218 | 219 | ind -= 1 220 | if i >= 1 and (lengths[i - 1] * lengths[i]) > 0: 221 | pd = -d - ll 222 | else: 223 | pd = d - ll 224 | 225 | while abs(pd) <= abs(l): 226 | ind += 1 227 | px, py, pyaw, directions = \ 228 | interpolate(ind, pd, m, maxc, ox, oy, oyaw, px, py, pyaw, directions) 229 | pd += d 230 | 231 | ll = l - pd - d # calc remain length 232 | 233 | ind += 1 234 | px, py, pyaw, directions = \ 235 | interpolate(ind, l, m, maxc, ox, oy, oyaw, px, py, pyaw, directions) 236 | 237 | if len(px) <= 1: 238 | return [], [], [], [] 239 | 240 | # remove unused data 241 | while len(px) >= 1 and px[-1] == 0.0: 242 | px.pop() 243 | py.pop() 244 | pyaw.pop() 245 | directions.pop() 246 | 247 | return px, py, pyaw, directions 248 | 249 | 250 | def planning_from_origin(gx, gy, gyaw, curv, step_size): 251 | D = math.hypot(gx, gy) 252 | d = D * curv 253 | 254 | theta = mod2pi(math.atan2(gy, gx)) 255 | alpha = mod2pi(-theta) 256 | beta = mod2pi(gyaw - theta) 257 | 258 | planners = [LSL, RSR, LSR, RSL, RLR, LRL] 259 | 260 | best_cost = float("inf") 261 | bt, bp, bq, best_mode = None, None, None, None 262 | 263 | for planner in planners: 264 | t, p, q, mode = planner(alpha, beta, d) 265 | 266 | if t is None: 267 | continue 268 | 269 | cost = (abs(t) + abs(p) + abs(q)) 270 | if best_cost > cost: 271 | bt, bp, bq, best_mode = t, p, q, mode 272 | best_cost = cost 273 | lengths = [bt, bp, bq] 274 | 275 | x_list, y_list, yaw_list, directions = generate_local_course( 276 | sum(lengths), lengths, best_mode, curv, step_size) 277 | 278 | return x_list, y_list, yaw_list, best_mode, best_cost 279 | 280 | 281 | def calc_dubins_path(sx, sy, syaw, gx, gy, gyaw, curv, step_size=0.1): 282 | gx = gx - sx 283 | gy = gy - sy 284 | 285 | l_rot = Rot.from_euler('z', syaw).as_dcm()[0:2, 0:2] 286 | le_xy = np.stack([gx, gy]).T @ l_rot 287 | le_yaw = gyaw - syaw 288 | 289 | lp_x, lp_y, lp_yaw, mode, lengths = planning_from_origin( 290 | le_xy[0], le_xy[1], le_yaw, curv, step_size) 291 | 292 | rot = Rot.from_euler('z', -syaw).as_dcm()[0:2, 0:2] 293 | converted_xy = np.stack([lp_x, lp_y]).T @ rot 294 | x_list = converted_xy[:, 0] + sx 295 | y_list = converted_xy[:, 1] + sy 296 | yaw_list = [pi_2_pi(i_yaw + syaw) for i_yaw in lp_yaw] 297 | 298 | return PATH(lengths, mode, x_list, y_list, yaw_list) 299 | 300 | 301 | def main(): 302 | # choose states pairs: (x, y, yaw) 303 | # simulation-1 304 | states = [(0, 0, 0), (10, 10, -90), (20, 5, 60), (30, 10, 120), 305 | (35, -5, 30), (25, -10, -120), (15, -15, 100), (0, -10, -90)] 306 | 307 | # simulation-2 308 | # states = [(-3, 3, 120), (10, -7, 30), (10, 13, 30), (20, 5, -25), 309 | # (35, 10, 180), (32, -10, 180), (5, -12, 90)] 310 | 311 | max_c = 0.25 # max curvature 312 | path_x, path_y, yaw = [], [], [] 313 | 314 | for i in range(len(states) - 1): 315 | s_x = states[i][0] 316 | s_y = states[i][1] 317 | s_yaw = np.deg2rad(states[i][2]) 318 | g_x = states[i + 1][0] 319 | g_y = states[i + 1][1] 320 | g_yaw = np.deg2rad(states[i + 1][2]) 321 | 322 | path_i = calc_dubins_path(s_x, s_y, s_yaw, g_x, g_y, g_yaw, max_c) 323 | 324 | for x, y, iyaw in zip(path_i.x, path_i.y, path_i.yaw): 325 | path_x.append(x) 326 | path_y.append(y) 327 | yaw.append(iyaw) 328 | 329 | # animation 330 | plt.ion() 331 | plt.figure(1) 332 | 333 | for i in range(len(path_x)): 334 | plt.clf() 335 | plt.plot(path_x, path_y, linewidth=1, color='gray') 336 | 337 | for x, y, theta in states: 338 | draw.Arrow(x, y, np.deg2rad(theta), 2, 'blueviolet') 339 | 340 | draw.Car(path_x[i], path_y[i], yaw[i], 1.5, 3) 341 | 342 | plt.axis("equal") 343 | plt.title("Simulation of Dubins Path") 344 | plt.axis([-10, 42, -20, 20]) 345 | plt.draw() 346 | plt.pause(0.001) 347 | 348 | plt.pause(1) 349 | 350 | 351 | if __name__ == '__main__': 352 | main() 353 | -------------------------------------------------------------------------------- /CurvesGenerator/quartic_polynomial.py: -------------------------------------------------------------------------------- 1 | """ 2 | Quartic Polynomial 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class QuarticPolynomial: 9 | def __init__(self, x0, v0, a0, v1, a1, T): 10 | A = np.array([[3 * T ** 2, 4 * T ** 3], 11 | [6 * T, 12 * T ** 2]]) 12 | b = np.array([v1 - v0 - a0 * T, 13 | a1 - a0]) 14 | X = np.linalg.solve(A, b) 15 | 16 | self.a0 = x0 17 | self.a1 = v0 18 | self.a2 = a0 / 2.0 19 | self.a3 = X[0] 20 | self.a4 = X[1] 21 | 22 | def calc_xt(self, t): 23 | xt = self.a0 + self.a1 * t + self.a2 * t ** 2 + \ 24 | self.a3 * t ** 3 + self.a4 * t ** 4 25 | 26 | return xt 27 | 28 | def calc_dxt(self, t): 29 | xt = self.a1 + 2 * self.a2 * t + \ 30 | 3 * self.a3 * t ** 2 + 4 * self.a4 * t ** 3 31 | 32 | return xt 33 | 34 | def calc_ddxt(self, t): 35 | xt = 2 * self.a2 + 6 * self.a3 * t + 12 * self.a4 * t ** 2 36 | 37 | return xt 38 | 39 | def calc_dddxt(self, t): 40 | xt = 6 * self.a3 + 24 * self.a4 * t 41 | 42 | return xt 43 | 44 | -------------------------------------------------------------------------------- /CurvesGenerator/quintic_polynomial.py: -------------------------------------------------------------------------------- 1 | """ 2 | Quintic Polynomial 3 | """ 4 | 5 | import math 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | from CurvesGenerator import draw 10 | 11 | 12 | class QuinticPolynomial: 13 | def __init__(self, x0, v0, a0, x1, v1, a1, T): 14 | A = np.array([[T ** 3, T ** 4, T ** 5], 15 | [3 * T ** 2, 4 * T ** 3, 5 * T ** 4], 16 | [6 * T, 12 * T ** 2, 20 * T ** 3]]) 17 | b = np.array([x1 - x0 - v0 * T - a0 * T ** 2 / 2, 18 | v1 - v0 - a0 * T, 19 | a1 - a0]) 20 | X = np.linalg.solve(A, b) 21 | 22 | self.a0 = x0 23 | self.a1 = v0 24 | self.a2 = a0 / 2.0 25 | self.a3 = X[0] 26 | self.a4 = X[1] 27 | self.a5 = X[2] 28 | 29 | def calc_xt(self, t): 30 | xt = self.a0 + self.a1 * t + self.a2 * t ** 2 + \ 31 | self.a3 * t ** 3 + self.a4 * t ** 4 + self.a5 * t ** 5 32 | 33 | return xt 34 | 35 | def calc_dxt(self, t): 36 | dxt = self.a1 + 2 * self.a2 * t + \ 37 | 3 * self.a3 * t ** 2 + 4 * self.a4 * t ** 3 + 5 * self.a5 * t ** 4 38 | 39 | return dxt 40 | 41 | def calc_ddxt(self, t): 42 | ddxt = 2 * self.a2 + 6 * self.a3 * t + 12 * self.a4 * t ** 2 + 20 * self.a5 * t ** 3 43 | 44 | return ddxt 45 | 46 | def calc_dddxt(self, t): 47 | dddxt = 6 * self.a3 + 24 * self.a4 * t + 60 * self.a5 * t ** 2 48 | 49 | return dddxt 50 | 51 | 52 | class Trajectory: 53 | def __init__(self): 54 | self.t = [] 55 | self.x = [] 56 | self.y = [] 57 | self.yaw = [] 58 | self.v = [] 59 | self.a = [] 60 | self.jerk = [] 61 | 62 | 63 | def simulation(): 64 | sx, sy, syaw, sv, sa = 10.0, 10.0, np.deg2rad(10.0), 1.0, 0.1 65 | gx, gy, gyaw, gv, ga = 30.0, -10.0, np.deg2rad(180.0), 1.0, 0.1 66 | 67 | MAX_ACCEL = 1.0 # max accel [m/s2] 68 | MAX_JERK = 0.5 # max jerk [m/s3] 69 | dt = 0.1 # T tick [s] 70 | 71 | MIN_T = 5 72 | MAX_T = 100 73 | T_STEP = 5 74 | 75 | sv_x = sv * math.cos(syaw) 76 | sv_y = sv * math.sin(syaw) 77 | gv_x = gv * math.cos(gyaw) 78 | gv_y = gv * math.sin(gyaw) 79 | 80 | sa_x = sa * math.cos(syaw) 81 | sa_y = sa * math.sin(syaw) 82 | ga_x = ga * math.cos(gyaw) 83 | ga_y = ga * math.sin(gyaw) 84 | 85 | path = Trajectory() 86 | 87 | for T in np.arange(MIN_T, MAX_T, T_STEP): 88 | path = Trajectory() 89 | xqp = QuinticPolynomial(sx, sv_x, sa_x, gx, gv_x, ga_x, T) 90 | yqp = QuinticPolynomial(sy, sv_y, sa_y, gy, gv_y, ga_y, T) 91 | 92 | for t in np.arange(0.0, T + dt, dt): 93 | path.t.append(t) 94 | path.x.append(xqp.calc_xt(t)) 95 | path.y.append(yqp.calc_xt(t)) 96 | 97 | vx = xqp.calc_dxt(t) 98 | vy = yqp.calc_dxt(t) 99 | path.v.append(np.hypot(vx, vy)) 100 | path.yaw.append(math.atan2(vy, vx)) 101 | 102 | ax = xqp.calc_ddxt(t) 103 | ay = yqp.calc_ddxt(t) 104 | a = np.hypot(ax, ay) 105 | 106 | if len(path.v) >= 2 and path.v[-1] - path.v[-2] < 0.0: 107 | a *= -1 108 | path.a.append(a) 109 | 110 | jx = xqp.calc_dddxt(t) 111 | jy = yqp.calc_dddxt(t) 112 | j = np.hypot(jx, jy) 113 | 114 | if len(path.a) >= 2 and path.a[-1] - path.a[-2] < 0.0: 115 | j *= -1 116 | path.jerk.append(j) 117 | 118 | if max(np.abs(path.a)) <= MAX_ACCEL and max(np.abs(path.jerk)) <= MAX_JERK: 119 | break 120 | 121 | print("t_len: ", path.t, "s") 122 | print("max_v: ", max(path.v), "m/s") 123 | print("max_a: ", max(np.abs(path.a)), "m/s2") 124 | print("max_jerk: ", max(np.abs(path.jerk)), "m/s3") 125 | 126 | for i in range(len(path.t)): 127 | plt.cla() 128 | plt.gcf().canvas.mpl_connect('key_release_event', 129 | lambda event: [exit(0) if event.key == 'escape' else None]) 130 | plt.axis("equal") 131 | plt.plot(path.x, path.y, linewidth=2, color='gray') 132 | draw.Car(sx, sy, syaw, 1.5, 3) 133 | draw.Car(gx, gy, gyaw, 1.5, 3) 134 | draw.Car(path.x[i], path.y[i], path.yaw[i], 1.5, 3) 135 | plt.title("Quintic Polynomial Curves") 136 | plt.pause(0.001) 137 | 138 | plt.show() 139 | 140 | 141 | if __name__ == '__main__': 142 | simulation() 143 | -------------------------------------------------------------------------------- /CurvesGenerator/reeds_shepp.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import numpy as np 4 | 5 | 6 | # parameters initiation 7 | STEP_SIZE = 0.2 8 | MAX_LENGTH = 1000.0 9 | PI = math.pi 10 | 11 | 12 | # class for PATH element 13 | class PATH: 14 | def __init__(self, lengths, ctypes, L, x, y, yaw, directions): 15 | self.lengths = lengths # lengths of each part of path (+: forward, -: backward) [float] 16 | self.ctypes = ctypes # type of each part of the path [string] 17 | self.L = L # total path length [float] 18 | self.x = x # final x positions [m] 19 | self.y = y # final y positions [m] 20 | self.yaw = yaw # final yaw angles [rad] 21 | self.directions = directions # forward: 1, backward:-1 22 | 23 | 24 | def calc_optimal_path(sx, sy, syaw, gx, gy, gyaw, maxc, step_size=STEP_SIZE): 25 | paths = calc_all_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size=step_size) 26 | 27 | minL = paths[0].L 28 | mini = 0 29 | 30 | for i in range(len(paths)): 31 | if paths[i].L <= minL: 32 | minL, mini = paths[i].L, i 33 | 34 | return paths[mini] 35 | 36 | 37 | def calc_all_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size=STEP_SIZE): 38 | q0 = [sx, sy, syaw] 39 | q1 = [gx, gy, gyaw] 40 | 41 | paths = generate_path(q0, q1, maxc) 42 | 43 | for path in paths: 44 | x, y, yaw, directions = \ 45 | generate_local_course(path.L, path.lengths, 46 | path.ctypes, maxc, step_size * maxc) 47 | 48 | # convert global coordinate 49 | path.x = [math.cos(-q0[2]) * ix + math.sin(-q0[2]) * iy + q0[0] for (ix, iy) in zip(x, y)] 50 | path.y = [-math.sin(-q0[2]) * ix + math.cos(-q0[2]) * iy + q0[1] for (ix, iy) in zip(x, y)] 51 | path.yaw = [pi_2_pi(iyaw + q0[2]) for iyaw in yaw] 52 | path.directions = directions 53 | path.lengths = [l / maxc for l in path.lengths] 54 | path.L = path.L / maxc 55 | 56 | return paths 57 | 58 | 59 | def set_path(paths, lengths, ctypes): 60 | path = PATH([], [], 0.0, [], [], [], []) 61 | path.ctypes = ctypes 62 | path.lengths = lengths 63 | 64 | # check same path exist 65 | for path_e in paths: 66 | if path_e.ctypes == path.ctypes: 67 | if sum([x - y for x, y in zip(path_e.lengths, path.lengths)]) <= 0.01: 68 | return paths # not insert path 69 | 70 | path.L = sum([abs(i) for i in lengths]) 71 | 72 | if path.L >= MAX_LENGTH: 73 | return paths 74 | 75 | assert path.L >= 0.01 76 | paths.append(path) 77 | 78 | return paths 79 | 80 | 81 | def LSL(x, y, phi): 82 | u, t = R(x - math.sin(phi), y - 1.0 + math.cos(phi)) 83 | 84 | if t >= 0.0: 85 | v = M(phi - t) 86 | if v >= 0.0: 87 | return True, t, u, v 88 | 89 | return False, 0.0, 0.0, 0.0 90 | 91 | 92 | def LSR(x, y, phi): 93 | u1, t1 = R(x + math.sin(phi), y - 1.0 - math.cos(phi)) 94 | u1 = u1 ** 2 95 | 96 | if u1 >= 4.0: 97 | u = math.sqrt(u1 - 4.0) 98 | theta = math.atan2(2.0, u) 99 | t = M(t1 + theta) 100 | v = M(t - phi) 101 | 102 | if t >= 0.0 and v >= 0.0: 103 | return True, t, u, v 104 | 105 | return False, 0.0, 0.0, 0.0 106 | 107 | 108 | def LRL(x, y, phi): 109 | u1, t1 = R(x - math.sin(phi), y - 1.0 + math.cos(phi)) 110 | 111 | if u1 <= 4.0: 112 | u = -2.0 * math.asin(0.25 * u1) 113 | t = M(t1 + 0.5 * u + PI) 114 | v = M(phi - t + u) 115 | 116 | if t >= 0.0 and u <= 0.0: 117 | return True, t, u, v 118 | 119 | return False, 0.0, 0.0, 0.0 120 | 121 | 122 | def SCS(x, y, phi, paths): 123 | flag, t, u, v = SLS(x, y, phi) 124 | 125 | if flag: 126 | paths = set_path(paths, [t, u, v], ["S", "WB", "S"]) 127 | 128 | flag, t, u, v = SLS(x, -y, -phi) 129 | if flag: 130 | paths = set_path(paths, [t, u, v], ["S", "R", "S"]) 131 | 132 | return paths 133 | 134 | 135 | def SLS(x, y, phi): 136 | phi = M(phi) 137 | 138 | if y > 0.0 and 0.0 < phi < PI * 0.99: 139 | xd = -y / math.tan(phi) + x 140 | t = xd - math.tan(phi / 2.0) 141 | u = phi 142 | v = math.sqrt((x - xd) ** 2 + y ** 2) - math.tan(phi / 2.0) 143 | return True, t, u, v 144 | elif y < 0.0 and 0.0 < phi < PI * 0.99: 145 | xd = -y / math.tan(phi) + x 146 | t = xd - math.tan(phi / 2.0) 147 | u = phi 148 | v = -math.sqrt((x - xd) ** 2 + y ** 2) - math.tan(phi / 2.0) 149 | return True, t, u, v 150 | 151 | return False, 0.0, 0.0, 0.0 152 | 153 | 154 | def CSC(x, y, phi, paths): 155 | flag, t, u, v = LSL(x, y, phi) 156 | if flag: 157 | paths = set_path(paths, [t, u, v], ["WB", "S", "WB"]) 158 | 159 | flag, t, u, v = LSL(-x, y, -phi) 160 | if flag: 161 | paths = set_path(paths, [-t, -u, -v], ["WB", "S", "WB"]) 162 | 163 | flag, t, u, v = LSL(x, -y, -phi) 164 | if flag: 165 | paths = set_path(paths, [t, u, v], ["R", "S", "R"]) 166 | 167 | flag, t, u, v = LSL(-x, -y, phi) 168 | if flag: 169 | paths = set_path(paths, [-t, -u, -v], ["R", "S", "R"]) 170 | 171 | flag, t, u, v = LSR(x, y, phi) 172 | if flag: 173 | paths = set_path(paths, [t, u, v], ["WB", "S", "R"]) 174 | 175 | flag, t, u, v = LSR(-x, y, -phi) 176 | if flag: 177 | paths = set_path(paths, [-t, -u, -v], ["WB", "S", "R"]) 178 | 179 | flag, t, u, v = LSR(x, -y, -phi) 180 | if flag: 181 | paths = set_path(paths, [t, u, v], ["R", "S", "WB"]) 182 | 183 | flag, t, u, v = LSR(-x, -y, phi) 184 | if flag: 185 | paths = set_path(paths, [-t, -u, -v], ["R", "S", "WB"]) 186 | 187 | return paths 188 | 189 | 190 | def CCC(x, y, phi, paths): 191 | flag, t, u, v = LRL(x, y, phi) 192 | if flag: 193 | paths = set_path(paths, [t, u, v], ["WB", "R", "WB"]) 194 | 195 | flag, t, u, v = LRL(-x, y, -phi) 196 | if flag: 197 | paths = set_path(paths, [-t, -u, -v], ["WB", "R", "WB"]) 198 | 199 | flag, t, u, v = LRL(x, -y, -phi) 200 | if flag: 201 | paths = set_path(paths, [t, u, v], ["R", "WB", "R"]) 202 | 203 | flag, t, u, v = LRL(-x, -y, phi) 204 | if flag: 205 | paths = set_path(paths, [-t, -u, -v], ["R", "WB", "R"]) 206 | 207 | # backwards 208 | xb = x * math.cos(phi) + y * math.sin(phi) 209 | yb = x * math.sin(phi) - y * math.cos(phi) 210 | 211 | flag, t, u, v = LRL(xb, yb, phi) 212 | if flag: 213 | paths = set_path(paths, [v, u, t], ["WB", "R", "WB"]) 214 | 215 | flag, t, u, v = LRL(-xb, yb, -phi) 216 | if flag: 217 | paths = set_path(paths, [-v, -u, -t], ["WB", "R", "WB"]) 218 | 219 | flag, t, u, v = LRL(xb, -yb, -phi) 220 | if flag: 221 | paths = set_path(paths, [v, u, t], ["R", "WB", "R"]) 222 | 223 | flag, t, u, v = LRL(-xb, -yb, phi) 224 | if flag: 225 | paths = set_path(paths, [-v, -u, -t], ["R", "WB", "R"]) 226 | 227 | return paths 228 | 229 | 230 | def calc_tauOmega(u, v, xi, eta, phi): 231 | delta = M(u - v) 232 | A = math.sin(u) - math.sin(delta) 233 | B = math.cos(u) - math.cos(delta) - 1.0 234 | 235 | t1 = math.atan2(eta * A - xi * B, xi * A + eta * B) 236 | t2 = 2.0 * (math.cos(delta) - math.cos(v) - math.cos(u)) + 3.0 237 | 238 | if t2 < 0: 239 | tau = M(t1 + PI) 240 | else: 241 | tau = M(t1) 242 | 243 | omega = M(tau - u + v - phi) 244 | 245 | return tau, omega 246 | 247 | 248 | def LRLRn(x, y, phi): 249 | xi = x + math.sin(phi) 250 | eta = y - 1.0 - math.cos(phi) 251 | rho = 0.25 * (2.0 + math.sqrt(xi * xi + eta * eta)) 252 | 253 | if rho <= 1.0: 254 | u = math.acos(rho) 255 | t, v = calc_tauOmega(u, -u, xi, eta, phi) 256 | if t >= 0.0 and v <= 0.0: 257 | return True, t, u, v 258 | 259 | return False, 0.0, 0.0, 0.0 260 | 261 | 262 | def LRLRp(x, y, phi): 263 | xi = x + math.sin(phi) 264 | eta = y - 1.0 - math.cos(phi) 265 | rho = (20.0 - xi * xi - eta * eta) / 16.0 266 | 267 | if 0.0 <= rho <= 1.0: 268 | u = -math.acos(rho) 269 | if u >= -0.5 * PI: 270 | t, v = calc_tauOmega(u, u, xi, eta, phi) 271 | if t >= 0.0 and v >= 0.0: 272 | return True, t, u, v 273 | 274 | return False, 0.0, 0.0, 0.0 275 | 276 | 277 | def CCCC(x, y, phi, paths): 278 | flag, t, u, v = LRLRn(x, y, phi) 279 | if flag: 280 | paths = set_path(paths, [t, u, -u, v], ["WB", "R", "WB", "R"]) 281 | 282 | flag, t, u, v = LRLRn(-x, y, -phi) 283 | if flag: 284 | paths = set_path(paths, [-t, -u, u, -v], ["WB", "R", "WB", "R"]) 285 | 286 | flag, t, u, v = LRLRn(x, -y, -phi) 287 | if flag: 288 | paths = set_path(paths, [t, u, -u, v], ["R", "WB", "R", "WB"]) 289 | 290 | flag, t, u, v = LRLRn(-x, -y, phi) 291 | if flag: 292 | paths = set_path(paths, [-t, -u, u, -v], ["R", "WB", "R", "WB"]) 293 | 294 | flag, t, u, v = LRLRp(x, y, phi) 295 | if flag: 296 | paths = set_path(paths, [t, u, u, v], ["WB", "R", "WB", "R"]) 297 | 298 | flag, t, u, v = LRLRp(-x, y, -phi) 299 | if flag: 300 | paths = set_path(paths, [-t, -u, -u, -v], ["WB", "R", "WB", "R"]) 301 | 302 | flag, t, u, v = LRLRp(x, -y, -phi) 303 | if flag: 304 | paths = set_path(paths, [t, u, u, v], ["R", "WB", "R", "WB"]) 305 | 306 | flag, t, u, v = LRLRp(-x, -y, phi) 307 | if flag: 308 | paths = set_path(paths, [-t, -u, -u, -v], ["R", "WB", "R", "WB"]) 309 | 310 | return paths 311 | 312 | 313 | def LRSR(x, y, phi): 314 | xi = x + math.sin(phi) 315 | eta = y - 1.0 - math.cos(phi) 316 | rho, theta = R(-eta, xi) 317 | 318 | if rho >= 2.0: 319 | t = theta 320 | u = 2.0 - rho 321 | v = M(t + 0.5 * PI - phi) 322 | if t >= 0.0 and u <= 0.0 and v <= 0.0: 323 | return True, t, u, v 324 | 325 | return False, 0.0, 0.0, 0.0 326 | 327 | 328 | def LRSL(x, y, phi): 329 | xi = x - math.sin(phi) 330 | eta = y - 1.0 + math.cos(phi) 331 | rho, theta = R(xi, eta) 332 | 333 | if rho >= 2.0: 334 | r = math.sqrt(rho * rho - 4.0) 335 | u = 2.0 - r 336 | t = M(theta + math.atan2(r, -2.0)) 337 | v = M(phi - 0.5 * PI - t) 338 | if t >= 0.0 and u <= 0.0 and v <= 0.0: 339 | return True, t, u, v 340 | 341 | return False, 0.0, 0.0, 0.0 342 | 343 | 344 | def CCSC(x, y, phi, paths): 345 | flag, t, u, v = LRSL(x, y, phi) 346 | if flag: 347 | paths = set_path(paths, [t, -0.5 * PI, u, v], ["WB", "R", "S", "WB"]) 348 | 349 | flag, t, u, v = LRSL(-x, y, -phi) 350 | if flag: 351 | paths = set_path(paths, [-t, 0.5 * PI, -u, -v], ["WB", "R", "S", "WB"]) 352 | 353 | flag, t, u, v = LRSL(x, -y, -phi) 354 | if flag: 355 | paths = set_path(paths, [t, -0.5 * PI, u, v], ["R", "WB", "S", "R"]) 356 | 357 | flag, t, u, v = LRSL(-x, -y, phi) 358 | if flag: 359 | paths = set_path(paths, [-t, 0.5 * PI, -u, -v], ["R", "WB", "S", "R"]) 360 | 361 | flag, t, u, v = LRSR(x, y, phi) 362 | if flag: 363 | paths = set_path(paths, [t, -0.5 * PI, u, v], ["WB", "R", "S", "R"]) 364 | 365 | flag, t, u, v = LRSR(-x, y, -phi) 366 | if flag: 367 | paths = set_path(paths, [-t, 0.5 * PI, -u, -v], ["WB", "R", "S", "R"]) 368 | 369 | flag, t, u, v = LRSR(x, -y, -phi) 370 | if flag: 371 | paths = set_path(paths, [t, -0.5 * PI, u, v], ["R", "WB", "S", "WB"]) 372 | 373 | flag, t, u, v = LRSR(-x, -y, phi) 374 | if flag: 375 | paths = set_path(paths, [-t, 0.5 * PI, -u, -v], ["R", "WB", "S", "WB"]) 376 | 377 | # backwards 378 | xb = x * math.cos(phi) + y * math.sin(phi) 379 | yb = x * math.sin(phi) - y * math.cos(phi) 380 | 381 | flag, t, u, v = LRSL(xb, yb, phi) 382 | if flag: 383 | paths = set_path(paths, [v, u, -0.5 * PI, t], ["WB", "S", "R", "WB"]) 384 | 385 | flag, t, u, v = LRSL(-xb, yb, -phi) 386 | if flag: 387 | paths = set_path(paths, [-v, -u, 0.5 * PI, -t], ["WB", "S", "R", "WB"]) 388 | 389 | flag, t, u, v = LRSL(xb, -yb, -phi) 390 | if flag: 391 | paths = set_path(paths, [v, u, -0.5 * PI, t], ["R", "S", "WB", "R"]) 392 | 393 | flag, t, u, v = LRSL(-xb, -yb, phi) 394 | if flag: 395 | paths = set_path(paths, [-v, -u, 0.5 * PI, -t], ["R", "S", "WB", "R"]) 396 | 397 | flag, t, u, v = LRSR(xb, yb, phi) 398 | if flag: 399 | paths = set_path(paths, [v, u, -0.5 * PI, t], ["R", "S", "R", "WB"]) 400 | 401 | flag, t, u, v = LRSR(-xb, yb, -phi) 402 | if flag: 403 | paths = set_path(paths, [-v, -u, 0.5 * PI, -t], ["R", "S", "R", "WB"]) 404 | 405 | flag, t, u, v = LRSR(xb, -yb, -phi) 406 | if flag: 407 | paths = set_path(paths, [v, u, -0.5 * PI, t], ["WB", "S", "WB", "R"]) 408 | 409 | flag, t, u, v = LRSR(-xb, -yb, phi) 410 | if flag: 411 | paths = set_path(paths, [-v, -u, 0.5 * PI, -t], ["WB", "S", "WB", "R"]) 412 | 413 | return paths 414 | 415 | 416 | def LRSLR(x, y, phi): 417 | # formula 8.11 *** TYPO IN PAPER *** 418 | xi = x + math.sin(phi) 419 | eta = y - 1.0 - math.cos(phi) 420 | rho, theta = R(xi, eta) 421 | 422 | if rho >= 2.0: 423 | u = 4.0 - math.sqrt(rho * rho - 4.0) 424 | if u <= 0.0: 425 | t = M(math.atan2((4.0 - u) * xi - 2.0 * eta, -2.0 * xi + (u - 4.0) * eta)) 426 | v = M(t - phi) 427 | 428 | if t >= 0.0 and v >= 0.0: 429 | return True, t, u, v 430 | 431 | return False, 0.0, 0.0, 0.0 432 | 433 | 434 | def CCSCC(x, y, phi, paths): 435 | flag, t, u, v = LRSLR(x, y, phi) 436 | if flag: 437 | paths = set_path(paths, [t, -0.5 * PI, u, -0.5 * PI, v], ["WB", "R", "S", "WB", "R"]) 438 | 439 | flag, t, u, v = LRSLR(-x, y, -phi) 440 | if flag: 441 | paths = set_path(paths, [-t, 0.5 * PI, -u, 0.5 * PI, -v], ["WB", "R", "S", "WB", "R"]) 442 | 443 | flag, t, u, v = LRSLR(x, -y, -phi) 444 | if flag: 445 | paths = set_path(paths, [t, -0.5 * PI, u, -0.5 * PI, v], ["R", "WB", "S", "R", "WB"]) 446 | 447 | flag, t, u, v = LRSLR(-x, -y, phi) 448 | if flag: 449 | paths = set_path(paths, [-t, 0.5 * PI, -u, 0.5 * PI, -v], ["R", "WB", "S", "R", "WB"]) 450 | 451 | return paths 452 | 453 | 454 | def generate_local_course(L, lengths, mode, maxc, step_size): 455 | point_num = int(L / step_size) + len(lengths) + 3 456 | 457 | px = [0.0 for _ in range(point_num)] 458 | py = [0.0 for _ in range(point_num)] 459 | pyaw = [0.0 for _ in range(point_num)] 460 | directions = [0 for _ in range(point_num)] 461 | ind = 1 462 | 463 | if lengths[0] > 0.0: 464 | directions[0] = 1 465 | else: 466 | directions[0] = -1 467 | 468 | if lengths[0] > 0.0: 469 | d = step_size 470 | else: 471 | d = -step_size 472 | 473 | ll = 0.0 474 | 475 | for m, l, i in zip(mode, lengths, range(len(mode))): 476 | if l > 0.0: 477 | d = step_size 478 | else: 479 | d = -step_size 480 | 481 | ox, oy, oyaw = px[ind], py[ind], pyaw[ind] 482 | 483 | ind -= 1 484 | if i >= 1 and (lengths[i - 1] * lengths[i]) > 0: 485 | pd = -d - ll 486 | else: 487 | pd = d - ll 488 | 489 | while abs(pd) <= abs(l): 490 | ind += 1 491 | px, py, pyaw, directions = \ 492 | interpolate(ind, pd, m, maxc, ox, oy, oyaw, px, py, pyaw, directions) 493 | pd += d 494 | 495 | ll = l - pd - d # calc remain length 496 | 497 | ind += 1 498 | px, py, pyaw, directions = \ 499 | interpolate(ind, l, m, maxc, ox, oy, oyaw, px, py, pyaw, directions) 500 | 501 | if len(px) <= 1: 502 | return [], [], [], [] 503 | 504 | # remove unused data 505 | while len(px) >= 1 and px[-1] == 0.0: 506 | px.pop() 507 | py.pop() 508 | pyaw.pop() 509 | directions.pop() 510 | 511 | return px, py, pyaw, directions 512 | 513 | 514 | def interpolate(ind, l, m, maxc, ox, oy, oyaw, px, py, pyaw, directions): 515 | if m == "S": 516 | px[ind] = ox + l / maxc * math.cos(oyaw) 517 | py[ind] = oy + l / maxc * math.sin(oyaw) 518 | pyaw[ind] = oyaw 519 | else: 520 | ldx = math.sin(l) / maxc 521 | if m == "WB": 522 | ldy = (1.0 - math.cos(l)) / maxc 523 | elif m == "R": 524 | ldy = (1.0 - math.cos(l)) / (-maxc) 525 | 526 | gdx = math.cos(-oyaw) * ldx + math.sin(-oyaw) * ldy 527 | gdy = -math.sin(-oyaw) * ldx + math.cos(-oyaw) * ldy 528 | px[ind] = ox + gdx 529 | py[ind] = oy + gdy 530 | 531 | if m == "WB": 532 | pyaw[ind] = oyaw + l 533 | elif m == "R": 534 | pyaw[ind] = oyaw - l 535 | 536 | if l > 0.0: 537 | directions[ind] = 1 538 | else: 539 | directions[ind] = -1 540 | 541 | return px, py, pyaw, directions 542 | 543 | 544 | def generate_path(q0, q1, maxc): 545 | dx = q1[0] - q0[0] 546 | dy = q1[1] - q0[1] 547 | dth = q1[2] - q0[2] 548 | c = math.cos(q0[2]) 549 | s = math.sin(q0[2]) 550 | x = (c * dx + s * dy) * maxc 551 | y = (-s * dx + c * dy) * maxc 552 | 553 | paths = [] 554 | paths = SCS(x, y, dth, paths) 555 | paths = CSC(x, y, dth, paths) 556 | paths = CCC(x, y, dth, paths) 557 | paths = CCCC(x, y, dth, paths) 558 | paths = CCSC(x, y, dth, paths) 559 | paths = CCSCC(x, y, dth, paths) 560 | 561 | return paths 562 | 563 | 564 | # utils 565 | def pi_2_pi(theta): 566 | while theta > PI: 567 | theta -= 2.0 * PI 568 | 569 | while theta < -PI: 570 | theta += 2.0 * PI 571 | 572 | return theta 573 | 574 | 575 | def R(x, y): 576 | """ 577 | Return the polar coordinates (r, theta) of the point (x, y) 578 | """ 579 | r = math.hypot(x, y) 580 | theta = math.atan2(y, x) 581 | 582 | return r, theta 583 | 584 | 585 | def M(theta): 586 | """ 587 | Regulate theta to -pi <= theta < pi 588 | """ 589 | phi = theta % (2.0 * PI) 590 | 591 | if phi < -PI: 592 | phi += 2.0 * PI 593 | if phi > PI: 594 | phi -= 2.0 * PI 595 | 596 | return phi 597 | 598 | 599 | def get_label(path): 600 | label = "" 601 | 602 | for m, l in zip(path.ctypes, path.lengths): 603 | label = label + m 604 | if l > 0.0: 605 | label = label + "+" 606 | else: 607 | label = label + "-" 608 | 609 | return label 610 | 611 | 612 | def calc_curvature(x, y, yaw, directions): 613 | c, ds = [], [] 614 | 615 | for i in range(1, len(x) - 1): 616 | dxn = x[i] - x[i - 1] 617 | dxp = x[i + 1] - x[i] 618 | dyn = y[i] - y[i - 1] 619 | dyp = y[i + 1] - y[i] 620 | dn = math.hypot(dxn, dyn) 621 | dp = math.hypot(dxp, dyp) 622 | dx = 1.0 / (dn + dp) * (dp / dn * dxn + dn / dp * dxp) 623 | ddx = 2.0 / (dn + dp) * (dxp / dp - dxn / dn) 624 | dy = 1.0 / (dn + dp) * (dp / dn * dyn + dn / dp * dyp) 625 | ddy = 2.0 / (dn + dp) * (dyp / dp - dyn / dn) 626 | curvature = (ddy * dx - ddx * dy) / (dx ** 2 + dy ** 2) 627 | d = (dn + dp) / 2.0 628 | 629 | if np.isnan(curvature): 630 | curvature = 0.0 631 | 632 | if directions[i] <= 0.0: 633 | curvature = -curvature 634 | 635 | if len(c) == 0: 636 | ds.append(d) 637 | c.append(curvature) 638 | 639 | ds.append(d) 640 | c.append(curvature) 641 | 642 | ds.append(ds[-1]) 643 | c.append(c[-1]) 644 | 645 | return c, ds 646 | 647 | 648 | def check_path(sx, sy, syaw, gx, gy, gyaw, maxc): 649 | paths = calc_all_paths(sx, sy, syaw, gx, gy, gyaw, maxc) 650 | 651 | assert len(paths) >= 1 652 | 653 | for path in paths: 654 | assert abs(path.x[0] - sx) <= 0.01 655 | assert abs(path.y[0] - sy) <= 0.01 656 | assert abs(path.yaw[0] - syaw) <= 0.01 657 | assert abs(path.x[-1] - gx) <= 0.01 658 | assert abs(path.y[-1] - gy) <= 0.01 659 | assert abs(path.yaw[-1] - gyaw) <= 0.01 660 | 661 | # course distance check 662 | d = [math.hypot(dx, dy) 663 | for dx, dy in zip(np.diff(path.x[0:len(path.x) - 1]), 664 | np.diff(path.y[0:len(path.y) - 1]))] 665 | 666 | for i in range(len(d)): 667 | assert abs(d[i] - STEP_SIZE) <= 0.001 668 | 669 | 670 | def main(): 671 | start_x = 3.0 # [m] 672 | start_y = 10.0 # [m] 673 | start_yaw = np.deg2rad(40.0) # [rad] 674 | end_x = 0.0 # [m] 675 | end_y = 1.0 # [m] 676 | end_yaw = np.deg2rad(0.0) # [rad] 677 | max_curvature = 0.1 678 | 679 | t0 = time.time() 680 | 681 | for i in range(1000): 682 | _ = calc_optimal_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature) 683 | 684 | t1 = time.time() 685 | print(t1 - t0) 686 | 687 | 688 | if __name__ == '__main__': 689 | main() 690 | -------------------------------------------------------------------------------- /HybridAstarPlanner/__pycache__/astar.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/__pycache__/astar.cpython-37.pyc -------------------------------------------------------------------------------- /HybridAstarPlanner/__pycache__/astar.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/__pycache__/astar.cpython-38.pyc -------------------------------------------------------------------------------- /HybridAstarPlanner/__pycache__/draw.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/__pycache__/draw.cpython-37.pyc -------------------------------------------------------------------------------- /HybridAstarPlanner/__pycache__/draw.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/__pycache__/draw.cpython-38.pyc -------------------------------------------------------------------------------- /HybridAstarPlanner/__pycache__/hybrid_astar.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/__pycache__/hybrid_astar.cpython-37.pyc -------------------------------------------------------------------------------- /HybridAstarPlanner/__pycache__/hybrid_astar.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/__pycache__/hybrid_astar.cpython-38.pyc -------------------------------------------------------------------------------- /HybridAstarPlanner/astar.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | import math 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | class Node: 8 | def __init__(self, x, y, cost, pind): 9 | self.x = x # x position of node 10 | self.y = y # y position of node 11 | self.cost = cost # g cost of node 12 | self.pind = pind # parent index of node 13 | 14 | 15 | class Para: 16 | def __init__(self, minx, miny, maxx, maxy, xw, yw, reso, motion): 17 | self.minx = minx 18 | self.miny = miny 19 | self.maxx = maxx 20 | self.maxy = maxy 21 | self.xw = xw 22 | self.yw = yw 23 | self.reso = reso # resolution of grid world 24 | self.motion = motion # motion set 25 | 26 | 27 | def astar_planning(sx, sy, gx, gy, ox, oy, reso, rr): 28 | """ 29 | return path of A*. 30 | :param sx: starting node x [m] 31 | :param sy: starting node y [m] 32 | :param gx: goal node x [m] 33 | :param gy: goal node y [m] 34 | :param ox: obstacles x positions [m] 35 | :param oy: obstacles y positions [m] 36 | :param reso: xy grid resolution 37 | :param rr: robot radius 38 | :return: path 39 | """ 40 | 41 | n_start = Node(round(sx / reso), round(sy / reso), 0.0, -1) 42 | n_goal = Node(round(gx / reso), round(gy / reso), 0.0, -1) 43 | 44 | ox = [x / reso for x in ox] 45 | oy = [y / reso for y in oy] 46 | 47 | P, obsmap = calc_parameters(ox, oy, rr, reso) 48 | 49 | open_set, closed_set = dict(), dict() 50 | open_set[calc_index(n_start, P)] = n_start 51 | 52 | q_priority = [] 53 | heapq.heappush(q_priority, 54 | (fvalue(n_start, n_goal), calc_index(n_start, P))) 55 | 56 | while True: 57 | if not open_set: 58 | break 59 | 60 | _, ind = heapq.heappop(q_priority) 61 | n_curr = open_set[ind] 62 | closed_set[ind] = n_curr 63 | open_set.pop(ind) 64 | 65 | for i in range(len(P.motion)): 66 | node = Node(n_curr.x + P.motion[i][0], 67 | n_curr.y + P.motion[i][1], 68 | n_curr.cost + u_cost(P.motion[i]), ind) 69 | 70 | if not check_node(node, P, obsmap): 71 | continue 72 | 73 | n_ind = calc_index(node, P) 74 | if n_ind not in closed_set: 75 | if n_ind in open_set: 76 | if open_set[n_ind].cost > node.cost: 77 | open_set[n_ind].cost = node.cost 78 | open_set[n_ind].pind = ind 79 | else: 80 | open_set[n_ind] = node 81 | heapq.heappush(q_priority, 82 | (fvalue(node, n_goal), calc_index(node, P))) 83 | 84 | pathx, pathy = extract_path(closed_set, n_start, n_goal, P) 85 | 86 | return pathx, pathy 87 | 88 | 89 | def calc_holonomic_heuristic_with_obstacle(node, ox, oy, reso, rr): 90 | n_goal = Node(round(node.x[-1] / reso), round(node.y[-1] / reso), 0.0, -1) 91 | 92 | ox = [x / reso for x in ox] 93 | oy = [y / reso for y in oy] 94 | 95 | P, obsmap = calc_parameters(ox, oy, reso, rr) 96 | 97 | open_set, closed_set = dict(), dict() 98 | open_set[calc_index(n_goal, P)] = n_goal 99 | 100 | q_priority = [] 101 | heapq.heappush(q_priority, (n_goal.cost, calc_index(n_goal, P))) 102 | 103 | while True: 104 | if not open_set: 105 | break 106 | 107 | _, ind = heapq.heappop(q_priority) 108 | n_curr = open_set[ind] 109 | closed_set[ind] = n_curr 110 | open_set.pop(ind) 111 | 112 | for i in range(len(P.motion)): 113 | node = Node(n_curr.x + P.motion[i][0], 114 | n_curr.y + P.motion[i][1], 115 | n_curr.cost + u_cost(P.motion[i]), ind) 116 | 117 | if not check_node(node, P, obsmap): 118 | continue 119 | 120 | n_ind = calc_index(node, P) 121 | if n_ind not in closed_set: 122 | if n_ind in open_set: 123 | if open_set[n_ind].cost > node.cost: 124 | open_set[n_ind].cost = node.cost 125 | open_set[n_ind].pind = ind 126 | else: 127 | open_set[n_ind] = node 128 | heapq.heappush(q_priority, (node.cost, calc_index(node, P))) 129 | 130 | hmap = [[np.inf for _ in range(P.yw)] for _ in range(P.xw)] 131 | 132 | for n in closed_set.values(): 133 | hmap[n.x - P.minx][n.y - P.miny] = n.cost 134 | 135 | return hmap 136 | 137 | 138 | def check_node(node, P, obsmap): 139 | if node.x <= P.minx or node.x >= P.maxx or \ 140 | node.y <= P.miny or node.y >= P.maxy: 141 | return False 142 | 143 | if obsmap[node.x - P.minx][node.y - P.miny]: 144 | return False 145 | 146 | return True 147 | 148 | 149 | def u_cost(u): 150 | return math.hypot(u[0], u[1]) 151 | 152 | 153 | def fvalue(node, n_goal): 154 | return node.cost + h(node, n_goal) 155 | 156 | 157 | def h(node, n_goal): 158 | return math.hypot(node.x - n_goal.x, node.y - n_goal.y) 159 | 160 | 161 | def calc_index(node, P): 162 | return (node.y - P.miny) * P.xw + (node.x - P.minx) 163 | 164 | 165 | def calc_parameters(ox, oy, rr, reso): 166 | minx, miny = round(min(ox)), round(min(oy)) 167 | maxx, maxy = round(max(ox)), round(max(oy)) 168 | xw, yw = maxx - minx, maxy - miny 169 | 170 | motion = get_motion() 171 | P = Para(minx, miny, maxx, maxy, xw, yw, reso, motion) 172 | obsmap = calc_obsmap(ox, oy, rr, P) 173 | 174 | return P, obsmap 175 | 176 | 177 | def calc_obsmap(ox, oy, rr, P): 178 | obsmap = [[False for _ in range(P.yw)] for _ in range(P.xw)] 179 | 180 | for x in range(P.xw): 181 | xx = x + P.minx 182 | for y in range(P.yw): 183 | yy = y + P.miny 184 | for oxx, oyy in zip(ox, oy): 185 | if math.hypot(oxx - xx, oyy - yy) <= rr / P.reso: 186 | obsmap[x][y] = True 187 | break 188 | 189 | return obsmap 190 | 191 | 192 | def extract_path(closed_set, n_start, n_goal, P): 193 | pathx, pathy = [n_goal.x], [n_goal.y] 194 | n_ind = calc_index(n_goal, P) 195 | 196 | while True: 197 | node = closed_set[n_ind] 198 | pathx.append(node.x) 199 | pathy.append(node.y) 200 | n_ind = node.pind 201 | 202 | if node == n_start: 203 | break 204 | 205 | pathx = [x * P.reso for x in reversed(pathx)] 206 | pathy = [y * P.reso for y in reversed(pathy)] 207 | 208 | return pathx, pathy 209 | 210 | 211 | def get_motion(): 212 | motion = [[-1, 0], [-1, 1], [0, 1], [1, 1], 213 | [1, 0], [1, -1], [0, -1], [-1, -1]] 214 | 215 | return motion 216 | 217 | 218 | def get_env(): 219 | ox, oy = [], [] 220 | 221 | for i in range(60): 222 | ox.append(i) 223 | oy.append(0.0) 224 | for i in range(60): 225 | ox.append(60.0) 226 | oy.append(i) 227 | for i in range(61): 228 | ox.append(i) 229 | oy.append(60.0) 230 | for i in range(61): 231 | ox.append(0.0) 232 | oy.append(i) 233 | for i in range(40): 234 | ox.append(20.0) 235 | oy.append(i) 236 | for i in range(40): 237 | ox.append(40.0) 238 | oy.append(60.0 - i) 239 | 240 | return ox, oy 241 | 242 | 243 | def main(): 244 | sx = 10.0 # [m] 245 | sy = 10.0 # [m] 246 | gx = 50.0 # [m] 247 | gy = 50.0 # [m] 248 | 249 | robot_radius = 2.0 250 | grid_resolution = 1.0 251 | ox, oy = get_env() 252 | 253 | pathx, pathy = astar_planning(sx, sy, gx, gy, ox, oy, grid_resolution, robot_radius) 254 | 255 | plt.plot(ox, oy, 'sk') 256 | plt.plot(pathx, pathy, '-r') 257 | plt.plot(sx, sy, 'sg') 258 | plt.plot(gx, gy, 'sb') 259 | plt.axis("equal") 260 | plt.show() 261 | 262 | 263 | if __name__ == '__main__': 264 | main() 265 | -------------------------------------------------------------------------------- /HybridAstarPlanner/draw.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import math 4 | PI = np.pi 5 | 6 | 7 | class Arrow: 8 | def __init__(self, x, y, theta, L, c): 9 | angle = np.deg2rad(30) 10 | d = 0.3 * L 11 | w = 2 12 | 13 | x_start = x 14 | y_start = y 15 | x_end = x + L * np.cos(theta) 16 | y_end = y + L * np.sin(theta) 17 | 18 | theta_hat_L = theta + PI - angle 19 | theta_hat_R = theta + PI + angle 20 | 21 | x_hat_start = x_end 22 | x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L) 23 | x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R) 24 | 25 | y_hat_start = y_end 26 | y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L) 27 | y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R) 28 | 29 | plt.plot([x_start, x_end], [y_start, y_end], color=c, linewidth=w) 30 | plt.plot([x_hat_start, x_hat_end_L], 31 | [y_hat_start, y_hat_end_L], color=c, linewidth=w) 32 | plt.plot([x_hat_start, x_hat_end_R], 33 | [y_hat_start, y_hat_end_R], color=c, linewidth=w) 34 | 35 | 36 | class Car: 37 | def __init__(self, x, y, yaw, w, L): 38 | theta_B = PI + yaw 39 | 40 | xB = x + L / 4 * np.cos(theta_B) 41 | yB = y + L / 4 * np.sin(theta_B) 42 | 43 | theta_BL = theta_B + PI / 2 44 | theta_BR = theta_B - PI / 2 45 | 46 | x_BL = xB + w / 2 * np.cos(theta_BL) # Bottom-Left vertex 47 | y_BL = yB + w / 2 * np.sin(theta_BL) 48 | x_BR = xB + w / 2 * np.cos(theta_BR) # Bottom-Right vertex 49 | y_BR = yB + w / 2 * np.sin(theta_BR) 50 | 51 | x_FL = x_BL + L * np.cos(yaw) # Front-Left vertex 52 | y_FL = y_BL + L * np.sin(yaw) 53 | x_FR = x_BR + L * np.cos(yaw) # Front-Right vertex 54 | y_FR = y_BR + L * np.sin(yaw) 55 | 56 | plt.plot([x_BL, x_BR, x_FR, x_FL, x_BL], 57 | [y_BL, y_BR, y_FR, y_FL, y_BL], 58 | linewidth=1, color='black') 59 | 60 | Arrow(x, y, yaw, L / 2, 'black') 61 | # plt.axis("equal") 62 | # plt.show() 63 | 64 | 65 | def draw_car(x, y, yaw, steer, C, color='black'): 66 | car = np.array([[-C.RB, -C.RB, C.RF, C.RF, -C.RB], 67 | [C.W / 2, -C.W / 2, -C.W / 2, C.W / 2, C.W / 2]]) 68 | 69 | wheel = np.array([[-C.TR, -C.TR, C.TR, C.TR, -C.TR], 70 | [C.TW / 4, -C.TW / 4, -C.TW / 4, C.TW / 4, C.TW / 4]]) 71 | 72 | rlWheel = wheel.copy() 73 | rrWheel = wheel.copy() 74 | frWheel = wheel.copy() 75 | flWheel = wheel.copy() 76 | 77 | Rot1 = np.array([[math.cos(yaw), -math.sin(yaw)], 78 | [math.sin(yaw), math.cos(yaw)]]) 79 | 80 | Rot2 = np.array([[math.cos(steer), math.sin(steer)], 81 | [-math.sin(steer), math.cos(steer)]]) 82 | 83 | frWheel = np.dot(Rot2, frWheel) 84 | flWheel = np.dot(Rot2, flWheel) 85 | 86 | frWheel += np.array([[C.WB], [-C.WD / 2]]) 87 | flWheel += np.array([[C.WB], [C.WD / 2]]) 88 | rrWheel[1, :] -= C.WD / 2 89 | rlWheel[1, :] += C.WD / 2 90 | 91 | frWheel = np.dot(Rot1, frWheel) 92 | flWheel = np.dot(Rot1, flWheel) 93 | 94 | rrWheel = np.dot(Rot1, rrWheel) 95 | rlWheel = np.dot(Rot1, rlWheel) 96 | car = np.dot(Rot1, car) 97 | 98 | frWheel += np.array([[x], [y]]) 99 | flWheel += np.array([[x], [y]]) 100 | rrWheel += np.array([[x], [y]]) 101 | rlWheel += np.array([[x], [y]]) 102 | car += np.array([[x], [y]]) 103 | 104 | plt.plot(car[0, :], car[1, :], color) 105 | plt.plot(frWheel[0, :], frWheel[1, :], color) 106 | plt.plot(rrWheel[0, :], rrWheel[1, :], color) 107 | plt.plot(flWheel[0, :], flWheel[1, :], color) 108 | plt.plot(rlWheel[0, :], rlWheel[1, :], color) 109 | Arrow(x, y, yaw, C.WB * 0.8, color) 110 | 111 | 112 | if __name__ == '__main__': 113 | # Arrow(-1, 2, 60) 114 | Car(0, 0, 1, 2, 60) 115 | -------------------------------------------------------------------------------- /HybridAstarPlanner/gif/hybrid Astar-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/gif/hybrid Astar-1.gif -------------------------------------------------------------------------------- /HybridAstarPlanner/gif/hybrid Astar-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/gif/hybrid Astar-2.gif -------------------------------------------------------------------------------- /HybridAstarPlanner/gif/hybrid Astar-t1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/gif/hybrid Astar-t1.gif -------------------------------------------------------------------------------- /HybridAstarPlanner/gif/hybrid Astar-t2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/gif/hybrid Astar-t2.gif -------------------------------------------------------------------------------- /HybridAstarPlanner/gif/hybrid Astar-t3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/gif/hybrid Astar-t3.gif -------------------------------------------------------------------------------- /HybridAstarPlanner/gif/hybrid Astar-t5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/HybridAstarPlanner/gif/hybrid Astar-t5.gif -------------------------------------------------------------------------------- /HybridAstarPlanner/hybrid_astar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hybrid A* 3 | @author: Huiming Zhou 4 | """ 5 | 6 | import os 7 | import sys 8 | import math 9 | import heapq 10 | from heapdict import heapdict 11 | import time 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | import scipy.spatial.kdtree as kd 15 | 16 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 17 | "/../../MotionPlanning/") 18 | 19 | import HybridAstarPlanner.astar as astar 20 | import HybridAstarPlanner.draw as draw 21 | import CurvesGenerator.reeds_shepp as rs 22 | 23 | 24 | class C: # Parameter config 25 | PI = math.pi 26 | 27 | XY_RESO = 2.0 # [m] 28 | YAW_RESO = np.deg2rad(15.0) # [rad] 29 | MOVE_STEP = 0.4 # [m] path interporate resolution 30 | N_STEER = 20.0 # steer command number 31 | COLLISION_CHECK_STEP = 5 # skip number for collision check 32 | EXTEND_BOUND = 1 # collision check range extended 33 | 34 | GEAR_COST = 100.0 # switch back penalty cost 35 | BACKWARD_COST = 5.0 # backward penalty cost 36 | STEER_CHANGE_COST = 5.0 # steer angle change penalty cost 37 | STEER_ANGLE_COST = 1.0 # steer angle penalty cost 38 | H_COST = 15.0 # Heuristic cost penalty cost 39 | 40 | RF = 4.5 # [m] distance from rear to vehicle front end of vehicle 41 | RB = 1.0 # [m] distance from rear to vehicle back end of vehicle 42 | W = 3.0 # [m] width of vehicle 43 | WD = 0.7 * W # [m] distance between left-right wheels 44 | WB = 3.5 # [m] Wheel base 45 | TR = 0.5 # [m] Tyre radius 46 | TW = 1 # [m] Tyre width 47 | MAX_STEER = 0.6 # [rad] maximum steering angle 48 | 49 | 50 | class Node: 51 | def __init__(self, xind, yind, yawind, direction, x, y, 52 | yaw, directions, steer, cost, pind): 53 | self.xind = xind 54 | self.yind = yind 55 | self.yawind = yawind 56 | self.direction = direction 57 | self.x = x 58 | self.y = y 59 | self.yaw = yaw 60 | self.directions = directions 61 | self.steer = steer 62 | self.cost = cost 63 | self.pind = pind 64 | 65 | 66 | class Para: 67 | def __init__(self, minx, miny, minyaw, maxx, maxy, maxyaw, 68 | xw, yw, yaww, xyreso, yawreso, ox, oy, kdtree): 69 | self.minx = minx 70 | self.miny = miny 71 | self.minyaw = minyaw 72 | self.maxx = maxx 73 | self.maxy = maxy 74 | self.maxyaw = maxyaw 75 | self.xw = xw 76 | self.yw = yw 77 | self.yaww = yaww 78 | self.xyreso = xyreso 79 | self.yawreso = yawreso 80 | self.ox = ox 81 | self.oy = oy 82 | self.kdtree = kdtree 83 | 84 | 85 | class Path: 86 | def __init__(self, x, y, yaw, direction, cost): 87 | self.x = x 88 | self.y = y 89 | self.yaw = yaw 90 | self.direction = direction 91 | self.cost = cost 92 | 93 | 94 | class QueuePrior: 95 | def __init__(self): 96 | self.queue = heapdict() 97 | 98 | def empty(self): 99 | return len(self.queue) == 0 # if Q is empty 100 | 101 | def put(self, item, priority): 102 | self.queue[item] = priority # push 103 | 104 | def get(self): 105 | return self.queue.popitem()[0] # pop out element with smallest priority 106 | 107 | 108 | def hybrid_astar_planning(sx, sy, syaw, gx, gy, gyaw, ox, oy, xyreso, yawreso): 109 | sxr, syr = round(sx / xyreso), round(sy / xyreso) 110 | gxr, gyr = round(gx / xyreso), round(gy / xyreso) 111 | syawr = round(rs.pi_2_pi(syaw) / yawreso) 112 | gyawr = round(rs.pi_2_pi(gyaw) / yawreso) 113 | 114 | nstart = Node(sxr, syr, syawr, 1, [sx], [sy], [syaw], [1], 0.0, 0.0, -1) 115 | ngoal = Node(gxr, gyr, gyawr, 1, [gx], [gy], [gyaw], [1], 0.0, 0.0, -1) 116 | 117 | kdtree = kd.KDTree([[x, y] for x, y in zip(ox, oy)]) 118 | P = calc_parameters(ox, oy, xyreso, yawreso, kdtree) 119 | 120 | hmap = astar.calc_holonomic_heuristic_with_obstacle(ngoal, P.ox, P.oy, P.xyreso, 1.0) 121 | steer_set, direc_set = calc_motion_set() 122 | open_set, closed_set = {calc_index(nstart, P): nstart}, {} 123 | 124 | qp = QueuePrior() 125 | qp.put(calc_index(nstart, P), calc_hybrid_cost(nstart, hmap, P)) 126 | 127 | while True: 128 | if not open_set: 129 | return None 130 | 131 | ind = qp.get() 132 | n_curr = open_set[ind] 133 | closed_set[ind] = n_curr 134 | open_set.pop(ind) 135 | 136 | update, fpath = update_node_with_analystic_expantion(n_curr, ngoal, P) 137 | 138 | if update: 139 | fnode = fpath 140 | break 141 | 142 | for i in range(len(steer_set)): 143 | node = calc_next_node(n_curr, ind, steer_set[i], direc_set[i], P) 144 | 145 | if not node: 146 | continue 147 | 148 | node_ind = calc_index(node, P) 149 | 150 | if node_ind in closed_set: 151 | continue 152 | 153 | if node_ind not in open_set: 154 | open_set[node_ind] = node 155 | qp.put(node_ind, calc_hybrid_cost(node, hmap, P)) 156 | else: 157 | if open_set[node_ind].cost > node.cost: 158 | open_set[node_ind] = node 159 | qp.put(node_ind, calc_hybrid_cost(node, hmap, P)) 160 | 161 | return extract_path(closed_set, fnode, nstart) 162 | 163 | 164 | def extract_path(closed, ngoal, nstart): 165 | rx, ry, ryaw, direc = [], [], [], [] 166 | cost = 0.0 167 | node = ngoal 168 | 169 | while True: 170 | rx += node.x[::-1] 171 | ry += node.y[::-1] 172 | ryaw += node.yaw[::-1] 173 | direc += node.directions[::-1] 174 | cost += node.cost 175 | 176 | if is_same_grid(node, nstart): 177 | break 178 | 179 | node = closed[node.pind] 180 | 181 | rx = rx[::-1] 182 | ry = ry[::-1] 183 | ryaw = ryaw[::-1] 184 | direc = direc[::-1] 185 | 186 | direc[0] = direc[1] 187 | path = Path(rx, ry, ryaw, direc, cost) 188 | 189 | return path 190 | 191 | 192 | def calc_next_node(n_curr, c_id, u, d, P): 193 | step = C.XY_RESO * 2 194 | 195 | nlist = math.ceil(step / C.MOVE_STEP) 196 | xlist = [n_curr.x[-1] + d * C.MOVE_STEP * math.cos(n_curr.yaw[-1])] 197 | ylist = [n_curr.y[-1] + d * C.MOVE_STEP * math.sin(n_curr.yaw[-1])] 198 | yawlist = [rs.pi_2_pi(n_curr.yaw[-1] + d * C.MOVE_STEP / C.WB * math.tan(u))] 199 | 200 | for i in range(nlist - 1): 201 | xlist.append(xlist[i] + d * C.MOVE_STEP * math.cos(yawlist[i])) 202 | ylist.append(ylist[i] + d * C.MOVE_STEP * math.sin(yawlist[i])) 203 | yawlist.append(rs.pi_2_pi(yawlist[i] + d * C.MOVE_STEP / C.WB * math.tan(u))) 204 | 205 | xind = round(xlist[-1] / P.xyreso) 206 | yind = round(ylist[-1] / P.xyreso) 207 | yawind = round(yawlist[-1] / P.yawreso) 208 | 209 | if not is_index_ok(xind, yind, xlist, ylist, yawlist, P): 210 | return None 211 | 212 | cost = 0.0 213 | 214 | if d > 0: 215 | direction = 1 216 | cost += abs(step) 217 | else: 218 | direction = -1 219 | cost += abs(step) * C.BACKWARD_COST 220 | 221 | if direction != n_curr.direction: # switch back penalty 222 | cost += C.GEAR_COST 223 | 224 | cost += C.STEER_ANGLE_COST * abs(u) # steer angle penalyty 225 | cost += C.STEER_CHANGE_COST * abs(n_curr.steer - u) # steer change penalty 226 | cost = n_curr.cost + cost 227 | 228 | directions = [direction for _ in range(len(xlist))] 229 | 230 | node = Node(xind, yind, yawind, direction, xlist, ylist, 231 | yawlist, directions, u, cost, c_id) 232 | 233 | return node 234 | 235 | 236 | def is_index_ok(xind, yind, xlist, ylist, yawlist, P): 237 | if xind <= P.minx or \ 238 | xind >= P.maxx or \ 239 | yind <= P.miny or \ 240 | yind >= P.maxy: 241 | return False 242 | 243 | ind = range(0, len(xlist), C.COLLISION_CHECK_STEP) 244 | 245 | nodex = [xlist[k] for k in ind] 246 | nodey = [ylist[k] for k in ind] 247 | nodeyaw = [yawlist[k] for k in ind] 248 | 249 | if is_collision(nodex, nodey, nodeyaw, P): 250 | return False 251 | 252 | return True 253 | 254 | 255 | def update_node_with_analystic_expantion(n_curr, ngoal, P): 256 | path = analystic_expantion(n_curr, ngoal, P) # rs path: n -> ngoal 257 | 258 | if not path: 259 | return False, None 260 | 261 | fx = path.x[1:-1] 262 | fy = path.y[1:-1] 263 | fyaw = path.yaw[1:-1] 264 | fd = path.directions[1:-1] 265 | 266 | fcost = n_curr.cost + calc_rs_path_cost(path) 267 | fpind = calc_index(n_curr, P) 268 | fsteer = 0.0 269 | 270 | fpath = Node(n_curr.xind, n_curr.yind, n_curr.yawind, n_curr.direction, 271 | fx, fy, fyaw, fd, fsteer, fcost, fpind) 272 | 273 | return True, fpath 274 | 275 | 276 | def analystic_expantion(node, ngoal, P): 277 | sx, sy, syaw = node.x[-1], node.y[-1], node.yaw[-1] 278 | gx, gy, gyaw = ngoal.x[-1], ngoal.y[-1], ngoal.yaw[-1] 279 | 280 | maxc = math.tan(C.MAX_STEER) / C.WB 281 | paths = rs.calc_all_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size=C.MOVE_STEP) 282 | 283 | if not paths: 284 | return None 285 | 286 | pq = QueuePrior() 287 | for path in paths: 288 | pq.put(path, calc_rs_path_cost(path)) 289 | 290 | while not pq.empty(): 291 | path = pq.get() 292 | ind = range(0, len(path.x), C.COLLISION_CHECK_STEP) 293 | 294 | pathx = [path.x[k] for k in ind] 295 | pathy = [path.y[k] for k in ind] 296 | pathyaw = [path.yaw[k] for k in ind] 297 | 298 | if not is_collision(pathx, pathy, pathyaw, P): 299 | return path 300 | 301 | return None 302 | 303 | 304 | def is_collision(x, y, yaw, P): 305 | for ix, iy, iyaw in zip(x, y, yaw): 306 | d = 1 307 | dl = (C.RF - C.RB) / 2.0 308 | r = (C.RF + C.RB) / 2.0 + d 309 | 310 | cx = ix + dl * math.cos(iyaw) 311 | cy = iy + dl * math.sin(iyaw) 312 | 313 | ids = P.kdtree.query_ball_point([cx, cy], r) 314 | 315 | if not ids: 316 | continue 317 | 318 | for i in ids: 319 | xo = P.ox[i] - cx 320 | yo = P.oy[i] - cy 321 | dx = xo * math.cos(iyaw) + yo * math.sin(iyaw) 322 | dy = -xo * math.sin(iyaw) + yo * math.cos(iyaw) 323 | 324 | if abs(dx) < r and abs(dy) < C.W / 2 + d: 325 | return True 326 | 327 | return False 328 | 329 | 330 | def calc_rs_path_cost(rspath): 331 | cost = 0.0 332 | 333 | for lr in rspath.lengths: 334 | if lr >= 0: 335 | cost += 1 336 | else: 337 | cost += abs(lr) * C.BACKWARD_COST 338 | 339 | for i in range(len(rspath.lengths) - 1): 340 | if rspath.lengths[i] * rspath.lengths[i + 1] < 0.0: 341 | cost += C.GEAR_COST 342 | 343 | for ctype in rspath.ctypes: 344 | if ctype != "S": 345 | cost += C.STEER_ANGLE_COST * abs(C.MAX_STEER) 346 | 347 | nctypes = len(rspath.ctypes) 348 | ulist = [0.0 for _ in range(nctypes)] 349 | 350 | for i in range(nctypes): 351 | if rspath.ctypes[i] == "R": 352 | ulist[i] = -C.MAX_STEER 353 | elif rspath.ctypes[i] == "WB": 354 | ulist[i] = C.MAX_STEER 355 | 356 | for i in range(nctypes - 1): 357 | cost += C.STEER_CHANGE_COST * abs(ulist[i + 1] - ulist[i]) 358 | 359 | return cost 360 | 361 | 362 | def calc_hybrid_cost(node, hmap, P): 363 | cost = node.cost + \ 364 | C.H_COST * hmap[node.xind - P.minx][node.yind - P.miny] 365 | 366 | return cost 367 | 368 | 369 | def calc_motion_set(): 370 | s = np.arange(C.MAX_STEER / C.N_STEER, 371 | C.MAX_STEER, C.MAX_STEER / C.N_STEER) 372 | 373 | steer = list(s) + [0.0] + list(-s) 374 | direc = [1.0 for _ in range(len(steer))] + [-1.0 for _ in range(len(steer))] 375 | steer = steer + steer 376 | 377 | return steer, direc 378 | 379 | 380 | def is_same_grid(node1, node2): 381 | if node1.xind != node2.xind or \ 382 | node1.yind != node2.yind or \ 383 | node1.yawind != node2.yawind: 384 | return False 385 | 386 | return True 387 | 388 | 389 | def calc_index(node, P): 390 | ind = (node.yawind - P.minyaw) * P.xw * P.yw + \ 391 | (node.yind - P.miny) * P.xw + \ 392 | (node.xind - P.minx) 393 | 394 | return ind 395 | 396 | 397 | def calc_parameters(ox, oy, xyreso, yawreso, kdtree): 398 | minx = round(min(ox) / xyreso) 399 | miny = round(min(oy) / xyreso) 400 | maxx = round(max(ox) / xyreso) 401 | maxy = round(max(oy) / xyreso) 402 | 403 | xw, yw = maxx - minx, maxy - miny 404 | 405 | minyaw = round(-C.PI / yawreso) - 1 406 | maxyaw = round(C.PI / yawreso) 407 | yaww = maxyaw - minyaw 408 | 409 | return Para(minx, miny, minyaw, maxx, maxy, maxyaw, 410 | xw, yw, yaww, xyreso, yawreso, ox, oy, kdtree) 411 | 412 | 413 | def draw_car(x, y, yaw, steer, color='black'): 414 | car = np.array([[-C.RB, -C.RB, C.RF, C.RF, -C.RB], 415 | [C.W / 2, -C.W / 2, -C.W / 2, C.W / 2, C.W / 2]]) 416 | 417 | wheel = np.array([[-C.TR, -C.TR, C.TR, C.TR, -C.TR], 418 | [C.TW / 4, -C.TW / 4, -C.TW / 4, C.TW / 4, C.TW / 4]]) 419 | 420 | rlWheel = wheel.copy() 421 | rrWheel = wheel.copy() 422 | frWheel = wheel.copy() 423 | flWheel = wheel.copy() 424 | 425 | Rot1 = np.array([[math.cos(yaw), -math.sin(yaw)], 426 | [math.sin(yaw), math.cos(yaw)]]) 427 | 428 | Rot2 = np.array([[math.cos(steer), math.sin(steer)], 429 | [-math.sin(steer), math.cos(steer)]]) 430 | 431 | frWheel = np.dot(Rot2, frWheel) 432 | flWheel = np.dot(Rot2, flWheel) 433 | 434 | frWheel += np.array([[C.WB], [-C.WD / 2]]) 435 | flWheel += np.array([[C.WB], [C.WD / 2]]) 436 | rrWheel[1, :] -= C.WD / 2 437 | rlWheel[1, :] += C.WD / 2 438 | 439 | frWheel = np.dot(Rot1, frWheel) 440 | flWheel = np.dot(Rot1, flWheel) 441 | 442 | rrWheel = np.dot(Rot1, rrWheel) 443 | rlWheel = np.dot(Rot1, rlWheel) 444 | car = np.dot(Rot1, car) 445 | 446 | frWheel += np.array([[x], [y]]) 447 | flWheel += np.array([[x], [y]]) 448 | rrWheel += np.array([[x], [y]]) 449 | rlWheel += np.array([[x], [y]]) 450 | car += np.array([[x], [y]]) 451 | 452 | plt.plot(car[0, :], car[1, :], color) 453 | plt.plot(frWheel[0, :], frWheel[1, :], color) 454 | plt.plot(rrWheel[0, :], rrWheel[1, :], color) 455 | plt.plot(flWheel[0, :], flWheel[1, :], color) 456 | plt.plot(rlWheel[0, :], rlWheel[1, :], color) 457 | draw.Arrow(x, y, yaw, C.WB * 0.8, color) 458 | 459 | 460 | def design_obstacles(x, y): 461 | ox, oy = [], [] 462 | 463 | for i in range(x): 464 | ox.append(i) 465 | oy.append(0) 466 | for i in range(x): 467 | ox.append(i) 468 | oy.append(y - 1) 469 | for i in range(y): 470 | ox.append(0) 471 | oy.append(i) 472 | for i in range(y): 473 | ox.append(x - 1) 474 | oy.append(i) 475 | for i in range(10, 21): 476 | ox.append(i) 477 | oy.append(15) 478 | for i in range(15): 479 | ox.append(20) 480 | oy.append(i) 481 | for i in range(15, 30): 482 | ox.append(30) 483 | oy.append(i) 484 | for i in range(16): 485 | ox.append(40) 486 | oy.append(i) 487 | 488 | return ox, oy 489 | 490 | 491 | def main(): 492 | print("start!") 493 | x, y = 51, 31 494 | sx, sy, syaw0 = 10.0, 7.0, np.deg2rad(120.0) 495 | gx, gy, gyaw0 = 45.0, 20.0, np.deg2rad(90.0) 496 | 497 | ox, oy = design_obstacles(x, y) 498 | 499 | t0 = time.time() 500 | path = hybrid_astar_planning(sx, sy, syaw0, gx, gy, gyaw0, 501 | ox, oy, C.XY_RESO, C.YAW_RESO) 502 | t1 = time.time() 503 | print("running T: ", t1 - t0) 504 | 505 | if not path: 506 | print("Searching failed!") 507 | return 508 | 509 | x = path.x 510 | y = path.y 511 | yaw = path.yaw 512 | direction = path.direction 513 | 514 | for k in range(len(x)): 515 | plt.cla() 516 | plt.plot(ox, oy, "sk") 517 | plt.plot(x, y, linewidth=1.5, color='r') 518 | 519 | if k < len(x) - 2: 520 | dy = (yaw[k + 1] - yaw[k]) / C.MOVE_STEP 521 | steer = rs.pi_2_pi(math.atan(-C.WB * dy / direction[k])) 522 | else: 523 | steer = 0.0 524 | 525 | draw_car(gx, gy, gyaw0, 0.0, 'dimgray') 526 | draw_car(x[k], y[k], yaw[k], steer) 527 | plt.title("Hybrid A*") 528 | plt.axis("equal") 529 | plt.pause(0.0001) 530 | 531 | plt.show() 532 | print("Done!") 533 | 534 | 535 | if __name__ == '__main__': 536 | main() 537 | -------------------------------------------------------------------------------- /LatticePlanner/__pycache__/draw.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/LatticePlanner/__pycache__/draw.cpython-37.pyc -------------------------------------------------------------------------------- /LatticePlanner/__pycache__/env.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/LatticePlanner/__pycache__/env.cpython-37.pyc -------------------------------------------------------------------------------- /LatticePlanner/draw.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import math 4 | 5 | PI = np.pi 6 | 7 | 8 | class Arrow: 9 | def __init__(self, x, y, theta, L, c): 10 | angle = np.deg2rad(30) 11 | d = 0.3 * L 12 | w = 2 13 | 14 | x_start = x 15 | y_start = y 16 | x_end = x + L * np.cos(theta) 17 | y_end = y + L * np.sin(theta) 18 | 19 | theta_hat_L = theta + PI - angle 20 | theta_hat_R = theta + PI + angle 21 | 22 | x_hat_start = x_end 23 | x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L) 24 | x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R) 25 | 26 | y_hat_start = y_end 27 | y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L) 28 | y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R) 29 | 30 | plt.plot([x_start, x_end], [y_start, y_end], color=c, linewidth=w) 31 | plt.plot([x_hat_start, x_hat_end_L], 32 | [y_hat_start, y_hat_end_L], color=c, linewidth=w) 33 | plt.plot([x_hat_start, x_hat_end_R], 34 | [y_hat_start, y_hat_end_R], color=c, linewidth=w) 35 | 36 | 37 | def draw_car(x, y, yaw, steer, C, color='black'): 38 | car = np.array([[-C.RB, -C.RB, C.RF, C.RF, -C.RB], 39 | [C.W / 2, -C.W / 2, -C.W / 2, C.W / 2, C.W / 2]]) 40 | 41 | wheel = np.array([[-C.TR, -C.TR, C.TR, C.TR, -C.TR], 42 | [C.TW / 4, -C.TW / 4, -C.TW / 4, C.TW / 4, C.TW / 4]]) 43 | 44 | rlWheel = wheel.copy() 45 | rrWheel = wheel.copy() 46 | frWheel = wheel.copy() 47 | flWheel = wheel.copy() 48 | 49 | Rot1 = np.array([[math.cos(yaw), -math.sin(yaw)], 50 | [math.sin(yaw), math.cos(yaw)]]) 51 | 52 | Rot2 = np.array([[math.cos(steer), math.sin(steer)], 53 | [-math.sin(steer), math.cos(steer)]]) 54 | 55 | frWheel = np.dot(Rot2, frWheel) 56 | flWheel = np.dot(Rot2, flWheel) 57 | 58 | frWheel += np.array([[C.WB], [-C.WD / 2]]) 59 | flWheel += np.array([[C.WB], [C.WD / 2]]) 60 | rrWheel[1, :] -= C.WD / 2 61 | rlWheel[1, :] += C.WD / 2 62 | 63 | frWheel = np.dot(Rot1, frWheel) 64 | flWheel = np.dot(Rot1, flWheel) 65 | 66 | rrWheel = np.dot(Rot1, rrWheel) 67 | rlWheel = np.dot(Rot1, rlWheel) 68 | car = np.dot(Rot1, car) 69 | 70 | frWheel += np.array([[x], [y]]) 71 | flWheel += np.array([[x], [y]]) 72 | rrWheel += np.array([[x], [y]]) 73 | rlWheel += np.array([[x], [y]]) 74 | car += np.array([[x], [y]]) 75 | 76 | plt.plot(car[0, :], car[1, :], color) 77 | plt.plot(frWheel[0, :], frWheel[1, :], color) 78 | plt.plot(rrWheel[0, :], rrWheel[1, :], color) 79 | plt.plot(flWheel[0, :], flWheel[1, :], color) 80 | plt.plot(rlWheel[0, :], rlWheel[1, :], color) 81 | Arrow(x, y, yaw, C.WB * 0.8, color) -------------------------------------------------------------------------------- /LatticePlanner/env.py: -------------------------------------------------------------------------------- 1 | """ 2 | Environment for Lattice Planner Simulation 3 | """ 4 | 5 | import math 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | 10 | class ENVCrusing: 11 | def __init__(self): 12 | self.max_c = 0.15 13 | self.road_width = 8.0 14 | self.ref_line = self.design_reference_line() 15 | self.bound_in = self.design_boundary_in() 16 | self.bound_out = self.design_boundary_out() 17 | 18 | @staticmethod 19 | def design_reference_line(): 20 | rx, ry, ryaw, rc = [], [], [], [] 21 | step_curve = 0.1 * math.pi 22 | step_line = 4 23 | 24 | cx, cy, cr = 30, 30, 20 25 | theta = np.arange(math.pi, math.pi * 1.5, step_curve) 26 | for itheta in theta: 27 | rx.append(cx + cr * math.cos(itheta)) 28 | ry.append(cy + cr * math.sin(itheta)) 29 | 30 | for ix in np.arange(30, 80, step_line): 31 | rx.append(ix) 32 | ry.append(10) 33 | 34 | cx, cy, cr = 80, 25, 15 35 | theta = np.arange(-math.pi / 2.0, math.pi / 2.0, step_curve) 36 | for itheta in theta: 37 | rx.append(cx + cr * math.cos(itheta)) 38 | ry.append(cy + cr * math.sin(itheta)) 39 | 40 | for ix in np.arange(80, 60, -step_line): 41 | rx.append(ix) 42 | ry.append(40) 43 | 44 | cx, cy, cr = 60, 60, 20 45 | theta = np.arange(-math.pi / 2.0, -math.pi, -step_curve) 46 | for itheta in theta: 47 | rx.append(cx + cr * math.cos(itheta)) 48 | ry.append(cy + cr * math.sin(itheta)) 49 | 50 | cx, cy, cr = 25, 60, 15 51 | theta = np.arange(0.0, math.pi, step_curve) 52 | for itheta in theta: 53 | rx.append(cx + cr * math.cos(itheta)) 54 | ry.append(cy + cr * math.sin(itheta)) 55 | 56 | for iy in np.arange(60, 30, -step_line): 57 | rx.append(10) 58 | ry.append(iy) 59 | 60 | return rx, ry 61 | 62 | def design_boundary_in(self): 63 | bx, by = [], [] 64 | step_curve = 0.1 65 | step_line = 2 66 | road_width = self.road_width 67 | 68 | cx, cy, cr = 30, 30, 20 - road_width 69 | theta = np.arange(math.pi, math.pi * 1.5, step_curve) 70 | for itheta in theta: 71 | bx.append(cx + cr * math.cos(itheta)) 72 | by.append(cy + cr * math.sin(itheta)) 73 | 74 | for ix in np.arange(30, 80, step_line): 75 | bx.append(ix) 76 | by.append(10 + road_width) 77 | 78 | cx, cy, cr = 80, 25, 15 - road_width 79 | theta = np.arange(-math.pi / 2.0, math.pi / 2.0, step_curve) 80 | for itheta in theta: 81 | bx.append(cx + cr * math.cos(itheta)) 82 | by.append(cy + cr * math.sin(itheta)) 83 | 84 | for ix in np.arange(80, 60, -step_line): 85 | bx.append(ix) 86 | by.append(40 - road_width) 87 | 88 | cx, cy, cr = 60, 60, 20 + road_width 89 | theta = np.arange(-math.pi / 2.0, -math.pi, -step_curve) 90 | for itheta in theta: 91 | bx.append(cx + cr * math.cos(itheta)) 92 | by.append(cy + cr * math.sin(itheta)) 93 | 94 | cx, cy, cr = 25, 60, 15 - road_width 95 | theta = np.arange(0.0, math.pi, step_curve) 96 | for itheta in theta: 97 | bx.append(cx + cr * math.cos(itheta)) 98 | by.append(cy + cr * math.sin(itheta)) 99 | 100 | for iy in np.arange(60, 30, -step_line): 101 | bx.append(10 + road_width) 102 | by.append(iy) 103 | 104 | bx.append(bx[0]) 105 | by.append(by[0]) 106 | 107 | return bx, by 108 | 109 | def design_boundary_out(self): 110 | bx, by = [], [] 111 | step_curve = 0.1 112 | step_line = 2 113 | road_width = self.road_width 114 | 115 | cx, cy, cr = 30, 30, 20 + road_width 116 | theta = np.arange(math.pi, math.pi * 1.5, step_curve) 117 | for itheta in theta: 118 | bx.append(cx + cr * math.cos(itheta)) 119 | by.append(cy + cr * math.sin(itheta)) 120 | 121 | for ix in np.arange(30, 80, step_line): 122 | bx.append(ix) 123 | by.append(10 - road_width) 124 | 125 | cx, cy, cr = 80, 25, 15 + road_width 126 | theta = np.arange(-math.pi / 2.0, math.pi / 2.0, step_curve) 127 | for itheta in theta: 128 | bx.append(cx + cr * math.cos(itheta)) 129 | by.append(cy + cr * math.sin(itheta)) 130 | 131 | for ix in np.arange(80, 60, -step_line): 132 | bx.append(ix) 133 | by.append(40 + road_width) 134 | 135 | cx, cy, cr = 60, 60, 20 - road_width 136 | theta = np.arange(-math.pi / 2.0, -math.pi, -step_curve) 137 | for itheta in theta: 138 | bx.append(cx + cr * math.cos(itheta)) 139 | by.append(cy + cr * math.sin(itheta)) 140 | 141 | cx, cy, cr = 25, 60, 15 + road_width 142 | theta = np.arange(0.0, math.pi, step_curve) 143 | for itheta in theta: 144 | bx.append(cx + cr * math.cos(itheta)) 145 | by.append(cy + cr * math.sin(itheta)) 146 | 147 | for iy in np.arange(60, 30, -step_line): 148 | bx.append(10 - road_width) 149 | by.append(iy) 150 | 151 | bx.append(bx[0]) 152 | by.append(by[0]) 153 | 154 | return bx, by 155 | 156 | 157 | class ENVStopping: 158 | def __init__(self): 159 | self.road_width = 6.0 160 | self.ref_line = self.design_reference_line() 161 | self.bound_up = self.design_bound_up() 162 | self.bound_down = self.design_bound_down() 163 | 164 | @staticmethod 165 | def design_reference_line(): 166 | rx, ry = [], [] 167 | 168 | for i in np.arange(0.0, 60.0, 1.0): 169 | rx.append(i) 170 | ry.append(0.0) 171 | 172 | return rx, ry 173 | 174 | def design_bound_up(self): 175 | bx_up, by_up = [], [] 176 | 177 | for i in np.arange(0.0, 60.0, 0.1): 178 | bx_up.append(i) 179 | by_up.append(self.road_width) 180 | 181 | return bx_up, by_up 182 | 183 | def design_bound_down(self): 184 | bx_down, by_down = [], [] 185 | 186 | for i in np.arange(0.0, 60.0, 0.1): 187 | bx_down.append(i) 188 | by_down.append(-self.road_width) 189 | 190 | return bx_down, by_down 191 | 192 | 193 | def main(): 194 | env = ENVCrusing() 195 | rx, ry = env.design_reference_line() 196 | bx1, by1 = env.design_boundary_in() 197 | bx2, by2 = env.design_boundary_out() 198 | 199 | plt.plot(rx, ry, marker='.') 200 | plt.plot(bx1, by1, linewidth=1.5, color='k') 201 | plt.plot(bx2, by2, linewidth=1.5, color='k') 202 | plt.axis("equal") 203 | plt.show() 204 | 205 | 206 | if __name__ == '__main__': 207 | main() 208 | -------------------------------------------------------------------------------- /LatticePlanner/gif/Crusing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/LatticePlanner/gif/Crusing.gif -------------------------------------------------------------------------------- /LatticePlanner/gif/StoppingMode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhm-real/MotionPlanning/7a6b43d9c1eecf97ae6bf1aa23bc4d5aaf0e1817/LatticePlanner/gif/StoppingMode.gif -------------------------------------------------------------------------------- /LatticePlanner/lattice_planner.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import math 4 | import copy 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + 9 | "/../../MotionPlanning/") 10 | 11 | from CurvesGenerator import cubic_spline, quintic_polynomial, quartic_polynomial 12 | import LatticePlanner.env as env 13 | import LatticePlanner.draw as draw 14 | 15 | 16 | class C: 17 | # Parameter 18 | MAX_SPEED = 50.0 / 3.6 19 | MAX_ACCEL = 8.0 20 | MAX_CURVATURE = 2.5 21 | 22 | ROAD_WIDTH = 8.0 23 | ROAD_SAMPLE_STEP = 1.0 24 | 25 | T_STEP = 0.15 26 | MAX_T = 5.0 27 | MIN_T = 4.0 28 | 29 | TARGET_SPEED = 30.0 / 3.6 30 | SPEED_SAMPLE_STEP = 5.0 / 3.6 31 | 32 | # cost weights for Cruising 33 | K_JERK = 0.1 34 | K_TIME = 0.1 35 | K_V_DIFF = 1.0 36 | K_OFFSET = 1.5 37 | K_COLLISION = 500 38 | 39 | # cost weights for Stopping 40 | # K_JERK = 0.1 41 | # K_TIME = 0.1 42 | # K_V_DIFF = 200 43 | # K_OFFSET = 1.0 44 | # K_COLLISION = 500 45 | 46 | # parameters for vehicle 47 | K_SIZE = 0.9 48 | RF = 4.5 * K_SIZE # [m] distance from rear to vehicle front end of vehicle 49 | RB = 1.0 * K_SIZE # [m] distance from rear to vehicle back end of vehicle 50 | W = 3.0 * K_SIZE # [m] width of vehicle 51 | WD = 0.7 * W # [m] distance between left-right wheels 52 | WB = 3.5 * K_SIZE # [m] Wheel base 53 | TR = 0.5 * K_SIZE # [m] Tyre radius 54 | TW = 1 * K_SIZE # [m] Tyre width 55 | MAX_STEER = 0.6 # [rad] maximum steering angle 56 | 57 | 58 | class Path: 59 | def __init__(self): 60 | self.t = [] 61 | 62 | self.l = [] 63 | self.l_v = [] 64 | self.l_a = [] 65 | self.l_jerk = [] 66 | 67 | self.s = [] 68 | self.s_v = [] 69 | self.s_a = [] 70 | self.s_jerk = [] 71 | 72 | self.x = [] 73 | self.y = [] 74 | self.yaw = [] 75 | self.ds = [] 76 | self.curv = [] 77 | 78 | self.cost = 0.0 79 | 80 | 81 | def sampling_paths_for_Cruising(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path): 82 | PATHS = dict() 83 | 84 | for s1_v in np.arange(C.TARGET_SPEED * 0.6, C.TARGET_SPEED * 1.4, C.TARGET_SPEED * 0.2): 85 | 86 | for t1 in np.arange(4.5, 5.5, 0.2): 87 | path_pre = Path() 88 | path_lon = quartic_polynomial.QuarticPolynomial(s0, s0_v, s0_a, s1_v, 0.0, t1) 89 | 90 | path_pre.t = list(np.arange(0.0, t1, C.T_STEP)) 91 | path_pre.s = [path_lon.calc_xt(t) for t in path_pre.t] 92 | path_pre.s_v = [path_lon.calc_dxt(t) for t in path_pre.t] 93 | path_pre.s_a = [path_lon.calc_ddxt(t) for t in path_pre.t] 94 | path_pre.s_jerk = [path_lon.calc_dddxt(t) for t in path_pre.t] 95 | 96 | for l1 in np.arange(-C.ROAD_WIDTH, C.ROAD_WIDTH, C.ROAD_SAMPLE_STEP): 97 | path = copy.deepcopy(path_pre) 98 | path_lat = quintic_polynomial.QuinticPolynomial(l0, l0_v, l0_a, l1, 0.0, 0.0, t1) 99 | 100 | path.l = [path_lat.calc_xt(t) for t in path_pre.t] 101 | path.l_v = [path_lat.calc_dxt(t) for t in path_pre.t] 102 | path.l_a = [path_lat.calc_ddxt(t) for t in path_pre.t] 103 | path.l_jerk = [path_lat.calc_dddxt(t) for t in path_pre.t] 104 | 105 | path.x, path.y = SL_2_XY(path.s, path.l, ref_path) 106 | path.yaw, path.curv, path.ds = calc_yaw_curv(path.x, path.y) 107 | 108 | l_jerk_sum = sum(np.abs(path.l_jerk)) 109 | s_jerk_sum = sum(np.abs(path.s_jerk)) 110 | v_diff = abs(C.TARGET_SPEED - path.s_v[-1]) 111 | 112 | path.cost = C.K_JERK * (l_jerk_sum + s_jerk_sum) + \ 113 | C.K_V_DIFF * v_diff + \ 114 | C.K_TIME * t1 * 2 + \ 115 | C.K_OFFSET * abs(path.l[-1]) + \ 116 | C.K_COLLISION * is_path_collision(path) 117 | 118 | PATHS[path] = path.cost 119 | 120 | return PATHS 121 | 122 | 123 | def sampling_paths_for_Stopping(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path): 124 | PATHS = dict() 125 | 126 | for s1_v in [-1.0, 0.0, 1.0, 2.0]: 127 | 128 | for t1 in np.arange(1.0, 16.0, 1.0): 129 | path_pre = Path() 130 | path_lon = quintic_polynomial.QuinticPolynomial(s0, s0_v, s0_a, 55.0, s1_v, 0.0, t1) 131 | 132 | path_pre.t = list(np.arange(0.0, t1, C.T_STEP)) 133 | path_pre.s = [path_lon.calc_xt(t) for t in path_pre.t] 134 | path_pre.s_v = [path_lon.calc_dxt(t) for t in path_pre.t] 135 | path_pre.s_a = [path_lon.calc_ddxt(t) for t in path_pre.t] 136 | path_pre.s_jerk = [path_lon.calc_dddxt(t) for t in path_pre.t] 137 | 138 | for l1 in [0.0]: 139 | path = copy.deepcopy(path_pre) 140 | path_lat = quintic_polynomial.QuinticPolynomial(l0, l0_v, l0_a, l1, 0.0, 0.0, t1) 141 | 142 | path.l = [path_lat.calc_xt(t) for t in path_pre.t] 143 | path.l_v = [path_lat.calc_dxt(t) for t in path_pre.t] 144 | path.l_a = [path_lat.calc_ddxt(t) for t in path_pre.t] 145 | path.l_jerk = [path_lat.calc_dddxt(t) for t in path_pre.t] 146 | 147 | path.x, path.y = SL_2_XY(path.s, path.l, ref_path) 148 | path.yaw, path.curv, path.ds = calc_yaw_curv(path.x, path.y) 149 | 150 | if path.yaw is None: 151 | continue 152 | 153 | l_jerk_sum = sum(np.abs(path.l_jerk)) 154 | s_jerk_sum = sum(np.abs(path.s_jerk)) 155 | v_diff = (path.s_v[-1]) ** 2 156 | 157 | path.cost = C.K_JERK * (l_jerk_sum + s_jerk_sum) + \ 158 | C.K_V_DIFF * v_diff + \ 159 | C.K_TIME * t1 * 2 + \ 160 | C.K_OFFSET * abs(path.l[-1]) + \ 161 | 50.0 * sum(np.abs(path.s_v)) 162 | 163 | PATHS[path] = path.cost 164 | 165 | return PATHS 166 | 167 | 168 | def SL_2_XY(s_set, l_set, ref_path): 169 | pathx, pathy = [], [] 170 | 171 | for i in range(len(s_set)): 172 | x_ref, y_ref = ref_path.calc_position(s_set[i]) 173 | 174 | if x_ref is None: 175 | break 176 | 177 | yaw = ref_path.calc_yaw(s_set[i]) 178 | x = x_ref + l_set[i] * math.cos(yaw + math.pi / 2.0) 179 | y = y_ref + l_set[i] * math.sin(yaw + math.pi / 2.0) 180 | 181 | pathx.append(x) 182 | pathy.append(y) 183 | 184 | return pathx, pathy 185 | 186 | 187 | def calc_yaw_curv(x, y): 188 | yaw, curv, ds = [], [], [] 189 | 190 | for i in range(len(x) - 1): 191 | dx = x[i + 1] - x[i] 192 | dy = y[i + 1] - y[i] 193 | ds.append(math.hypot(dx, dy)) 194 | yaw.append(math.atan2(dy, dx)) 195 | 196 | if len(yaw) == 0: 197 | return None, None, None 198 | 199 | yaw.append(yaw[-1]) 200 | ds.append(ds[-1]) 201 | 202 | for i in range(len(yaw) - 1): 203 | curv.append((yaw[i + 1] - yaw[i]) / ds[i]) 204 | 205 | return yaw, curv, ds 206 | 207 | 208 | def is_path_collision(path): 209 | index = range(0, len(path.x), 5) 210 | x = [path.x[i] for i in index] 211 | y = [path.y[i] for i in index] 212 | yaw = [path.yaw[i] for i in index] 213 | 214 | for ix, iy, iyaw in zip(x, y, yaw): 215 | d = 1.8 216 | dl = (C.RF - C.RB) / 2.0 217 | r = math.hypot((C.RF + C.RB) / 2.0, C.W / 2.0) + d 218 | 219 | cx = ix + dl * math.cos(iyaw) 220 | cy = iy + dl * math.sin(iyaw) 221 | 222 | for i in range(len(C.obs)): 223 | xo = C.obs[i][0] - cx 224 | yo = C.obs[i][1] - cy 225 | dx = xo * math.cos(iyaw) + yo * math.sin(iyaw) 226 | dy = -xo * math.sin(iyaw) + yo * math.cos(iyaw) 227 | 228 | if abs(dx) < r and abs(dy) < C.W / 2 + d: 229 | return 1.0 230 | 231 | return 0.0 232 | 233 | 234 | def verify_path(path): 235 | # if any([v > C.speed_max for v in path.s_v]) or \ 236 | # any([abs(a) > C.acceleration_max for a in path.s_a]): 237 | # return False 238 | 239 | if any([v > C.MAX_SPEED for v in path.s_v]) or \ 240 | any([abs(a) > C.MAX_ACCEL for a in path.s_a]) or \ 241 | any([abs(curv) > C.MAX_CURVATURE for curv in path.curv]): 242 | return False 243 | 244 | return True 245 | 246 | 247 | def extract_optimal_path(paths): 248 | if len(paths) == 0: 249 | return None 250 | 251 | while len(paths) > 1: 252 | path = min(paths, key=paths.get) 253 | paths.pop(path) 254 | if verify_path(path) is False: 255 | continue 256 | else: 257 | return path 258 | 259 | return paths[-1] 260 | 261 | 262 | def lattice_planner_for_Cruising(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path): 263 | paths = sampling_paths_for_Cruising(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path) 264 | path = extract_optimal_path(paths) 265 | 266 | return path 267 | 268 | 269 | def lattice_planner_for_Stopping(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path): 270 | paths = sampling_paths_for_Stopping(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path) 271 | path = extract_optimal_path(paths) 272 | 273 | return path 274 | 275 | 276 | def get_reference_line(x, y): 277 | index = range(0, len(x), 3) 278 | x = [x[i] for i in index] 279 | y = [y[i] for i in index] 280 | 281 | cubicspline = cubic_spline.Spline2D(x, y) 282 | s = np.arange(0, cubicspline.s[-1], 0.1) 283 | 284 | rx, ry, ryaw, rk = [], [], [], [] 285 | for i_s in s: 286 | ix, iy = cubicspline.calc_position(i_s) 287 | rx.append(ix) 288 | ry.append(iy) 289 | ryaw.append(cubicspline.calc_yaw(i_s)) 290 | rk.append(cubicspline.calc_curvature(i_s)) 291 | 292 | return rx, ry, ryaw, rk, cubicspline 293 | 294 | 295 | def pi_2_pi(theta): 296 | if theta > math.pi: 297 | return theta - 2.0 * math.pi 298 | 299 | if theta < -math.pi: 300 | return theta + 2.0 * math.pi 301 | 302 | return theta 303 | 304 | 305 | def main_Crusing(): 306 | ENV = env.ENVCrusing() 307 | wx, wy = ENV.ref_line 308 | bx1, by1 = ENV.bound_in 309 | bx2, by2 = ENV.bound_out 310 | 311 | C.obs = np.array([[50, 10], [96, 25], [70, 40], 312 | [40, 50], [25, 75]]) 313 | 314 | obs_x = [x for x, y in C.obs] 315 | obs_y = [y for x, y in C.obs] 316 | 317 | rx, ry, ryaw, rk, ref_path = get_reference_line(wx, wy) 318 | 319 | l0 = 2.0 # current lateral position [m] 320 | l0_v = 0.0 # current lateral speed [m/s] 321 | l0_a = 0.0 # current lateral acceleration [m/s] 322 | s0 = 0.0 # current course position 323 | s0_v = 20.0 / 3.6 # current speed [m/s] 324 | s0_a = 0.0 325 | 326 | while True: 327 | path = lattice_planner_for_Cruising(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path) 328 | 329 | if path is None: 330 | print("No feasible path found!!") 331 | break 332 | 333 | l0 = path.l[1] 334 | l0_v = path.l_v[1] 335 | l0_a = path.l_a[1] 336 | s0 = path.s[1] 337 | s0_v = path.s_v[1] 338 | s0_a = path.s_a[1] 339 | 340 | if np.hypot(path.x[1] - rx[-1], path.y[1] - ry[-1]) <= 2.0: 341 | print("Goal") 342 | break 343 | 344 | dy = (path.yaw[2] - path.yaw[1]) / path.ds[1] 345 | steer = pi_2_pi(math.atan(-C.WB * dy)) 346 | 347 | plt.cla() 348 | # for stopping simulation with the esc key. 349 | plt.gcf().canvas.mpl_connect( 350 | 'key_release_event', 351 | lambda event: [exit(0) if event.key == 'escape' else None]) 352 | plt.plot(rx, ry, linestyle='--', color='gray') 353 | plt.plot(bx1, by1, linewidth=1.5, color='k') 354 | plt.plot(bx2, by2, linewidth=1.5, color='k') 355 | plt.plot(path.x[1:], path.y[1:], linewidth='2', color='royalblue') 356 | plt.plot(obs_x, obs_y, 'ok') 357 | draw.draw_car(path.x[1], path.y[1], path.yaw[1], steer, C) 358 | plt.title("[Crusing Mode] v :" + str(s0_v * 3.6)[0:4] + " km/h") 359 | plt.axis("equal") 360 | plt.pause(0.0001) 361 | 362 | plt.pause(0.0001) 363 | plt.show() 364 | 365 | 366 | def main_Stopping(): 367 | ENV = env.ENVStopping() 368 | wx, wy = ENV.ref_line 369 | bx1, by1 = ENV.bound_up 370 | bx2, by2 = ENV.bound_down 371 | 372 | C.ROAD_WIDTH = ENV.road_width 373 | rx, ry, ryaw, rk, ref_path = get_reference_line(wx, wy) 374 | 375 | l0 = 0.0 # current lateral position [m] 376 | l0_v = 0.0 # current lateral speed [m/s] 377 | l0_a = 0.0 # current lateral acceleration [m/s] 378 | s0 = 0.0 # current course position 379 | s0_v = 30.0 / 3.6 # current speed [m/s] 380 | s0_a = 0.0 381 | 382 | while True: 383 | path = lattice_planner_for_Stopping(l0, l0_v, l0_a, s0, s0_v, s0_a, ref_path) 384 | 385 | if path is None: 386 | print("No feasible path found!!") 387 | break 388 | 389 | l0 = path.l[1] 390 | l0_v = path.l_v[1] 391 | l0_a = path.l_a[1] 392 | s0 = path.s[1] 393 | s0_v = path.s_v[1] 394 | s0_a = path.s_a[1] 395 | 396 | if np.hypot(path.x[1] - 56.0, path.y[1] - 0) <= 1.5: 397 | print("Goal") 398 | break 399 | 400 | plt.cla() 401 | # for stopping simulation with the esc key. 402 | plt.gcf().canvas.mpl_connect( 403 | 'key_release_event', 404 | lambda event: [exit(0) if event.key == 'escape' else None]) 405 | plt.plot(rx, ry, linestyle='--', color='gray') 406 | plt.plot(bx1, by1, linewidth=1.5, color='k') 407 | plt.plot(bx2, by2, linewidth=1.5, color='k') 408 | plt.plot(path.x[1:], path.y[1:], linewidth='2', color='royalblue') 409 | draw.draw_car(path.x[1], path.y[1], path.yaw[1], 0.0, C) 410 | plt.title("[Stopping Mode] v :" + str(s0_v * 3.6)[0:4] + " km/h") 411 | plt.axis("equal") 412 | plt.pause(0.0001) 413 | 414 | plt.pause(0.0001) 415 | plt.show() 416 | 417 | plt.plot(rx, ry, linestyle='--', color='gray') 418 | plt.plot(bx1, by1, linewidth=1.5, color='k') 419 | plt.plot(bx2, by2, linewidth=1.5, color='k') 420 | plt.axis("equal") 421 | plt.show() 422 | 423 | 424 | if __name__ == '__main__': 425 | main_Crusing() 426 | # main_Stopping() 427 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | This repository implemented some common motion planners used on autonomous vehicles, including 3 | * [Hybrid A* Planner](https://ai.stanford.edu/~ddolgov/papers/dolgov_gpp_stair08.pdf) 4 | * [Frenet Optimal Trajectory](https://www.researchgate.net/publication/224156269_Optimal_Trajectory_Generation_for_Dynamic_Street_Scenarios_in_a_Frenet_Frame) 5 | * [Hierarchical Optimization-Based Collision Avoidance (H-OBCA)](https://ieeexplore.ieee.org/document/9062306) (Incomplete) 6 | 7 | Also, this repository provides some controllers for path tracking, including 8 | * [Pure Pursuit + PID](https://www.ri.cmu.edu/pub_files/pub3/coulter_r_craig_1992_1/coulter_r_craig_1992_1.pdf) 9 | * [Rear-Wheel Feedback + PID](https://www.ri.cmu.edu/pub_files/2009/2/Automatic_Steering_Methods_for_Autonomous_Automobile_Path_Tracking.pdf) 10 | * [Front-Wheel Feedback / Stanley + PID](http://robots.stanford.edu/papers/thrun.stanley05.pdf) 11 | * [LQR + PID](https://github.com/ApolloAuto/apollo/tree/master/modules/control/controller) 12 | * [Linear MPC](https://borrelli.me.berkeley.edu/pdfpub/pub-6.pdf) 13 | 14 | ## Requirement 15 | * Python 3.6 or above 16 | * [SciPy](https://www.scipy.org/) 17 | * [cvxpy](https://github.com/cvxgrp/cvxpy) 18 | * [Reeds-Shepp Curves](https://github.com/zhm-real/ReedsSheppCurves) 19 | * [pycubicspline](https://github.com/AtsushiSakai/pycubicspline) 20 | 21 | ## Vehicle models 22 | This repository uses two models: simple car model and [car pulling trailers model](http://planning.cs.uiuc.edu/node661.html#77556). 23 | 24 | ## Hybrid A* Planner 25 |
26 | 27 | 28 | 29 | 30 | 31 |
12
32 | 33 | 34 | 35 | 36 | 37 |
1112
38 | 39 | 40 | 41 | 42 | 43 |
1314
44 |
45 | 46 | ## State Lattice Planner 47 |
48 | 49 | 50 | 51 | 52 | 53 |
12
54 |
55 | 56 | ## Controllers 57 |
58 | 59 | 60 | 61 | 62 | 63 |
12
64 | 65 | 66 | 67 | 68 | 69 |
12
70 | 71 | 72 | 73 | 74 | 75 |
12
76 |
77 | 78 | ## Paper 79 | ### Planning 80 | * [Basic Path Planning Algorithms: ](https://github.com/zhm-real/PathPlanning) PathPlanning 81 | * [Baidu Apollo Planning module: ](https://github.com/ApolloAuto/apollo/tree/master/modules/planning) Recommended Materials 82 | * [Survey of Planning and Control algos: ](https://arxiv.org/pdf/1604.07446.pdf) A Survey of Motion Planning and Control Techniques for Self-driving Urban Vehicles 83 | * [Hybrid A* Planner: ](https://ai.stanford.edu/~ddolgov/papers/dolgov_gpp_stair08.pdf) Practical Search Techniques in Path Planning for Autonomous Driving 84 | * [Frenet Optimal Trajectory: ](https://www.researchgate.net/publication/224156269_Optimal_Trajectory_Generation_for_Dynamic_Street_Scenarios_in_a_Frenet_Frame) Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenet Frame 85 | 86 | ### Control 87 | * [Baidu Apollo Control module: ](https://github.com/ApolloAuto/apollo/tree/master/modules/control) Recommended Materials 88 | * [Pure Pursuit: ](https://www.ri.cmu.edu/pub_files/pub3/coulter_r_craig_1992_1/coulter_r_craig_1992_1.pdf) Implementation of the Pure Pursuit Path Tracking Algorithm 89 | * [Rear-Wheel Feedback: ](https://www.ri.cmu.edu/pub_files/2009/2/Automatic_Steering_Methods_for_Autonomous_Automobile_Path_Tracking.pdf) Automatic Steering Methods for Autonomous Automobile Path Tracking 90 | * [Front-Wheel Feedback / Stanley: ](http://robots.stanford.edu/papers/thrun.stanley05.pdf) Stanley: The Robot that Won the DARPA Grand Challenge 91 | * [LQR: ](https://github.com/ApolloAuto/apollo/tree/master/modules/control/controller) ApolloAuto/apollo: An open autonomous driving platform 92 | * [Linear MPC: ](https://borrelli.me.berkeley.edu/pdfpub/pub-6.pdf) MPC-Based Approach to Active Steering for Autonomous Vehicle Systems 93 | 94 | ## Useful Material 95 | * [HybridAStarTrailer (Julia): ](https://github.com/AtsushiSakai/HybridAStarTrailer) by AtsushiSakai 96 | * [Hybrid Path Planner (C++): ](https://github.com/karlkurzer/path_planner) by KTH Research Concept Vehicle 97 | --------------------------------------------------------------------------------