From 871ec408138991820c64df36f6dddf60869e8e78 Mon Sep 17 00:00:00 2001 From: rachelmoan <moanrachel516@gmail.com> Date: Wed, 16 Oct 2024 21:18:45 -0500 Subject: [PATCH] traj_opt testing problems straight from the database --- guided_mrmp/conflict_resolvers/traj_opt.py | 449 +++++++++++++-------- 1 file changed, 290 insertions(+), 159 deletions(-) diff --git a/guided_mrmp/conflict_resolvers/traj_opt.py b/guided_mrmp/conflict_resolvers/traj_opt.py index e4e05b5..1b9302e 100644 --- a/guided_mrmp/conflict_resolvers/traj_opt.py +++ b/guided_mrmp/conflict_resolvers/traj_opt.py @@ -2,7 +2,7 @@ import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import Circle, Rectangle from casadi import * -# from guided_mrmp.conflict_resolvers.curve_path import smooth_path +from guided_mrmp.conflict_resolvers.curve_path import smooth_path class TrajOptMultiRobot(): def __init__(self, num_robots, robot_radius, starts, goals, circle_obstacles, rectangle_obstacles, @@ -45,9 +45,19 @@ class TrajOptMultiRobot(): def log_normal_barrier(self, sigma, d, c): return c*fmax(0, 2-(d/sigma))**2.5 - def solve(self, num_control_intervals, initial_guess): + def problem_setup(self, N, x_range, y_range): + """ + Problem setup for the multi-robot collision resolution traj opt problem + + inputs: + - N (int): number of control intervals + - x_range (tuple): range of x values + - y_range (tuple): range of y values - N = num_control_intervals + outputs: + - problem (dict): dictionary containing the optimization problem + and the decision variables + """ opti = Opti() # Optimization problem # ---- decision variables --------- # @@ -57,16 +67,15 @@ class TrajOptMultiRobot(): y = pos[1::2,:] heading = X[self.num_robots*2:,:] # heading is the last value - - circle_obs = DM(self.circle_obs) # make the obstacles casadi objects - U = opti.variable(self.num_robots*2, N) # control trajectory (v, omega) vel = U[0::2,:] omega = U[1::2,:] T = opti.variable() # final time - - # sum up the cost of distance to obstacles + # ---- obstacle setup ------------ # + circle_obs = DM(self.circle_obs) # make the obstacles casadi objects + + # ------ Obstacle dist cost ------ # # TODO:: Include rectangular obstacles dist_to_other_obstacles = 0 for r in range(self.num_robots): @@ -74,29 +83,22 @@ class TrajOptMultiRobot(): 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(self.robot_radius + circle[2] + 0.5, d, 1) - # dist_to_other_obstacles += self.log_normal_barrier(5, d, 5) + dist_to_other_obstacles += self.apply_quadratic_barrier(2*(self.robot_radius + circle[2]), d, 5) + # ------ Robot dist cost ------ # 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]) - dist_to_other_robots += self.apply_quadratic_barrier(2*self.robot_radius+.5, d, 1) - - dt = T/N # length of a control interval + dist_to_other_robots += self.apply_quadratic_barrier(2*self.robot_radius, d, 1) + - # print("Initial pos:", pos[:, 0]) - # print("Initial heading:", heading[:, 0]) + # ---- dynamics constraints ---- # + dt = T/N # length of a control interval - pi = [3.14159*2]*self.num_robots + pi = [3.14159]*self.num_robots pi = np.array(pi) pi = DM(pi) @@ -106,62 +108,101 @@ class TrajOptMultiRobot(): dthetadt = omega[:,k] opti.subject_to(x[:,k+1]==x[:,k] + dt*dxdt) opti.subject_to(y[:,k+1]==y[:,k] + dt*dydt) - opti.subject_to(heading[:,k+1]==fmod(heading[:,k] + dt*dthetadt, pi)) + opti.subject_to(heading[:,k+1]==fmod(heading[:,k] + dt*dthetadt, 2*pi)) + + # ------ Control panalty ------ # # Calculate the sum of squared differences between consecutive heading angles heading_diff_penalty = 0 for k in range(N-1): - heading_diff_penalty += sumsqr(heading[:,k+1] - heading[:,k]) + heading_diff_penalty += sumsqr(fmod(heading[:,k+1] - heading[:,k] + pi, 2*pi) - pi) + + # ------ cost function ------ # opti.minimize(self.rob_dist_weight*dist_to_other_robots + self.obs_dist_weight*dist_to_other_obstacles + self.time_weight*T - + 5*heading_diff_penalty) + + self.control_weight*heading_diff_penalty) - # ---- path constraints ----------- + # ------ control constraints ------ # for k in range(N): for r in range(self.num_robots): opti.subject_to(sumsqr(vel[r,k]) <= 0.2**2) opti.subject_to(sumsqr(omega[r,k]) <= 0.2**2) - opti.subject_to(opti.bounded(0,x,10)) - opti.subject_to(opti.bounded(0,y,10)) - # opti.subject_to(opti.bounded(-0.05,vel,0.05)) - # opti.subject_to(opti.bounded(-.1,U,.1)) # control is limited + # ------ bound x, y, and time ------ # + opti.subject_to(opti.bounded(x_range[0],x,x_range[1])) + opti.subject_to(opti.bounded(y_range[0],y,y_range[1])) + opti.subject_to(opti.bounded(0,T,100)) - # ---- boundary conditions -------- + # ------ initial conditions ------ # for r in range(self.num_robots): - # opti.subject_to(vel[r, 0]==0) - opti.subject_to(pos[2*r : 2*(r+1), 0]==self.starts[r]) + + opti.subject_to(heading[r, 0]==self.starts[r][2]) + opti.subject_to(pos[2*r : 2*(r+1), 0]==self.starts[r][0:2]) opti.subject_to(pos[2*r : 2*(r+1), -1]==self.goals[r]) - # --- misc. constraints --- # - opti.subject_to(opti.bounded(0,T,100)) + return {'opti':opti, 'X':X, 'T':T} - # --- initial values for solver --- # - opti.set_initial(T, 20) + def solve_optimization_problem(self, problem, initial_guesses=None, solver_options=None): + opti = problem['opti'] - opti.set_initial(X,initial_guess) - - - - # --- solve NLP --- # - opti.solver("ipopt") # set numerical backend - sol = opti.solve() # actual solve + if initial_guesses: + for param, value in initial_guesses.items(): + print(f"param = {param}") + print(f"value = {value}") + opti.set_initial(problem[param], value) + + # Set numerical backend, with options if provided + if solver_options: + opti.solver('ipopt', solver_options) + else: + opti.solver('ipopt') + + try: + sol = opti.solve() # actual solve + status = 'succeeded' + except: + sol = None + status = 'failed' + + results = { + 'status' : status, + 'solution' : sol, + } + + if sol: + for var_name, var in problem.items(): + if var_name != 'opti': + results[var_name] = sol.value(var) + + return results + + def solve(self, N, x_range, y_range, initial_guesses): + """ + Setup and solve a multi-robot traj opt problem - # print(f"pos = {opti.debug.value(pos[2:4,:])}") + input: + - N (int): the number of control intervals + - x_range (tuple): + - y_range (tuple): + """ + problem = self.problem_setup(N, x_range, y_range) + results = self.solve_optimization_problem(problem, initial_guesses) - # Extract x and y values - x_vals = np.array(sol.value(x)) - y_vals = np.array(sol.value(y)) + X = results['X'] + sol = results['solution'] - # Extract theta values - theta_vals = np.array(sol.value(heading)) + # Extract the values that we want from the optimizer's solution + pos = X[:self.num_robots*2,:] + x_vals = pos[0::2,:] + y_vals = pos[1::2,:] + theta_vals = X[self.num_robots*2:,:] return sol,pos, x_vals, y_vals, theta_vals - def plot_paths(self, x_opt, initial_guess): + def plot_paths(self, x_opt, initial_guess, x_range, y_range): fig, ax = plt.subplots() # Plot obstacles @@ -171,12 +212,7 @@ class TrajOptMultiRobot(): # 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)) + colors = plt.cm.Set1(np.linspace(0, 1, self.num_robots)) # Plot robot paths for r,color in zip(range(self.num_robots),colors): @@ -184,31 +220,73 @@ class TrajOptMultiRobot(): 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.plot(initial_guess[r*3, :], initial_guess[r*3+1, :], color=color, linestyle='--') - ax.scatter(initial_guess[r*3, :], initial_guess[r*3+1, :], color=color, s=5 ) + if initial_guess is not None: + ax.plot(initial_guess[r*3, :], initial_guess[r*3+1, :], color=color, linestyle='--') + ax.scatter(initial_guess[r*3, :], initial_guess[r*3+1, :], color=color, s=5 ) + + plot_roomba(self.starts[r][0], self.starts[r][1], 0, color) + # plot_roomba(self.goals[r][0], self.goals[r][1], 0, color) + + + + plt.ylim(0, y_range[1]) + plt.xlim(0,x_range[1]) + plt.axis("equal") + plt.axis("off") + - ax.set_xlabel('X') - ax.set_ylabel('Y') - ax.legend() - ax.set_aspect('equal', 'box') + plt.tight_layout() - plt.ylim(0,10) - plt.xlim(0,10) - plt.title('Robot Paths') plt.grid(False) plt.show() + def plot_paths_db(self, x_opt, initial_guess,x_range, y_range): + 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')) + + colors = plt.cm.Set1(np.linspace(0, 1, self.num_robots)) + + # Plot robot paths + for r,color in zip(range(self.num_robots),colors): + if x_opt is not None: + 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=135,facecolors='none', edgecolors=color) + if initial_guess is not None: + ax.plot(initial_guess[r*3, :], initial_guess[r*3+1, :], color=color, linestyle='--') + ax.scatter(initial_guess[r*3, :], initial_guess[r*3+1, :], color=color, s=5 ) -def plot_sim(x_histories, y_histories, h_histories): + if x_opt is not None: plot_roomba(self.starts[r][0], self.starts[r][1], 0, color) + # plot_roomba(self.goals[r][0], self.goals[r][1], 0, color) - if len(x_histories) > 20: - colors = plt.cm.hsv(np.linspace(0.2, 1.0, len(x_histories))) - elif len(x_histories) > 10: - colors = plt.cm.tab20(np.linspace(0, 1, len(x_histories))) - else: - colors = plt.cm.tab10(np.linspace(0, 1, len(x_histories))) - + + plt.ylim(0, y_range[1]) + plt.xlim(0,x_range[1]) + plt.axis("equal") + # plt.axis("off") + + + plt.tight_layout() + + plt.grid(False) + plt.show() + + +def plot_sim(x_histories, y_histories, h_histories, x_range, y_range): + + x_histories = np.array(x_histories) + y_histories = np.array(y_histories) + h_histories = np.array(h_histories) + + colors = plt.cm.Set1(np.linspace(0, 1, len(x_histories))) longest_traj = max([len(x) for x in x_histories]) @@ -234,17 +312,23 @@ def plot_sim(x_histories, y_histories, h_histories): plot_roomba(x_history[-1], y_history[-1], h_history[-1], color) - ax = plt.gca() - ax.set_xlim([0, 10]) - ax.set_ylim([0, 10]) + plt.ylim(0, y_range[1]) + plt.xlim(0,x_range[1]) + plt.axis("equal") + # plt.axis("off") + + plt.tight_layout() + plt.grid(False) + plt.draw() + plt.savefig(f"frames/sim_{i}.png") plt.pause(0.2) input() -def plot_roomba(x, y, yaw, color): +def plot_roomba(x, y, yaw, color, radius=.5): """ Args: @@ -252,37 +336,114 @@ def plot_roomba(x, y, yaw, color): y (): yaw (): """ - LENGTH = 0.5 # [m] - WIDTH = 0.25 # [m] - OFFSET = LENGTH # [m] - fig = plt.gcf() ax = fig.gca() - circle = plt.Circle((x, y), .5, color=color, fill=False) + circle = plt.Circle((x, y), radius, color=color, fill=False) ax.add_patch(circle) # Plot direction marker - dx = 1 * np.cos(yaw) - dy = 1 * np.sin(yaw) - ax.arrow(x, y, dx, dy, head_width=0.1, head_length=0.1, fc='r', ec='r') + dx = radius * np.cos(yaw) + dy = radius * np.sin(yaw) + ax.arrow(x, y, dx, dy, head_width=0.1, head_length=0.05, fc='r', ec='r') + +def generate_prob_from_db(N, cp_dist=.5): + from guided_mrmp.utils import Library + import random + lib = Library("guided_mrmp/database/5x2_library") + lib.read_library_from_file() + + d = lib.key_to_idx + + # get a random key from the library + key, idx = random.choice(list(d.items())) + + print(key) + print(len(key)) + + num_robots = len(key) // 4 + + start_nodes = [] + goal_nodes = [] + + for i in range(0, len(key), 4): + start = [int(key[i]), int(key[i+1])] + goal = [int(key[i+2]), int(key[i+3])] + start_heading = np.arctan2(goal[1] - start[1], goal[0] - start[0]) + start.append(start_heading) + start_nodes.append(start) + goal_nodes.append(goal) + + + sol = lib.get_matching_solution(start_nodes, goal_nodes) + + print(f"sol = {sol}") + # turn this solution into an initial guess + initial_guess = np.zeros((num_robots*3, N+1)) + for i in range(num_robots): + print(f"Robot {i+1} solution:") + rough_points = np.array(sol[i]) + points = [] + for point in rough_points: + if point[0] == -1: break + points.append(point) + + points = np.array(points) + print(f"points = {points}") + + smoothed_curve = smooth_path(points, cp_dist, N) + print(f"smoothed_curve = {smoothed_curve}") + initial_guess[i*3, :] = smoothed_curve[:, 0] # x + initial_guess[i*3 + 1, :] = smoothed_curve[:, 1] # y + + for j in range(N): + dx = smoothed_curve[j+1, 0] - smoothed_curve[j, 0] + dy = smoothed_curve[j+1, 1] - smoothed_curve[j, 1] + initial_guess[i*3 + 2, j] = np.arctan2(dy, dx) + + # initial_guess[i*3 + 2, :] = np.arctan2(np.diff(smoothed_curve[:, 1]), + # np.diff(smoothed_curve[:, 0])) + + + + + print(sol) + + for i in range(num_robots): + print(f"Robot {i+1} initial guess:") + print(f"x: {initial_guess[i*3, :]}") + print(f"y: {initial_guess[i*3 + 1, :]}") + print(f"theta: {initial_guess[i*3 + 2, :]}") + + return start_nodes, goal_nodes, initial_guess if __name__ == "__main__": + import os + import numpy as np + import random + + seed = 1123581 + seed = 112 + print(f"***Setting Python Seed {seed}***") + os.environ['PYTHONHASHSEED'] = str(seed) + np.random.seed(seed) + random.seed(seed) + # define obstacles - circle_obs = np.array([[5,5,1], - [7,7,1], - [3,3,1]]) + circle_obs = np.array([[5,3,1]]) - circle_obs = np.array([]) + # circle_obs = np.array([]) rectangle_obs = np.array([]) # define all the robots' starts and goals robot_starts = [[1,6],[9,1],[2,2],[1,3]] robot_goals = [[9,1],[1,6],[8,8],[7,3]] + + # robot_starts = [[9,5]] # robot_goals = [[1,5]] @@ -294,76 +455,38 @@ if __name__ == "__main__": # other params num_robots = 4 - rob_radius = 0.25 - N = 20 - + rob_radius = .75 + N = 30 - # ---- straight line initial guess ---- # - print(f"N = {N}") - initial_guess = np.zeros((num_robots*3,N+1)) - print(initial_guess) - # for i,(start,goal) in enumerate(zip(robot_starts, robot_goals)): - for i in range(0,num_robots*3,3): - # print(f"i = {i}") - start=robot_starts[int(i/3)] - goal=robot_goals[int(i/3)] - # print(f"start = {start}") - # print(f"goal = {goal}") - initial_guess[i,:] = np.linspace(start[0], goal[0], N+1) - initial_guess[i+1,:] = np.linspace(start[1], goal[1], N+1) - dx = goal[0] - start[0] - dy = goal[1] - start[1] - initial_guess[i+2,:] = np.arctan2(dy,dx)*np.ones(N+1) - # initial_guess[i+2,:] = np.linspace(.5, .5, N+1) - # initial_guess[i+3,:] = np.linspace(.5, .5, N+1) - - print(initial_guess) - - # jagged initial guess - # initial_guess = np.array([ - # [1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9], - # [6, 5, 4, 3, 2, 1, 1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], - # [9, 9, 9, 9, 9, 9, 8 ,7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1], - # [1, 2, 3, 4, 5, 6, 6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - # ]) - - # points1 = np.array([[1,6], - # [1,1], - # [9,1]]) - - # points2 = np.array([[9,1], - # [9,6], - # [1,6]]) - - # points3 = np.array([[2,2], - # [4,4], - # [8,8]]) - - # points4 = np.array([[1,3], - # [3,3], - # [7,3]]) + robot_starts, robot_goals, initial_guess = generate_prob_from_db(N) - # smoothed_curve1 = smooth_path(points1, 3) - # smoothed_curve2 = smooth_path(points2, 3) - # smoothed_curve3 = smooth_path(points3, 3) - # smoothed_curve4 = smooth_path(points4, 3) + num_robots = len(robot_starts) + h = 2 + x_range = (0, 5*h) + y_range = (0, 2*h) + robot_starts = np.array(robot_starts) + robot_goals = np.array(robot_goals) + robot_starts = robot_starts*h + .5*h + robot_goals = robot_goals*h + .5*h + initial_guess = initial_guess*h + .5*h - # print(f"smoothed_curve = {smoothed_curve}") + print(f"robot_starts = {robot_starts}") + print(f"robot_goals = {robot_goals}") - # initial_guess = np.zeros((num_robots*3,N+1)) - # initial_guess[0,:] = smoothed_curve1[:,0] - # initial_guess[1,:] = smoothed_curve1[:,1] - # initial_guess[3,:] = smoothed_curve2[:,0] - # initial_guess[4,:] = smoothed_curve2[:,1] - # initial_guess[6,:] = smoothed_curve3[:,0] - # initial_guess[7,:] = smoothed_curve3[:,1] - # initial_guess[9,:] = smoothed_curve4[:,0] - # initial_guess[10,:] = smoothed_curve4[:,1] + # ---- straight line initial guess ---- # + straight_line = False + if straight_line: + initial_guess = np.zeros((num_robots*3,N+1)) + for i in range(0,num_robots*3,3): + start=robot_starts[int(i/3)] + goal=robot_goals[int(i/3)] + initial_guess[i,:] = np.linspace(start[0], goal[0], N+1) + initial_guess[i+1,:] = np.linspace(start[1], goal[1], N+1) + dx = goal[0] - start[0] + dy = goal[1] - start[1] + initial_guess[i+2,:] = np.arctan2(dy,dx)*np.ones(N+1) - solver = TrajOptMultiRobot(num_robots=num_robots, robot_radius=rob_radius, @@ -376,13 +499,21 @@ if __name__ == "__main__": control_weight=control_costs_weight, time_weight=time_weight ) - sol,pos, xs, ys, thetas = solver.solve(N, initial_guess) + + initial_guesses = { + 'X': initial_guess, + 'T': 20 + } + + solver.plot_paths_db(None, initial_guess, x_range, y_range) + sol,pos, xs, ys, thetas = solver.solve(N, x_range, y_range, initial_guesses) pos_vals = np.array(sol.value(pos)) - - solver.plot_paths(pos_vals, initial_guess) + solver.plot_paths_db(None, initial_guess, x_range, y_range) + solver.plot_paths_db(pos_vals, None, x_range, y_range) + plot_sim(xs, ys, thetas, x_range, y_range) - print(pos_vals) + # print(pos_vals) @@ -392,6 +523,6 @@ if __name__ == "__main__": # ys.append(pos_vals[r*2+1, :]) # thetas.append(pos_vals[num_robots*2 + r, :]) - plot_sim(xs, ys, thetas) + # plot_sim(xs, ys, thetas) -- GitLab