├── .idea ├── .gitignore ├── AgentsMotionSimulation.iml ├── csv-plugin.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml └── modules.xml ├── Done └── plgsim.py ├── ToDo ├── agent.py ├── main.py └── polygon.py ├── pics ├── 图2、高度错开性避免碰撞策略流程图.png └── 基于自组织的均匀多边形编队仿真.gif └── readme.md /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/AgentsMotionSimulation.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/csv-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Done/plgsim.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import time 3 | 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | from matplotlib import animation 7 | 8 | """无人机设置""" 9 | n = 8 # 个数 10 | 11 | """多边形参数设置""" 12 | m = 5 # 边数 13 | r = 30 # 多边形半径 14 | cx, cy, cz = 50, 50, 200 # 中心位置 15 | 16 | """高度错开收敛所需参数""" 17 | hv = 2 # 高度错开收敛时,纵轴运动速度 18 | hgap = 380 / n 19 | 20 | """抵达边界所需参数""" 21 | step_len = 0.3 # 移动步长 22 | Detect_Error = 0.1 # 允许误差 23 | 24 | """边界目标位置调整所需参数""" 25 | max_n = (n - m) / m if ((n - m) / m) % 1 == 0 else int((n - m) / m) + 1 26 | unit_gap = 2 * r * np.sin(np.pi / m) / (max_n + 1) # 2 * R * np.sin(np.pi/n)是边界长度, unit_gap是调整单位距离 27 | 28 | """均匀化参数""" 29 | Stop_Gap_NUM = 3 # 齿链上,若所有结点两两之间仅仅相小于3个节点,则判断为编队完成 30 | Move_Gap_Num = 20 # 20 * 0.01 =0.2 齿链上,步长转换成节点数 31 | 32 | """随机生成无人机初始位置,记录为Point_List""" 33 | pxs = np.random.randint(1, 100, n) 34 | pys = np.random.randint(1, 100, n) 35 | pzs = [0 for i in range(n)] 36 | Point_List = [[pxs[i], pys[i], 0] for i in range(n)] 37 | 38 | """ 计算多边形顶点位置""" 39 | Vertexs_List = [[50 + r * np.sin(i * 2 * np.pi / m), 50 + r * np.cos(i * 2 * np.pi / m), cz] for i in range(1, m + 1)] 40 | verxs, verys, verzs = [v[0] for v in Vertexs_List], [v[1] for v in Vertexs_List], [v[2] for v in Vertexs_List] 41 | verxs.append(Vertexs_List[0][0]) 42 | verys.append(Vertexs_List[0][1]) 43 | verzs.append(Vertexs_List[0][2]) 44 | 45 | """初始化图像""" 46 | fig = plt.figure() 47 | ax = plt.gca(projection='3d') 48 | ax.set_xlim(0, 100) 49 | ax.set_xlabel('X') 50 | ax.set_ylim(0, 100) 51 | ax.set_ylabel('Y') 52 | ax.set_zlim(0, 400) 53 | ax.set_zlabel('Z') 54 | sc = ax.scatter3D(pxs, pys, pzs, color='r', alpha=0.7) 55 | ax.plot(verxs, verys, verzs, color='black', linestyle=':') 56 | 57 | """均匀化时,链表化节点参数""" 58 | d = 2 * r * np.sin(np.pi / m) 59 | num = int(d / 0.01) 60 | tooth_distance = d / num 61 | Vertexs_Tooth_Chain = [] 62 | 63 | """链表化多边形""" 64 | for i in range(0, m): 65 | Vertexs_Tooth_Chain.append(Vertexs_List[i]) 66 | base_pos = copy.deepcopy(Vertexs_List[i]) 67 | if i == len(Vertexs_List) - 1: 68 | nxt = Vertexs_List[0] 69 | else: 70 | nxt = Vertexs_List[i + 1] 71 | x = np.array([nxt[0] - Vertexs_List[i][0], nxt[1] - Vertexs_List[i][1]]) # 方向向量 72 | y = np.array([1, 0]) # x轴方向 73 | xmd = np.sqrt(x.dot(x)) # x.dot(x) 点乘自己,相当于向量模平方 74 | ymd = np.sqrt(y.dot(y)) 75 | cos_angle = x.dot(y) / (xmd * ymd) 76 | angle = np.arccos(cos_angle) 77 | if x[0] >= 0 and x[1] >= 0: 78 | for j in range(1, num): 79 | a = base_pos[0] + j * tooth_distance * abs(np.cos(angle)) 80 | b = base_pos[1] + j * tooth_distance * abs(np.sin(angle)) 81 | Vertexs_Tooth_Chain.append([a, b, cz]) 82 | elif x[0] <= 0 and x[1] >= 0: 83 | for j in range(1, num): 84 | a = base_pos[0] - j * tooth_distance * abs(np.cos(angle)) 85 | b = base_pos[1] + j * tooth_distance * abs(np.sin(angle)) 86 | Vertexs_Tooth_Chain.append([a, b, cz]) 87 | elif x[0] <= 0 and x[1] <= 0: 88 | for j in range(1, num): 89 | a = base_pos[0] - j * tooth_distance * abs(np.cos(angle)) 90 | b = base_pos[1] - j * tooth_distance * abs(np.sin(angle)) 91 | Vertexs_Tooth_Chain.append([a, b, cz]) 92 | else: 93 | for j in range(1, num): 94 | a = base_pos[0] + j * tooth_distance * abs(np.cos(angle)) 95 | b = base_pos[1] - j * tooth_distance * abs(np.sin(angle)) 96 | Vertexs_Tooth_Chain.append([a, b, cz]) 97 | 98 | """------------------------分割线---------------------------------------------""" 99 | """由于智能体策略相对复杂,一个类写下所有状态下相应决策,代码过长,便按照当前状态的不同策略拆分成两个类""" 100 | 101 | 102 | class Point_1(): 103 | """第一阶段智能体模型表示: 高度错开 + 移动到多边形上""" 104 | 105 | def __init__(self, id): 106 | self.id = id 107 | self.random = np.random.random() # 随机数,用于高度错开 108 | self.aim_hight = None # 当前阶段运动目标位置 109 | self.aim_adjusted = None # 多边形顶点位置被占领后,重新确定的目标 110 | self.max_n = max_n 111 | 112 | def decide0(self): 113 | """抵达目标高度""" 114 | my_pos = copy.deepcopy(Point_List[self.id]) 115 | if abs(cz - my_pos[2]) < hv: 116 | Point_List[self.id][2] = cz 117 | else: 118 | aim = my_pos[2] + hv 119 | Point_List[self.id][2] = aim 120 | 121 | def decide1(self): 122 | """高度错开""" 123 | if self.aim_hight == None: 124 | li1 = [[Obj_List[i].send_random(), i] for i in range(n)] 125 | 126 | li1.sort(key=lambda x: x[0]) 127 | my_index = li1.index([self.random, self.id]) 128 | self.aim_hight = 10 + (my_index + 1) * hgap # 因为my_index从0 开始取 129 | # 有目标高度后开始更新位置 130 | my_pos = copy.deepcopy(Point_List[self.id]) 131 | if abs(my_pos[2] - self.aim_hight) < hv: 132 | Point_List[self.id][2] = self.aim_hight 133 | elif my_pos[2] < self.aim_hight: 134 | Point_List[self.id][2] = my_pos[2] + hv 135 | else: 136 | Point_List[self.id][2] = my_pos[2] - hv 137 | 138 | def decide2(self, list=copy.deepcopy(Vertexs_List)): 139 | """抵达多边形主要逻辑: 抵达顶点 + 顶点被占领后调整目标位置""" 140 | if self.aim_adjusted == None: 141 | nearest = self.detect_nearest(list) # 检测最近顶点 142 | idx = self.occupy(nearest) 143 | if idx == self.id: 144 | self.update(nearest) 145 | elif idx == None: 146 | self.update(nearest) 147 | else: 148 | self.aim_adjusted = self.adjust(idx) 149 | if self.aim_adjusted: # 调整成功 150 | self.update(self.aim_adjusted) 151 | else: 152 | list2 = copy.deepcopy(list) 153 | list2.remove(nearest) 154 | return self.decide2(list2) 155 | else: 156 | self.update(self.aim_adjusted) 157 | 158 | def send_random(self): 159 | return self.random 160 | 161 | def detect_nearest(self, list): 162 | """检测最近的多边形顶点""" 163 | init_distance = self.distance_calculate(Point_List[self.id], list[0]) 164 | count, i = 0, 0 165 | for each in list: 166 | D = self.distance_calculate(Point_List[self.id], each) 167 | if D < init_distance: 168 | init_distance = D 169 | count = i 170 | i += 1 171 | return list[count] 172 | 173 | def distance_calculate(self, A, B): # [1,1,?],[2,2,?] 得1.4142135623730951 174 | return pow(pow(abs(A[0] - B[0]), 2) + pow(abs(A[1] - B[1]), 2), 0.5) 175 | 176 | def occupy(self, nearest): 177 | """检测是谁占领的该多边形顶点,返回其id""" 178 | for each in Point_List: 179 | d = self.distance_calculate(each, nearest) 180 | if d < Detect_Error: 181 | id = Point_List.index(each) 182 | return id 183 | return None 184 | 185 | def update(self, aim): 186 | """朝着目标位置运动,更新坐标""" 187 | self_pot = copy.deepcopy(Point_List[self.id]) 188 | x = np.array([aim[0] - self_pot[0], aim[1] - self_pot[1]]) # 方向向量 189 | y = np.array([1, 0]) # x轴方向 190 | xmd = np.sqrt(x.dot(x)) # x.dot(x) 点乘自己,相当于向量模平方 191 | ymd = np.sqrt(y.dot(y)) 192 | if xmd > step_len: 193 | cos_angle = x.dot(y) / (xmd * ymd) 194 | angle = np.arccos(cos_angle) # 0.....pi 195 | if x[0] >= 0 and x[1] >= 0: 196 | self_pot[0] = self_pot[0] + step_len * abs(np.cos(angle)) 197 | self_pot[1] = self_pot[1] + step_len * np.sin(angle) 198 | elif x[0] <= 0 and x[1] >= 0: 199 | self_pot[0] = self_pot[0] - step_len * abs(np.cos(angle)) 200 | self_pot[1] = self_pot[1] + step_len * np.sin(angle) 201 | elif x[0] <= 0 and x[1] <= 0: 202 | self_pot[0] = self_pot[0] - step_len * abs(np.cos(angle)) 203 | self_pot[1] = self_pot[1] - step_len * np.sin(angle) 204 | else: 205 | self_pot[0] = self_pot[0] + step_len * abs(np.cos(angle)) 206 | self_pot[1] = self_pot[1] - step_len * np.sin(angle) 207 | Point_List[self.id] = self_pot 208 | else: 209 | Point_List[self.id][0] = aim[0] 210 | Point_List[self.id][1] = aim[1] 211 | 212 | def adjust(self, idx): 213 | """多边形顶点被占领后,向占领者询问自己的目标位置""" 214 | order = Obj_List[idx].send_order() 215 | if order == None: return None 216 | for each in Vertexs_List: 217 | d = self.distance_calculate(each, Point_List[idx]) 218 | if d < Detect_Error: 219 | identity = Vertexs_List.index(each) 220 | aim = copy.deepcopy(Vertexs_List[identity]) 221 | count = self.max_n - order # 1,2 222 | if identity == 0: 223 | pre = Vertexs_List[-1] 224 | nxt = Vertexs_List[identity + 1] 225 | elif identity == len(Vertexs_List) - 1: 226 | pre = Vertexs_List[identity - 1] 227 | nxt = Vertexs_List[0] 228 | else: 229 | pre = Vertexs_List[identity - 1] 230 | nxt = Vertexs_List[identity + 1] 231 | 232 | if count % 2 == 0: # 偶数顺时针 233 | x = np.array([pre[0] - aim[0], pre[1] - aim[1]]) # 方向向量 234 | else: # 奇数逆时针 235 | x = np.array([nxt[0] - aim[0], nxt[1] - aim[1]]) # 方向向量 236 | count2 = count / 2 if count % 2 == 0 else int(count / 2) + 1 237 | y = np.array([1, 0]) # x轴方向 238 | xmd = np.sqrt(x.dot(x)) # x.dot(x) 点乘自己,相当于向量模平方 239 | ymd = np.sqrt(y.dot(y)) 240 | cos_angle = x.dot(y) / (xmd * ymd) 241 | angle = np.arccos(cos_angle) 242 | if x[0] >= 0 and x[1] >= 0: 243 | 244 | aim[0] = aim[0] + count2 * unit_gap * abs(np.cos(angle)) 245 | aim[1] = aim[1] + count2 * unit_gap * np.sin(angle) 246 | elif x[0] <= 0 and x[1] >= 0: 247 | aim[0] = aim[0] - count2 * unit_gap * abs(np.cos(angle)) 248 | aim[1] = aim[1] + count2 * unit_gap * np.sin(angle) 249 | elif x[0] <= 0 and x[1] <= 0: 250 | aim[0] = aim[0] - count2 * unit_gap * abs(np.cos(angle)) 251 | aim[1] = aim[1] - count2 * unit_gap * np.sin(angle) 252 | else: 253 | aim[0] = aim[0] + count2 * unit_gap * abs(np.cos(angle)) 254 | aim[1] = aim[1] - count2 * unit_gap * np.sin(angle) 255 | return aim 256 | 257 | def send_order(self): 258 | if self.max_n <= 0: 259 | return None # 告诉询问着索要失败 260 | else: 261 | self.max_n -= 1 262 | return self.max_n 263 | 264 | 265 | """------------------------分割线---------------------------------------------""" 266 | 267 | 268 | class Point2(): 269 | """第二阶段智能体模型: 均匀化分布 + 高度收敛""" 270 | next_dis = len(Vertexs_Tooth_Chain) + 1 271 | 272 | def __init__(self, id): 273 | """初始化,绑定前后智能体关系""" 274 | self.id = id 275 | my_id = PointAddIndexSorted.index(Point_addIndex[self.id]) 276 | if my_id == 0: 277 | self.pre_id = Point_addIndex.index(PointAddIndexSorted[n - 1]) 278 | self.next_id = Point_addIndex.index(PointAddIndexSorted[1]) 279 | elif my_id == n - 1: 280 | self.next_id = Point_addIndex.index(PointAddIndexSorted[0]) 281 | self.pre_id = Point_addIndex.index(PointAddIndexSorted[n - 2]) 282 | else: 283 | self.pre_id = Point_addIndex.index(PointAddIndexSorted[my_id - 1]) 284 | self.next_id = Point_addIndex.index(PointAddIndexSorted[my_id + 1]) 285 | 286 | def decide1(self): 287 | """均匀化,往前后节点的中间位置移动; 再有拐角的多边形上,怎么计算中间位置? 288 | 答案: 把多边形等距划开,链表化,看成一条尺链,计算出每一个节点的坐标, 289 | 计算中间位置,就只需要计算所需要移动的节点的索引 290 | 291 | 补充:这只是编程实现手法,现实情况,一个agent,定位前后agent位置, 292 | 加上多边形位置已知,往其中点移动使可实现的,所以不矛盾。 293 | """ 294 | pre_chain_index = Point_addIndex[self.pre_id][3] 295 | next_chain_index = Point_addIndex[self.next_id][3] 296 | self_chain_index = Point_addIndex[self.id][3] 297 | if pre_chain_index < next_chain_index: # 正常情况 298 | self.next_dis = next_chain_index - self_chain_index 299 | mmid = ((next_chain_index + pre_chain_index) / 2 + self_chain_index) / 2 300 | else: 301 | self.next_dis = next_chain_index - self_chain_index + len(Vertexs_Tooth_Chain) 302 | if self.next_dis >= len(Vertexs_Tooth_Chain): 303 | self.next_dis -= len(Vertexs_Tooth_Chain) 304 | mmid = ((next_chain_index + len(Vertexs_Tooth_Chain) + pre_chain_index) / 2 + self_chain_index) / 2 305 | 306 | if abs(mmid - self_chain_index) <= Stop_Gap_NUM: 307 | if mmid % 1 == 0: 308 | self.move(int(mmid)) 309 | elif self_chain_index > mmid: # 在目标顺时针方向 310 | self.move(int(mmid) + 1) 311 | else: 312 | self.move(int(mmid)) 313 | elif mmid > self_chain_index: 314 | self.move(self_chain_index + Move_Gap_Num) 315 | else: 316 | self.move(self_chain_index - Move_Gap_Num) 317 | 318 | def decide2(self): 319 | """高度收敛""" 320 | my_hight = Point_List[self.id][2] 321 | if abs(my_hight - cz) < hv: 322 | Point_List[self.id][2] = cz 323 | elif my_hight > cz: 324 | Point_List[self.id][2] = my_hight - hv 325 | else: 326 | Point_List[self.id][2] = my_hight + hv 327 | 328 | def move(self, aim): 329 | if aim >= len(Vertexs_Tooth_Chain): aim -= len(Vertexs_Tooth_Chain) 330 | li = copy.deepcopy(Vertexs_Tooth_Chain[aim]) 331 | Point_List[self.id][0] = copy.deepcopy(Vertexs_Tooth_Chain[aim])[0] 332 | Point_List[self.id][1] = copy.deepcopy(Vertexs_Tooth_Chain[aim])[1] 333 | li.append(aim) 334 | Point_addIndex[self.id] = li 335 | 336 | 337 | """实例化多个对象""" 338 | Obj_List = [Point_1(i) for i in range(0, n)] # 返回生成的n个对象的列表 339 | 340 | """排序后带索引的节点列表""" 341 | PointAddIndexSorted = [] 342 | 343 | """---------------------------分割线------------------------""" 344 | 345 | 346 | def gen(): 347 | """仿真数据源""" 348 | global PointAddIndexSorted 349 | global Point_addIndex 350 | global Vertexs_Tooth_Chain 351 | state_flag = 0 352 | Point_List2 = [] # 用于比较 353 | init_flag = 0 354 | 355 | def distance_calculate_2D(A, B): 356 | return pow(pow(A[0] - B[0], 2) + pow(A[1] - B[1], 2), 0.5) 357 | 358 | while True: 359 | JudgeList = [] 360 | if state_flag == 0: 361 | for i in range(n): 362 | Obj_List[i].decide0() 363 | elif state_flag == 1: 364 | for i in range(n): 365 | Obj_List[i].decide1() 366 | elif state_flag == 2: 367 | for i in range(n): 368 | Obj_List[i].decide2() 369 | elif state_flag == 3: 370 | 371 | if init_flag == 0: 372 | Point_addIndex = [] 373 | for i in Point_List: 374 | for j in Vertexs_Tooth_Chain: 375 | if distance_calculate_2D(i, j) <= tooth_distance / 2: 376 | li = copy.deepcopy(i) 377 | li.append(Vertexs_Tooth_Chain.index(j)) 378 | Point_addIndex.append(li) 379 | break 380 | 381 | PointAddIndexSorted = copy.deepcopy(Point_addIndex) 382 | PointAddIndexSorted.sort(key=lambda x: x[3]) 383 | Obj_List2 = [Point2(i) for i in range(0, n)] # 返回生成的n个对象的列表 384 | init_flag = 1 385 | 386 | for i in range(n): 387 | Obj_List2[i].decide1() 388 | JudgeList.append(Obj_List2[i].next_dis) 389 | if judge(JudgeList): 390 | state_flag += 1 391 | elif state_flag == 4: 392 | for i in range(n): 393 | Obj_List2[i].decide2() 394 | 395 | else: 396 | print("最终编队完成, 10s后退出") 397 | time.sleep(10) 398 | exit() 399 | if Point_List2 == Point_List: 400 | state_flag += 1 401 | else: 402 | pass 403 | Point_List2 = copy.deepcopy(Point_List) 404 | yield Point_List 405 | 406 | 407 | def judge(arg_list): 408 | d = len(Vertexs_Tooth_Chain) / n 409 | for each in arg_list: 410 | if abs(each - d) > 100: 411 | return False 412 | return True 413 | 414 | 415 | def update(arg_list): 416 | """刷新动画""" 417 | li = np.array(arg_list) 418 | sx, sy, sz = [], [], [] 419 | for each in arg_list: 420 | sx.append(each[0]) 421 | sy.append(each[1]) 422 | sz.append(each[2]) 423 | sc.set_offsets(li[:, :-1]) 424 | sc.set_3d_properties(sz, zdir='z') 425 | return sc 426 | 427 | 428 | ani = animation.FuncAnimation(fig, update, frames=gen, interval=1) 429 | plt.show() 430 | -------------------------------------------------------------------------------- /ToDo/agent.py: -------------------------------------------------------------------------------- 1 | import random 2 | import numpy as np 3 | 4 | class Agent: 5 | def __init__(self, id, x, y, z): 6 | self.id = id 7 | self.x = x 8 | self.y = y 9 | self.z = z 10 | self.v_1 = 5; self.error_1 = 2 11 | self.v_2 = 3; self.error_2 = 1 12 | 13 | 14 | self.rp = 0 # 高度错开随机参数 15 | self.tph = 0 # 高度错临时高度 16 | self.state = 0 17 | self.isStop = 0 18 | self.agents = None 19 | self.pgn = None 20 | self.flag_1 = False; self.flag_2 = False; self.flag_3 = False 21 | self.flag_4 = False; self.flag_5 = False; self.flag_6 = False 22 | self.edge_pos_arr = [] 23 | 24 | # 抵达多边形参数 25 | self.state_2 = 0 26 | 27 | def move_in_longitude_1(self): 28 | """爬升至多边形设定高度""" 29 | if self.flag_1: 30 | return self.gen_random_p() 31 | d = self.pgn.h - self.z 32 | if abs(d) < self.error_1: 33 | self.z = self.pgn.h 34 | self.flag_1 = True 35 | else: 36 | self.z += self.v_1 if d > 0 else -self.v_1 37 | 38 | def gen_random_p(self): 39 | """生成排序所用随机数""" 40 | if self.flag_2: 41 | return self.wait_for_height_sort() 42 | self.rp = random.random() 43 | self.flag_2 = True 44 | 45 | def wait_for_height_sort(self): 46 | """等待所有智能体进入高度排序状态""" 47 | self.flag_5 = True 48 | if self.flag_6: return self.gen_temp_height() 49 | 50 | if self.check_all_flag_5(): 51 | self.set_all_flag_6() 52 | 53 | 54 | def gen_temp_height(self): 55 | if self.flag_3: 56 | return self.move_in_longitude_2() 57 | sagents = sorted(self.agents, key=lambda x:x.rp) 58 | idx = sagents.index(self.agents[self.id]) 59 | self.tph = self.pgn.h + (idx - len(self.agents)//2) * (350/len(self.agents)) 60 | self.flag_3 = True 61 | 62 | def move_in_longitude_2(self): 63 | if self.flag_4: return 64 | d = self.tph - self.z 65 | if abs(d) < self.error: 66 | self.z = self.tph 67 | self.flag_4 = True 68 | if self.check_all_flag_4(): 69 | self.set_all_agents_state(1) 70 | else: 71 | self.z += self.v if d > 0 else -self.v 72 | 73 | def move_to_vertex(self): 74 | ix = self.cal_nearest_vertex() 75 | if not ix: 76 | return self.move_to_edge() 77 | 78 | 79 | 80 | def move_to_edge(self): 81 | """移动到多边性边上""" 82 | pass 83 | 84 | def move_on_edge(self): 85 | """多边形上移动""" 86 | pass 87 | 88 | def update_forward(self, aim): 89 | x = np.array([aim[0] - self.x, aim[1] - self.y]) # 方向向量 90 | y = np.array([1, 0]) # x轴方向 91 | mx = np.sqrt(x.dot(x)) # x.dot(x) 点乘自己,相当于向量模平方 92 | my = np.sqrt(y.dot(y)) 93 | if mx > self.v_2: 94 | cos_angle = x.dot(y) / (mx * my) 95 | angle = np.arccos(cos_angle) # 0.....pi 96 | if x[0] >= 0 and x[1] >= 0: 97 | self.x = self.x + self.v_2 * abs(np.cos(angle)) 98 | self.y = self.y + self.v_2 * np.sin(angle) 99 | elif x[0] <= 0 and x[1] >= 0: 100 | self.x = self.x - self.v_2 * abs(np.cos(angle)) 101 | self.y = self.y + self.v_2 * np.sin(angle) 102 | elif x[0] <= 0 and x[1] <= 0: 103 | self.x = self.x - self.v_2 * abs(np.cos(angle)) 104 | self.y = self.y - self.v_2 * np.sin(angle) 105 | else: 106 | self.x = self.x + self.v_2 * abs(np.cos(angle)) 107 | self.y = self.y - self.v_2 * np.sin(angle) 108 | else: 109 | self.x = aim[0] 110 | self.y = aim[1] 111 | 112 | 113 | 114 | def limited_state_machine(self): 115 | """运动决策有限状态机""" 116 | if self.isStop: return 117 | """0~3表示三种运动决策""" 118 | if self.state == 0: 119 | self.move_in_longitude_1() 120 | elif self.state == 1: 121 | self.move_to_vertex() 122 | elif self.state == 2: 123 | self.move_to_edge() 124 | elif self.state == 3: 125 | self.move_on_edge() 126 | 127 | def get_id(self): 128 | return self.id 129 | 130 | def get_pos(self): 131 | return self.x, self.y, self.z 132 | 133 | def check_all_flag_2(self): 134 | for a in self.agents: 135 | if a.flag_2 == False: 136 | return False 137 | return True 138 | 139 | def check_all_flag_4(self): 140 | for a in self.agents: 141 | if a.flag_4 == False: 142 | return False 143 | return True 144 | 145 | def set_all_agents_state(self, n): 146 | for a in self.agents: 147 | a.state = n 148 | 149 | def check_all_flag_5(self): 150 | for a in self.agents: 151 | if a.flag_5 == False: 152 | return False 153 | return True 154 | 155 | def set_all_flag_6(self): 156 | for a in self.agents: 157 | a.flag_6 = True 158 | 159 | def cal_distance_2d(self, a, b): 160 | return (a[0]-b[0])**2 + (a[1]-b[1])**2 161 | 162 | def cal_nearest_vertex(self): 163 | temp = [] 164 | for i in range(len(self.pgn.vertexs)): 165 | d = self.cal_distance_2d([self.pgn.vertexs[i][0],self.pgn.vertexs[i][1]], \ 166 | [self.x,self.y]) 167 | temp.append([i,d]) 168 | temp.sort(temp, key=lambda x:x[1]) 169 | for cb in temp: 170 | if not self.pgn.isOccupied[cb[0]]: 171 | return cb[0] 172 | else: 173 | return False 174 | 175 | def inquire_edge_pos(self, id): 176 | return [] 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /ToDo/main.py: -------------------------------------------------------------------------------- 1 | from polygon import Polygon 2 | from agent import Agent 3 | from random import randint, shuffle 4 | import matplotlib.pyplot as plt 5 | from matplotlib import animation 6 | import numpy as np 7 | 8 | 9 | class MultiMotionSimulate: 10 | def __init__(self, n, m, r, h, cx, cy): 11 | self.pgn = Polygon(m, r, h, cx, cy) 12 | 13 | self.n = n # 初始化多n个智能体 14 | self.s = [i for i in range(n)] # 随机启动顺序 15 | xd, xh = 0, 100 16 | yd, yh = 0, 100 17 | self.agents = [Agent(i, randint(xd, xh), randint(yd, yh), 0) for i in range(n)] 18 | 19 | self.fig, self.ax, self.sc = None, None, None 20 | self.init_show() 21 | 22 | for a in self.agents: 23 | a.pgn = self.pgn 24 | a.agents = self.agents 25 | 26 | self.isStop = False 27 | 28 | def init_show(self): 29 | """初始画布,""" 30 | self.fig = plt.figure() 31 | self.ax = plt.gca(projection='3d') 32 | self.ax.set_xlim(0, 100) 33 | self.ax.set_xlabel('X') 34 | self.ax.set_ylim(0, 100) 35 | self.ax.set_ylabel('Y') 36 | self.ax.set_zlim(0, 400) 37 | self.ax.set_zlabel('Z') 38 | vxs, vys, vzs = [v[0] for v in self.pgn.vertexs], [v[1] for v in self.pgn.vertexs], [self.pgn.h] * len( 39 | self.pgn.vertexs) # 多边形顶点坐标 40 | vxs.append(vxs[0]); 41 | vys.append(vys[0]); 42 | vzs.append(vzs[0]) # 线条首尾相连 43 | self.ax.plot(vxs, vys, vzs, color='black', linestyle=':') 44 | pxs, pys, pzs = [p.x for p in self.agents], [p.y for p in self.agents], [p.z for p in self.agents] # agent坐标 45 | self.sc = self.ax.scatter3D(pxs, pys, pzs, color='r', alpha=0.7) 46 | 47 | def observe(self): 48 | """观察所有agents的状态""" 49 | while not self.isStop: 50 | shuffle(self.s) 51 | for c in self.s: 52 | c = randint(0, self.n - 1) 53 | self.agents[c].limited_state_machine() 54 | yield None 55 | 56 | def deal_3d(self, agents): 57 | """3d画图数据设置""" 58 | pzs = [p.z for p in self.agents] 59 | temp = np.array([[p.x, p.y, p.z] for p in self.agents]) 60 | self.sc.set_offsets(temp[:, :-1]) 61 | self.sc.set_3d_properties(pzs, zdir='z') 62 | return self.sc 63 | 64 | def show(self): 65 | """画图""" 66 | ani = animation.FuncAnimation(self.fig, func = self.deal_3d, frames = self.observe, interval= 1) 67 | plt.show() 68 | 69 | 70 | if __name__ == '__main__': 71 | # 初始化目标多边形,边形为5,半径30,高度200, 中心坐标为50,50 72 | mms = MultiMotionSimulate(10, 5, 30, 200, 50, 50) 73 | mms.show() 74 | -------------------------------------------------------------------------------- /ToDo/polygon.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Polygon: 5 | def __init__(self, m, r, h, cx = 50, cy = 50) -> object: 6 | self.m = m 7 | self.r = r 8 | self.h = h 9 | self.cx = cx 10 | self.cy = cy 11 | self.vertexs = [] 12 | self.gen_ploygen_vertexs() 13 | self.isOccupied = [False for i in range(m)] 14 | 15 | def gen_ploygen_vertexs(self): 16 | for i in range(1, self.m + 1): 17 | x = self.cx + self.r * np.sin(i * 2 * np.pi / self.m) 18 | y = self.cy + self.r * np.cos(i * 2 * np.pi / self.m) 19 | self.vertexs.append([x, y]) 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pics/图2、高度错开性避免碰撞策略流程图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colaforced/AgentsMotionSimulation/b80b15146d47f6567596e4b457d75c2b63193230/pics/图2、高度错开性避免碰撞策略流程图.png -------------------------------------------------------------------------------- /pics/基于自组织的均匀多边形编队仿真.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colaforced/AgentsMotionSimulation/b80b15146d47f6567596e4b457d75c2b63193230/pics/基于自组织的均匀多边形编队仿真.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 基于自组织的多无人机编队算法设计 2 | ## 一、基于自组织的多无人均匀多边形编队控制 3 | ### 1. 基于自组织的多无人机编队问题详解 4 | #### 1.1 初始配置、目标配置和无人机模型定义 5 | **1.1.1、初始配置、目标配置和无人机模型定义** 6 | 7 | 1) 我们把无人机考虑成一个具有一定体积的智能体,不考虑其具体动力学特性, 8 | 仅仅将其视为一个具有一定体积的、可自主移动的圆球。智能体之间不能接 9 | 触、碰撞。 10 | 2) 智能体是同质的、可互换的、匿名的,所有个体性质都是相同的,唯一的不 11 | 同只有其所处的环境不同或者说其运动状态不同,除此之外,没有任何特殊 12 | 性,匿名性也导致智能体无法依靠 ID 分辨其他个体。且个体具有无记忆性, 13 | 无法记录上一步的行为或者决策。 14 | 3) 智能体拥有相同的笛卡尔坐标系统,且目标多边形位置是固定已知的,没个 15 | 无人机均有记录均匀多边形的几个顶点位置。 16 | 4) 忽略通信细节,不考虑通信时延和定位精度问题,每个智能体均可准确知道 17 | 其他任何个体位置,实际上仿真实验中只用到局部其他个体的位置信息。 18 | 5) 智能体有两种状态,静止和激活状态,静止即为保持位置不变化,但仍然会 19 | 与外界保持沟通;激活状态则是智能体主动判断自身所处位置,根据环境做 20 | 出决策,然后执行决策。仿真时,智能体默认保持静止,激活是异步进行的, 21 | 同一时间只考虑激活一个个体,激活个体随机选择,依次达到对现实运行的 22 | 仿真。 23 | 24 | 25 | #### 多边形形成算法设计 26 | **1.2.1、多边形形成控制算法约束条件** 27 | 28 | 算法设计之先,我们需要给出一些智能体运动约束条件: 29 | 1)智能体之间不能碰撞,更不能重合,设定智能体之间最小间距为 d。 30 | 2)本文所设计控制算法不涉及具体控制率,每个个体移动速度相同,表现在算法中则是每个个体每次激活时移动的距离小于等于 D。 31 | 32 | 33 | **步骤一 :抵达多边形** 34 | ~~~python 35 | """ 36 | Vertex_Position 表示多边形顶点坐标列表、detect 表示函数检测最近顶点 detect_occupy 表示 37 | 函数检测顶点是否被占领、move 表示函数移动、get 表示函数通信索要排队序号,adjust 表示目标 38 | """ 39 | 40 | def function1(Vertex_Position = Vertex_Position): 41 | nearest_point = detect(Vertex_Position) 42 | #检测最近多边形顶点位置 43 | if !detect_occupy(nearest_point): 44 | #如果检测到该顶点没被占领 45 | move(nearest_point) 46 | #移向目标顶点 47 | else: 48 | order = get(occupy_ID) 49 | #向占领的智能体索要当前排队序号 50 | if order: 51 | adjust_aim = adjust(order) 52 | #根据排队序号调整自己的目标位置 53 | else: 54 | Vertex_Position = Vertex_Position.remove(nearest_point) 55 | return functions(Vertex_Position) 56 | #索要排序号失败则移除顶点列表中的最近顶点, 57 | #并再次调用决策函数 58 | return None 59 | ~~~ 60 | 61 | **步骤二 :均匀化** 62 | ~~~python 63 | """ 64 | def function2(): 65 | pre_index ,next_index = detect_neighbor() 66 | #检测前后相邻个体在均匀多边形链表上的索引 67 | aim = ((pre_index+next_index)/2 + my_index)/2 68 | #aim 为前后相邻个体的中点与自身位置的中点 69 | move(aim) 70 | #往目标位置移动 71 | 72 | 注:detect_neighbor()表示函数检测多边形上前后相邻个体 73 | ~~~ 74 | 75 | ### 2 仿真结果 76 | 77 | 代码见 ``done/plgsim.py`` 78 | 由于本科时候太菜了,自己写的代码自己都不想去维护。 79 | 为了读者阅读方便,尝试重构代码(见``ToDo``), 80 | 基本框架已经搭起来了,但策略迁移工作量太多了, 81 | 又是毕业之际,暂时保留再做,感兴趣的小伙伴也也可以提出代码合并哈。 82 | 83 | ![](/pics/基于自组织的均匀多边形编队仿真.gif "基于自组织的均匀多边形编队仿真.gif") 84 | 85 | 86 | 87 | ### 3: 链接 88 | 1. https://blog.csdn.net/py431382/article/details/89854176?spm=1001.2014.3001.5501 89 | 2. https://www.bilibili.com/video/BV1PE41117m6?spm_id_from=333.999.0.0 90 | 91 | 92 | ## 二、基于自组织的多智能体追逐与合围 93 | 94 | 95 | https://www.bilibili.com/video/BV1PE41117DG 96 | 97 | 98 | 代码待整理上传 --------------------------------------------------------------------------------