Newer
Older
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle
from guided_mrmp.optimizer import Optimizer
from casadi import *
rachelmoan
committed
"""
A class that resolves conflicts using trajectoy optimization.
"""
def __init__(self, num_robots, robot_radius, starts, goals, circle_obstacles, rectangle_obstacles,
rachelmoan
committed
rob_dist_weight, obs_dist_weight, control_weight, time_weight, goal_weight, conflicts, all_robots):
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.time_weight = time_weight
self.robot_radius = MX(robot_radius)
self.goal_weight = goal_weight
rachelmoan
committed
self.conflicts = conflicts
self.all_robots = all_robots
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 sumsqr(robot_position - transpose(circle[:2]))
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): distance to the obstacle
c (float): controls the steepness of curve.
higher c --> gets more expensive faster as you move toward obs
d_max (float): The threshold distance at which the barrier starts to apply
"""
return c*fmax(0, d_max-d)**2
def log_normal_barrier(self, sigma, d, c):
return c*fmax(0, 2-(d/sigma))**2.5
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
outputs:
- problem (dict): dictionary containing the optimization problem
and the decision variables
"""
opti = Opti() # Optimization problem
# ---- decision variables --------- #
X = opti.variable(self.num_robots*3, N+1) # state trajectory (x,y,heading)
pos = X[:self.num_robots*2,:] # position is the first two values
x = pos[0::2,:]
y = pos[1::2,:]
heading = X[self.num_robots*2:,:] # heading is the last value
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
# ---- 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):
for k in range(N):
for c in range(circle_obs.shape[0]):
circle = circle_obs[c, :]
d = sumsqr(pos[2*r : 2*(r+1), k] - transpose(circle[:2])) - 2*self.robot_radius - circle[2]
dist_to_other_obstacles += self.apply_quadratic_barrier(3*(self.robot_radius + circle[2]), d, 10)
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:
d = sumsqr(pos[2*r1 : 2*(r1+1), k] - pos[2*r2 : 2*(r2+1), k])
rachelmoan
committed
dist_to_other_robots += self.apply_quadratic_barrier(3*self.robot_radius, d, 2)
# ---- dynamics constraints ---- #
dt = T/N # length of a control interval
pi = [3.14159]*self.num_robots
pi = np.array(pi)
pi = DM(pi)
for k in range(N): # loop over control intervals
dxdt = vel[:,k] * cos(heading[:,k])
dydt = vel[:,k] * sin(heading[:,k])
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, 2*pi))
# ------ Control panalty ------ #
# Calculate the sum of squared differences between consecutive heading angles
control_penalty += sumsqr(fmod(heading[:,k+1] - heading[:,k] + pi, 2*pi) - pi)
control_penalty += sumsqr(vel[:,k+1] - vel[:,k])
# ------ Distance to goal penalty ------ #
dist_to_goal = 0
for r in range(self.num_robots):
# calculate the distance to the goal in the final control interval
#
dist_to_goal += sumsqr(pos[2*r : 2*(r+1), -1] - self.goals[r])
robot_cost = self.rob_dist_weight*dist_to_other_robots
obs_cost = self.obs_dist_weight*dist_to_other_obstacles
time_cost = self.time_weight*T
control_cost = self.control_weight*control_penalty
goal_cost = self.goal_weight*dist_to_goal
cost = robot_cost + obs_cost + control_cost + time_cost +goal_cost
# ------ control constraints ------ #
for k in range(N):
for r in range(self.num_robots):
rachelmoan
committed
opti.subject_to(sumsqr(vel[r,k]) <= .5**2)
opti.subject_to(sumsqr(omega[r,k]) <= .5**2)
# ------ bound x, y, and time ------ #
opti.subject_to(opti.bounded(x_range[0]+self.robot_radius,x,x_range[1]-self.robot_radius))
opti.subject_to(opti.bounded(y_range[0]+self.robot_radius,y,y_range[1]-self.robot_radius))
opti.subject_to(opti.bounded(0,T,50))
for r in range(self.num_robots):
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(sumsqr(pos[2*r : 2*(r+1), -1] - self.goals[r]) < 1**2)
return {'opti':opti, 'X':X, 'U':U, 'T':T, 'cost':cost, 'robot_cost':robot_cost, 'obs_cost':obs_cost, 'time_cost':time_cost, 'control_cost':control_cost, 'goal_cost':goal_cost}
def solve_optimization_problem(self, problem, initial_guesses=None, solver_options=None, prior_solution=None):
opt = Optimizer(problem)
results,sol = opt.solve_optimization_problem(initial_guesses, solver_options, prior_solution)
return results,sol
def solve(self, N, x_range, y_range, initial_guesses=None, solver_options=None, prior_solution=None):
"""
Setup and solve a multi-robot traj opt problem
input:
- N (int): the number of control intervals
- x_range (tuple):
- y_range (tuple):
"""
problem = self.problem_setup(N, x_range, y_range)
rachelmoan
committed
results,old_sol = self.solve_optimization_problem(problem, initial_guesses, solver_options, prior_solution)
if results['status'] == 'failed':
rachelmoan
committed
return None, None, None, None, None, None, None, None
X = results['X']
sol = results['solution']
U = results['U']
rachelmoan
committed
T = results['T']
# 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:,:]
vels = U[0::2,:]
omegas = U[1::2,:]
return lam_g,sol,pos, vels, omegas, x_vals, y_vals, theta_vals, T
def get_local_controls(self, controls):
"""
Get the local controls for the robots in the conflict
"""
l = self.num_robots
final_trajs = [None]*l
for c in self.conflicts:
# Get the robots involved in the conflict
robots = [self.all_robots[r.label] for r in c]
# Solve the trajectory optimization problem
initial_guess = None
rachelmoan
committed
solver_options = {'ipopt.print_level': 1, 'print_time': 1}
# y range is the smallest y of the starts/goals to the largest y of the starts/goals
y_range = (min([r[1]-self.robot_radius for r in self.starts + self.goals]), max([r[1]+self.robot_radius for r in self.starts + self.goals]))
x_range = (min([r[0]-self.robot_radius for r in self.starts + self.goals]), max([r[0]+self.robot_radius for r in self.starts + self.goals]))
sol, x_opt, vels, omegas, xs,ys, thetas, T = self.solve(20, x_range, y_range,initial_guess, solver_options)
if sol is None:
print("Failed to solve the optimization problem")
pos_vals = np.array(sol.value(x_opt))
# Update the controls for the robots
for r, vel, omega, x,y in zip(robots, vels, omegas, xs,ys):
rachelmoan
committed
controls[r.label] = [vel[0], omega[0]]
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
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.title('Robot Paths')
plt.grid(False)
plt.show()
rachelmoan
committed
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
def check_goal_overlap(goal1, goal2, rad):
"""
Check if the goals overlap
"""
# get the vector between the two goals
v = np.array(goal2) - np.array(goal1)
# check if the distance between the two goals is less than 2*rad
if np.linalg.norm(v) < 2*rad:
return True
return False
def fix_goal_overlap(start1, goal1, start2, goal2):
"""
Fix the goal overlap
"""
# get the vectors between the starts and goals
v1 = np.array(goal1) - np.array(start1)
v2 = np.array(goal2) - np.array(start2)
# get the vectors orthogonal to the vectors between the starts and goals
v1_orth = np.array([-v1[1], v1[0]])
v2_orth = np.array([-v2[1], v2[0]])
# move the goals in the direction of the orthogonal vectors
goal1 = goal1 + .5*v1_orth
goal2 = goal2 + .5*v2_orth
return goal1, goal2
if __name__ == "__main__":
# load all the data from test/db_opt_data1.yaml
import yaml
with open('guided_mrmp/tests/db_opt_data2.yaml') as file:
data = yaml.load(file, Loader=yaml.Loader)
from guided_mrmp.utils import Env
starts = data['starts']
goals = data['goals']
circle_obstacles = data['env'].circle_obs
rectangle_obstacles = data['env'].rect_obs
rob_dist_weight = data['rob_dist_weight']
obs_dist_weight = data['obstacle_weight']
control_weight = data['control_weight']
time_weight = data['time_weight']
goal_weight = data['goal_weight']
robot_radius = data['rad']
conflicts = data['conflicts']
all_robots = data['robots']
next_desired_controls = data['next_desired_controls']
current_trajs = data['trajectories']
old_goals = goals.copy()
start1 = starts[0][:2]
start2 = starts[1][:2]
goals[0], goals[1] = fix_goal_overlap(start1, goals[0], start2, goals[1])
# create the TrajOptResolver object
resolver = TrajOptResolver(len(starts),
robot_radius,
starts,
goals,
circle_obstacles,
rectangle_obstacles,
rob_dist_weight,
obs_dist_weight,
2,
time_weight,
goal_weight,
conflicts,
all_robots)
next_desired_controls, new_trajs = resolver.get_local_controls(next_desired_controls)
# use matplotlib to plot the current trajectories of the robots, and
# the new trajectories of the robots
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
for r in range(len(current_trajs)):
# plot the starts and goals of the robots as circles with radius rad
ax.add_patch(plt.Circle(starts[r], robot_radius, color='green', fill=True))
ax.add_patch(plt.Circle(goals[r], robot_radius, color='green', fill=False))
# plot the old goals of the robots as circles with radius rad
ax.add_patch(plt.Circle(old_goals[r], robot_radius, color='red', fill=False))
ax.plot(current_trajs[r][0], current_trajs[r][1], label=f'Robot {r+1} current', color='red')
ax.plot(new_trajs[r][0], new_trajs[r][1], label=f'Robot {r+1} new', color='blue')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.legend()
plt.show()