|
| 1 | +import numpy as np |
| 2 | +from scipy.spatial import Voronoi, voronoi_plot_2d |
| 3 | +import matplotlib.pyplot as plt |
| 4 | +import random |
| 5 | +from shapely import Polygon, Point, intersection |
| 6 | +from tqdm import tqdm |
| 7 | +from pathlib import Path |
| 8 | +import math |
| 9 | +from scipy.optimize import minimize |
| 10 | +import argparse |
| 11 | + |
| 12 | +from copy import deepcopy as dc |
| 13 | +from sklearn.mixture import GaussianMixture |
| 14 | + |
| 15 | +from utils import * |
| 16 | +from models import * |
| 17 | + |
| 18 | + |
| 19 | +def parse_args(): |
| 20 | + parser = argparse.ArgumentParser() |
| 21 | + parser.add_argument('--robots-num', type=int, default=12, help="number of robots") |
| 22 | + parser.add_argument('--range', type=float, default=3.0, help="sensing range") |
| 23 | + parser.add_argument('--width', type=float, default=30.0, help="area width") |
| 24 | + args = parser.parse_args() |
| 25 | + return args |
| 26 | + |
| 27 | +args = parse_args() |
| 28 | + |
| 29 | +ROBOTS_NUM = args.robots_num |
| 30 | +ROBOT_RANGE = args.range |
| 31 | +TARGETS_NUM = 2 |
| 32 | +COMPONENTS_NUM = 4 |
| 33 | +PARTICLES_NUM = 500 |
| 34 | +AREA_W = args.width |
| 35 | +GRID_STEPS = 64 |
| 36 | +vmax = 1.5 |
| 37 | +SAFETY_DIST = 2.0 |
| 38 | +EPISODES = 1 |
| 39 | +NUM_OBSTACLES = 1 |
| 40 | +NUM_STEPS = 500 |
| 41 | +NUM_CHANNELS = 3 |
| 42 | +GAMMA = 0.5 |
| 43 | +USE_CBF = False |
| 44 | +SAVE_POS = False |
| 45 | + |
| 46 | +resolution = 2 * ROBOT_RANGE / GRID_STEPS |
| 47 | + |
| 48 | + |
| 49 | +import numpy as np |
| 50 | +import matplotlib.pyplot as plt |
| 51 | +from pathlib import Path |
| 52 | + |
| 53 | +path = Path().resolve() |
| 54 | +path = path / "trained_models" |
| 55 | +# MODEL_PATH = path/'multichannel_2d_cnn.pt' |
| 56 | +MODEL_PATH = path/'2d_cnn_3ch_cbf.pt' |
| 57 | +print("Model Path ", MODEL_PATH) |
| 58 | + |
| 59 | + |
| 60 | +import torch |
| 61 | +from torch import nn |
| 62 | +from torch.nn import functional as F |
| 63 | + |
| 64 | +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| 65 | +print("Device: ", device) |
| 66 | + |
| 67 | +model = Multichannel_2D_CNN(NUM_CHANNELS).to(device) |
| 68 | + |
| 69 | +model.load_state_dict(torch.load(MODEL_PATH, map_location=device)) |
| 70 | +model.eval() |
| 71 | +print(model) |
| 72 | + |
| 73 | +eval_data = np.zeros((EPISODES, NUM_STEPS)) |
| 74 | +collision_counter = np.zeros((EPISODES, NUM_STEPS)) |
| 75 | + |
| 76 | +def objective_function(u): |
| 77 | + return np.linalg.norm(u)**2 |
| 78 | + |
| 79 | +def safety_constraint(u, A, b): |
| 80 | + return -np.dot(A,u) + b |
| 81 | + |
| 82 | +for episode in range(EPISODES): |
| 83 | + print(f"*** Episode {episode} ***") |
| 84 | + targets = np.zeros((TARGETS_NUM, 1, 2)) |
| 85 | + targets[0, 0, 0] = 0.0 |
| 86 | + targets[0, 0, 1] = 12.5 |
| 87 | + targets[1, 0, 0] = 8.0 |
| 88 | + targets[1, 0, 1] = 7.5 |
| 89 | + # for i in range(TARGETS_NUM): |
| 90 | + # targets[i, 0, 0] = -0.5*(AREA_W-1) + (AREA_W-1) * np.random.rand() |
| 91 | + # targets[i, 0, 1] = -0.5*(AREA_W-1) + (AREA_W-1) * np.random.rand() |
| 92 | + |
| 93 | + # plt.plot([-0.5*AREA_W, 0.5*AREA_W], [-0.5*AREA_W, -0.5*AREA_W], c='tab:blue', label="Environment") |
| 94 | + # plt.plot([0.5*AREA_W, 0.5*AREA_W], [-0.5*AREA_W, 0.5*AREA_W], c='tab:blue') |
| 95 | + # plt.plot([0.5*AREA_W, -0.5*AREA_W], [0.5*AREA_W, 0.5*AREA_W], c='tab:blue') |
| 96 | + # plt.plot([-0.5*AREA_W, -0.5*AREA_W], [0.5*AREA_W, -0.5*AREA_W], c='tab:blue') |
| 97 | + # plt.scatter(targets[:, :, 0], targets[:, :, 1], c='tab:orange', label="Targets") |
| 98 | + # # plt.legend() |
| 99 | + # plt.show() |
| 100 | + |
| 101 | + STD_DEV = 3.0 |
| 102 | + samples = np.zeros((TARGETS_NUM, PARTICLES_NUM, 2)) |
| 103 | + for k in range(TARGETS_NUM): |
| 104 | + for i in range(PARTICLES_NUM): |
| 105 | + samples[k, i, :] = targets[k, 0, :] + STD_DEV * np.random.randn(1, 2) |
| 106 | + |
| 107 | + |
| 108 | + |
| 109 | + # Fit GMM |
| 110 | + samples = samples.reshape((TARGETS_NUM*PARTICLES_NUM, 2)) |
| 111 | + print(samples.shape) |
| 112 | + gmm = GaussianMixture(n_components=COMPONENTS_NUM, covariance_type='full', max_iter=1000) |
| 113 | + gmm.fit(samples) |
| 114 | + |
| 115 | + means = gmm.means_ |
| 116 | + covariances = gmm.covariances_ |
| 117 | + mix = gmm.weights_ |
| 118 | + |
| 119 | + # print(f"Means: {means}") |
| 120 | + # print(f"Covs: {covariances}") |
| 121 | + # print(f"Mix: {mix}") |
| 122 | + |
| 123 | + |
| 124 | + ## -------- Generate decentralized probability grid --------- |
| 125 | + GRID_STEPS = 64 |
| 126 | + s = AREA_W/GRID_STEPS # step |
| 127 | + |
| 128 | + xg = np.linspace(-0.5*AREA_W, 0.5*AREA_W, GRID_STEPS) |
| 129 | + yg = np.linspace(-0.5*AREA_W, 0.5*AREA_W, GRID_STEPS) |
| 130 | + Xg, Yg = np.meshgrid(xg, yg) |
| 131 | + # Xg.shape |
| 132 | + # print(Xg.shape) |
| 133 | + |
| 134 | + Z = gmm_pdf(Xg, Yg, means, covariances, mix) |
| 135 | + Z = Z.reshape(GRID_STEPS, GRID_STEPS) |
| 136 | + Zmax = np.max(Z) |
| 137 | + Z = Z / Zmax |
| 138 | + |
| 139 | + obstacles = np.array([5.0, -5.0]) |
| 140 | + OBS_W = 20.0 |
| 141 | + |
| 142 | + fig, ax = plt.subplots(1, 1, figsize=(8,8)) |
| 143 | + plot_occgrid(Xg, Yg, Z, ax=ax) |
| 144 | + ax.plot([obstacles[0]-0.5*OBS_W, obstacles[0]+0.5*OBS_W], [obstacles[1]-0.5*OBS_W, obstacles[1]-0.5*OBS_W], c='tab:blue') |
| 145 | + ax.plot([obstacles[0]+0.5*OBS_W, obstacles[0]+0.5*OBS_W], [obstacles[1]-0.5*OBS_W, obstacles[1]+0.5*OBS_W], c='tab:blue') |
| 146 | + ax.plot([obstacles[0]+0.5*OBS_W, obstacles[0]-0.5*OBS_W], [obstacles[1]+0.5*OBS_W, obstacles[1]+0.5*OBS_W], c='tab:blue') |
| 147 | + ax.plot([obstacles[0]-0.5*OBS_W, obstacles[0]-0.5*OBS_W], [obstacles[1]+0.5*OBS_W, obstacles[1]-0.5*OBS_W], c='tab:blue') |
| 148 | + # ---------- Simulate episode --------- |
| 149 | + # ROBOTS_NUM = np.random.randint(6, ROBOTS_MAX) |
| 150 | + converged = False |
| 151 | + points = np.zeros((ROBOTS_NUM, 2)) |
| 152 | + points[:, 0] = -0.5*AREA_W + 8 * np.random.rand(ROBOTS_NUM) |
| 153 | + points[:, 1] = -0.5*AREA_W + AREA_W * np.random.rand(ROBOTS_NUM) |
| 154 | + robots_hist = np.zeros((1, points.shape[0], points.shape[1])) |
| 155 | + robots_hist[0, :, :] = points |
| 156 | + vis_regions = [] |
| 157 | + discretize_precision = 0.5 |
| 158 | + dt = 0.25 |
| 159 | + failed = False |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | + imgs = np.zeros((1, ROBOTS_NUM, NUM_CHANNELS, GRID_STEPS, GRID_STEPS)) |
| 165 | + vels = np.zeros((1, ROBOTS_NUM, 2)) |
| 166 | + |
| 167 | + r_step = 2 * ROBOT_RANGE / GRID_STEPS |
| 168 | + denom = np.sum(s**2 * gmm_pdf(Xg, Yg, means, covariances, mix)) |
| 169 | + print("Total info: ", denom) |
| 170 | + for s in range(1, NUM_STEPS+1): |
| 171 | + # print(f"*** Step {s} ***") |
| 172 | + all_stopped = True |
| 173 | + |
| 174 | + # mirror points across each edge of the env |
| 175 | + dummy_points = np.zeros((5*ROBOTS_NUM, 2)) |
| 176 | + dummy_points[:ROBOTS_NUM, :] = points |
| 177 | + mirrored_points = mirror(points, AREA_W) |
| 178 | + mir_pts = np.array(mirrored_points) |
| 179 | + dummy_points[ROBOTS_NUM:, :] = mir_pts |
| 180 | + |
| 181 | + # Voronoi partitioning |
| 182 | + vor = Voronoi(dummy_points) |
| 183 | + |
| 184 | + conv = True |
| 185 | + lim_regions = [] |
| 186 | + img_s = np.zeros((ROBOTS_NUM, NUM_CHANNELS, GRID_STEPS, GRID_STEPS)) |
| 187 | + vel_s = np.zeros((ROBOTS_NUM, 2)) |
| 188 | + num = 0.0 |
| 189 | + collision = False |
| 190 | + for idx in range(ROBOTS_NUM): |
| 191 | + p_i = vor.points[idx] |
| 192 | + |
| 193 | + |
| 194 | + # Save grid |
| 195 | + xg_i = np.linspace(-ROBOT_RANGE, ROBOT_RANGE, GRID_STEPS) |
| 196 | + yg_i = np.linspace(-ROBOT_RANGE, ROBOT_RANGE, GRID_STEPS) |
| 197 | + Xg_i, Yg_i = np.meshgrid(xg_i, yg_i) |
| 198 | + Z_i = gmm_pdf(Xg_i, Yg_i, means-p_i, covariances, mix) |
| 199 | + Z_i = Z_i.reshape(GRID_STEPS, GRID_STEPS) |
| 200 | + Zmax_i = np.max(Z_i) |
| 201 | + Z_i = Z_i / Zmax_i |
| 202 | + |
| 203 | + img_i = Z_i * 255 |
| 204 | + img_i = np.expand_dims(img_i, 0) |
| 205 | + |
| 206 | + neighs = np.delete(vor.points[:ROBOTS_NUM], idx, 0) |
| 207 | + local_pts = neighs - p_i |
| 208 | + |
| 209 | + # Remove undetected neighbors |
| 210 | + undetected = [] |
| 211 | + for i in range(local_pts.shape[0]): |
| 212 | + if local_pts[i, 0] < -ROBOT_RANGE or local_pts[i, 0] > ROBOT_RANGE or local_pts[i, 1] < -ROBOT_RANGE or local_pts[i, 1] > ROBOT_RANGE: |
| 213 | + undetected.append(i) |
| 214 | + |
| 215 | + local_pts = np.delete(local_pts, undetected, 0) |
| 216 | + |
| 217 | + img_obs = np.zeros((1, GRID_STEPS, GRID_STEPS)) |
| 218 | + img_neighs = np.zeros((1, GRID_STEPS, GRID_STEPS)) |
| 219 | + for i in range(GRID_STEPS): |
| 220 | + for j in range(GRID_STEPS): |
| 221 | + # jj = GRID_STEPS-1-j |
| 222 | + p_ij = np.array([-ROBOT_RANGE+j*r_step, -ROBOT_RANGE+i*r_step]) |
| 223 | + # print(f"Point ({i},{j}): {p_ij}") |
| 224 | + for n in local_pts: |
| 225 | + if np.linalg.norm(n - p_ij) <= SAFETY_DIST: |
| 226 | + img_neighs[0, i, j] = 255 |
| 227 | + |
| 228 | + # Check if outside boundaries |
| 229 | + p_w = p_ij + p_i |
| 230 | + if p_w[0] < -0.5*AREA_W or p_w[0] > 0.5*AREA_W or p_w[1] < -0.5*AREA_W or p_w[1] > 0.5*AREA_W: |
| 231 | + img_obs[0, i, j] = 255 |
| 232 | + |
| 233 | + # Check for obstacles |
| 234 | + if p_w[0] > obstacles[0] - 0.6*OBS_W and p_w[0] < obstacles[0] + 0.6*OBS_W and p_w[1] > obstacles[1] - 0.6*OBS_W and p_w[1] < obstacles[1] + 0.6*OBS_W: |
| 235 | + # if np.linalg.norm(p_w - obs) < SAFETY_DIST: |
| 236 | + img_obs[0, i, j] = 255 |
| 237 | + |
| 238 | + # -------- EVAL ------- |
| 239 | + region_id = vor.point_region[idx] |
| 240 | + cell = vor.regions[region_id] |
| 241 | + verts = vor.vertices[cell] |
| 242 | + poly = Polygon(verts) |
| 243 | + th = np.arange(0, 2*np.pi+np.pi/20, np.pi/20) |
| 244 | + rng_pts = p_i + ROBOT_RANGE * np.array([np.cos(th), np.sin(th)]).transpose() |
| 245 | + range_poly = Polygon(rng_pts) |
| 246 | + lim_region = intersection(poly, range_poly) |
| 247 | + |
| 248 | + dA = r_step**2 |
| 249 | + for x_i in np.arange(p_i[0]-ROBOT_RANGE, p_i[0]+ROBOT_RANGE, r_step): |
| 250 | + for y_i in np.arange(p_i[1]-ROBOT_RANGE, p_i[1]+ROBOT_RANGE, r_step): |
| 251 | + pt = Point(x_i, y_i) |
| 252 | + if lim_region.contains(pt): |
| 253 | + dA_pdf = dA * gmm_pdf(x_i, y_i, means, covariances, mix) |
| 254 | + num += dA_pdf |
| 255 | + |
| 256 | + # Check collisions |
| 257 | + dist = np.linalg.norm(local_pts, axis=1) |
| 258 | + if (dist < 0.1).any(): |
| 259 | + collision_counter[episode, s-1] = 1 |
| 260 | + print("Collision detected with neighbors!") |
| 261 | + if p_i[0] > obstacles[0] - 0.5*OBS_W and p_i[0] < obstacles[0] + 0.5*OBS_W and p_i[1] > obstacles[1] - 0.5*OBS_W and p_i[1] < obstacles[1] + 0.5*OBS_W: |
| 262 | + # if np.linalg.norm(obs - p_i) < 0.5*SAFETY_DIST: |
| 263 | + collision_counter[episode, s-1] = 1 |
| 264 | + print("Collision detected with obstacle!") |
| 265 | + failed = True |
| 266 | + |
| 267 | + |
| 268 | + img_i = np.concatenate((img_i, img_neighs), 0) |
| 269 | + img_i = np.concatenate((img_i, img_obs), 0) |
| 270 | + # print("img_i shape: ", img_i.shape) |
| 271 | + |
| 272 | + img_in = torch.from_numpy(img_i).unsqueeze(0)#.unsqueeze(0) |
| 273 | + img_in = img_in.to(torch.float).to(device) |
| 274 | + vel_i = model(img_in) * resolution |
| 275 | + vel_i = vel_i.squeeze(0) |
| 276 | + vel_i = vel_i.cpu().detach().numpy() |
| 277 | + |
| 278 | + # CBF |
| 279 | + if USE_CBF: |
| 280 | + local_pts = neighs - p_i |
| 281 | + constraints = [] |
| 282 | + for n in local_pts: |
| 283 | + h = np.linalg.norm(n)**2 - SAFETY_DIST**2 |
| 284 | + A_cbf = 2*n |
| 285 | + b_cbf = GAMMA * h |
| 286 | + constraints.append({'type': 'ineq', 'fun': lambda u: safety_constraint(u, A_cbf, b_cbf)}) |
| 287 | + |
| 288 | + local_obs = obstacles - p_i |
| 289 | + for obs in local_obs: |
| 290 | + h = np.linalg.norm(obs)**2 - (2*SAFETY_DIST)**2 |
| 291 | + A_cbf = 2*obs |
| 292 | + b_cbf = GAMMA * h |
| 293 | + constraints.append({'type': 'ineq', 'fun': lambda u: safety_constraint(u, A_cbf, b_cbf)}) |
| 294 | + # print("vdes: ", vel_i) |
| 295 | + # print("Acbf: ", A_cbf) |
| 296 | + # print("b_cbf: ", b_cbf) |
| 297 | + # print("h: ", h) |
| 298 | + obj = lambda u: objective_function(u-vel_i) |
| 299 | + res = minimize(obj, vel_i, constraints=constraints, bounds=[(-vmax, vmax), (-vmax, vmax)]) |
| 300 | + vel_i = res.x |
| 301 | + |
| 302 | + # print(f"Velocity of robot {idx}: {vel_i}") |
| 303 | + # print("points[idx] shape: ", points[idx, :].shape) |
| 304 | + points[idx, 0] = points[idx, 0] + vel_i[0]*dt |
| 305 | + points[idx, 1] = points[idx, 1] + vel_i[1]*dt |
| 306 | + |
| 307 | + if np.linalg.norm(vel_i) > 0.15: |
| 308 | + all_stopped = False |
| 309 | + |
| 310 | + robots_hist = np.concatenate((robots_hist, np.expand_dims(points, 0))) |
| 311 | + eta = num / denom |
| 312 | + # print("Efficiency: ", eta) |
| 313 | + eval_data[episode, s-1] = eta[0] |
| 314 | + |
| 315 | + |
| 316 | + |
| 317 | + if all_stopped: |
| 318 | + break |
| 319 | + |
| 320 | + path = Path().resolve() |
| 321 | + res_path = path / "results" |
| 322 | + |
| 323 | + if SAVE_POS: |
| 324 | + np.save(res_path/"eta16.npy", eval_data) |
| 325 | + np.save(res_path/"collisions16.npy", collision_counter) |
| 326 | + |
| 327 | + if not failed: |
| 328 | + np.save(res_path/"elle_pos.npy", robots_hist) |
| 329 | + |
| 330 | + |
| 331 | +for i in range(ROBOTS_NUM): |
| 332 | + ax.plot(robots_hist[:, i, 0], robots_hist[:, i, 1]) |
| 333 | + ax.scatter(robots_hist[-1, i, 0], robots_hist[-1, i, 1]) |
| 334 | + |
| 335 | + |
| 336 | +plt.show() |
| 337 | + |
| 338 | + |
| 339 | + |
0 commit comments