From 1fcde4edc5d647aeab8e6de38951245549d2f1af Mon Sep 17 00:00:00 2001 From: rachelmoan <moanrachel516@gmail.com> Date: Mon, 3 Jun 2024 10:08:27 -0600 Subject: [PATCH] Adding trajectory optimization for multiple robots --- LocalPlanners/TrajOpt.py | 204 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 LocalPlanners/TrajOpt.py diff --git a/LocalPlanners/TrajOpt.py b/LocalPlanners/TrajOpt.py new file mode 100644 index 0000000..100a88d --- /dev/null +++ b/LocalPlanners/TrajOpt.py @@ -0,0 +1,204 @@ +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Circle, Rectangle +from casadi import * + +class TrajOptMultiRobot(): + def __init__(self, num_robots, robot_radius, starts, goals, circle_obstacles, rectangle_obstacles, + rob_dist_weight, obs_dist_weight, control_weight, time_weight): + self.num_robots = num_robots + self.starts = starts + self.goals = goals + self.circle_obs = circle_obstacles + self.rect_obs = rectangle_obstacles + self.rob_dist_weight = rob_dist_weight + self.obs_dist_weight = obs_dist_weight + self.control_weight =control_weight + self.time_weight = time_weight + self.robot_radius = MX(robot_radius) + + def dist(self, robot_position, circle): + """ + Returns the distance between a robot and a circle + + params: + robot_position [x,y] + circle [x,y,radius] + """ + return norm_2(robot_position - transpose(circle[:2])) - circle[2] - self.robot_radius + + def apply_quadratic_barrier(self, d_max, d, c): + """ + Applies a quadratic barrier to some given distance. The quadratic barrier + is a soft barrier function. We are using it for now to avoid any issues with + invalid initial solutions, which hard barrier functions cannot handle. + + params: + d (float): + c (float): controls the steepness of curve. + higher c --> gets more expensive faster as you move toward obs + d_max (float): + """ + return c*fmax(0, d_max-d)**2 + + def solve(self, num_control_intervals): + + N = num_control_intervals + opti = Opti() # Optimization problem + + # ---- decision variables --------- # + X = opti.variable(self.num_robots*4, N+1) # state trajectory (x,y,v_x,v_y) + pos = X[:self.num_robots*2,:] # position is the first two values + vel = X[self.num_robots*2:,:] # velocity is the last two values + + + circle_obs = DM(self.circle_obs) # make the obstacles casadi objects + + U = opti.variable(self.num_robots*2, N) # control trajectory (a_x, a_y) + T = opti.variable() # final time + + + # sum up the cost of distance to obstacles + # TODO:: Include rectangular obstacles + dist_to_other_obstacles = 0 + for r in range(self.num_robots): + for k in range(N): + for c in range(circle_obs.shape[0]): + circle = circle_obs[c, :] + d = self.dist(pos[2*r : 2*(r+1), k], circle) + dist_to_other_obstacles += self.apply_quadratic_barrier(1, d, 1) + + dist_to_other_robots = 0 + for k in range(N): + for r1 in range(self.num_robots): + for r2 in range(self.num_robots): + if r1 != r2: + # print(f"\n{r1} position1 = {pos[2*r1 : 2*(r1+1), k]}") + # print(f"{r2} position2 = {pos[2*r2 : 2*(r2+1), k]}") + + # note: using norm 2 here gives an invalid num detected error. + # Must be the sqrt causing an issue + # d = norm_2(pos[2*r1 : 2*(r1+1), k] - pos[2*r2 : 2*(r2+1), k]) - 2*self.robot_radius + d = sumsqr(pos[2*r1 : 2*(r1+1), k] - pos[2*r2 : 2*(r2+1), k]) - 2*self.robot_radius + dist_to_other_robots += self.apply_quadratic_barrier(5, d, 2) + + # control costs + control_costs = 0 + dt = T/N # length of a control interval + for k in range(N): # loop over control intervals + opti.subject_to(pos[:,k+1]==pos[:,k] + dt*vel[:,k]) + opti.subject_to(vel[:,k+1]==vel[:,k] + dt*U[:,k]) + + + opti.minimize(dist_robots_weight*dist_to_other_robots + + dist_obstacles_weight*dist_to_other_obstacles + + control_costs_weight* control_costs + + time_weight*T) + + # ---- path constraints ----------- + for k in range(N): + for r in range(self.num_robots): + opti.subject_to(sumsqr(vel[2*r:2*(r+1),k]) <= 0.2**2) + opti.subject_to(sumsqr(U[2*r:2*(r+1),k]) <= 0.1**2) + + opti.subject_to(opti.bounded(0,pos,10)) + # opti.subject_to(opti.bounded(-0.2,vel,0.2)) + # opti.subject_to(opti.bounded(-.1,U,.1)) # control is limited + + # ---- boundary conditions -------- + for r in range(self.num_robots): + opti.subject_to(vel[2*r : 2*(r+1), 0]==[0,0]) + opti.subject_to(pos[2*r : 2*(r+1), 0]==self.starts[r]) + opti.subject_to(pos[2*r : 2*(r+1), -1]==self.goals[r]) + + # ---- misc. constraints ---------- + opti.subject_to(opti.bounded(0,T,100)) + + # ---- initial values for solver --- + opti.set_initial(T, 20) + + # ---- solve NLP ------ + opti.solver("ipopt") # set numerical backend + sol = opti.solve() # actual solve + + return sol,pos + + def plot_paths(self, x_opt): + fig, ax = plt.subplots() + + # Plot obstacles + for obstacle in self.circle_obs: + # if len(obstacle) == 2: # Circle + ax.add_patch(Circle(obstacle, obstacle[2], color='red')) + # elif len(obstacle) == 4: # Rectangle + # ax.add_patch(Rectangle((obstacle[0], obstacle[1]), obstacle[2], obstacle[3], color='red')) + + if self.num_robots > 20: + colors = plt.cm.hsv(np.linspace(0.2, 1.0, self.num_robots)) + elif self.num_robots > 10: + colors = plt.cm.tab20(np.linspace(0, 1, self.num_robots)) + else: + colors = plt.cm.tab10(np.linspace(0, 1, self.num_robots)) + + # Plot robot paths + for r,color in zip(range(self.num_robots),colors): + ax.plot(x_opt[r*2, :], x_opt[r*2+1, :], label=f'Robot {r+1}', color=color) + ax.scatter(x_opt[r*2, :], x_opt[r*2+1, :], color=color, s=10 ) + ax.scatter(self.starts[r][0], self.starts[r][1], s=85,color=color) + ax.scatter(self.goals[r][0], self.goals[r][1], s=85,facecolors='none', edgecolors=color) + + ax.set_xlabel('X') + ax.set_ylabel('Y') + ax.legend() + ax.set_aspect('equal', 'box') + + plt.ylim(0,10) + plt.xlim(0,10) + plt.title('Robot Paths') + plt.grid(False) + plt.show() + + +if __name__ == "__main__": + + # define obstacles + circle_obs = np.array([[5,5,1], + [7,7,1], + [3,3,1]]) + + rectangle_obs = np.array([]) + + # define all the robots' starts and goals + robot_starts = [[1,6],[9,5],[2,2],[1,3]] + robot_goals = [[9,6],[1,5],[8,8],[7,3]] + + # weights for the cost function + dist_robots_weight = 1.0 + dist_obstacles_weight = 1.7 + control_costs_weight = 1.0 + time_weight = 1.0 + + # other params + num_robots = 4 + rob_radius = 0.25 + + solver = TrajOptMultiRobot(num_robots=num_robots, + robot_radius=rob_radius, + starts=robot_starts, + goals=robot_goals, + circle_obstacles=circle_obs, + rectangle_obstacles=rectangle_obs, + rob_dist_weight=dist_robots_weight, + obs_dist_weight=dist_obstacles_weight, + control_weight=control_costs_weight, + time_weight=time_weight + ) + sol,pos = solver.solve(20) + pos_vals = np.array(sol.value(pos)) + + + solver.plot_paths(pos_vals) + + + + -- GitLab