Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
yangliu28
GitHub Repository: yangliu28/swarm_formation_sim
Path: blob/master/demo_2.py
104 views
1
# This second demo is a variation of first one, also shows how a robot swarm can autonomously
2
# choose a loop shape and form the shape. But the decision making and the role assignment are
3
# performed while the swarm is already in a loop shape. The role assignment method applies to
4
# loop network only, but has the advantage of using no message relay.
5
6
# input arguments:
7
# '-n': number of robots
8
# '--manual': manual mode, press ENTER to proceed between simulations
9
10
# Description:
11
# Starting dispersed in random positions, the swarm aggregates together to form a random loop.
12
# From here, the following steps will run repeatedly. The robots first make collective decision
13
# of which loop shape to form, then they perform role assignment to distribute target positions.
14
# At the same time of role assignment, the robots also adjust local shapes so the collective
15
# reshapes to the target loop shape.
16
17
# the simulations that run consecutively
18
# Simulation 1: aggregate together to form a random loop
19
# Simulation 2: consensus decision making for target loop shape
20
# Simulation 3: role assignment and loop reshape
21
22
# 04/28/2018
23
# Added a stretching process in simulation 3, before the loop reshapes to the target. Because
24
# the loop may get tangled if reshaping directly from previous loop formation.
25
26
27
from __future__ import print_function
28
import pygame
29
import sys, os, getopt, math
30
import numpy as np
31
import pickle
32
33
swarm_size = 30 # default swarm size
34
manual_mode = False # manually press enter key to proceed between simulations
35
36
# read command line options
37
try:
38
opts, args = getopt.getopt(sys.argv[1:], 'n:', ['manual'])
39
except getopt.GetoptError as err:
40
print(str(err))
41
sys.exit()
42
for opt,arg in opts:
43
if opt == '-n':
44
swarm_size = int(arg)
45
elif opt == '--manual':
46
manual_mode = True
47
48
# calculate world size and screen size
49
power_exponent = 1.3 # between 1.0 and 2.0
50
# the larger the parameter, the slower the windows grows with swarm size; vice versa
51
pixels_per_length = 50 # fixed
52
# calculate world_side_coef from a desired screen size for 30 robots
53
def cal_world_side_coef():
54
desired_screen_size = 400 # desired screen size for 30 robots
55
desired_world_size = float(desired_screen_size) / pixels_per_length
56
return desired_world_size / pow(30, 1/power_exponent)
57
world_side_coef = cal_world_side_coef()
58
world_side_length = world_side_coef * pow(swarm_size, 1/power_exponent)
59
world_size = (world_side_length, world_side_length) # square physical world
60
# screen size calculated from world size
61
screen_side_length = int(pixels_per_length * world_side_length)
62
screen_size = (screen_side_length, screen_side_length) # square display world
63
64
# formation configuration
65
comm_range = 0.65 # communication range in the world
66
desired_space_ratio = 0.8 # ratio of the desired space to the communication range
67
# should be larger than 1/1.414=0.71, to avoid connections crossing each other
68
desired_space = comm_range * desired_space_ratio
69
# deviate robot heading, so as to avoid robot travlling perpendicular to the walls
70
perp_thres = math.pi/18 # threshold, range from the perpendicular line
71
devia_angle = math.pi/9 # deviate these much angle from perpendicualr line
72
# consensus configuration
73
loop_folder = "loop-data2" # folder to store the loop shapes
74
shape_catalog = ["airplane", "circle", "cross", "goblet", "hand", "K", "lamp", "square",
75
"star", "triangle", "wrench"]
76
shape_quantity = len(shape_catalog) # the number of decisions
77
shape_decision = -1 # the index of chosen decision, in range(shape_quantity)
78
# also the index in shape_catalog
79
# variable to force shape to different choices, for video recording
80
force_shape_set = range(shape_quantity)
81
82
# robot properties
83
robot_poses = np.random.rand(swarm_size, 2) * world_side_length # initialize the robot poses
84
dist_table = np.zeros((swarm_size, swarm_size)) # distances between robots
85
conn_table = np.zeros((swarm_size, swarm_size)) # connections between robots
86
# 0 for disconnected, 1 for connected
87
conn_lists = [[] for i in range(swarm_size)] # lists of robots connected
88
# function for all simulations, update the distances and connections between the robots
89
def dist_conn_update():
90
global dist_table
91
global conn_table
92
global conn_lists
93
conn_lists = [[] for i in range(swarm_size)] # empty the lists
94
for i in range(swarm_size):
95
for j in range(i+1, swarm_size):
96
dist_temp = np.linalg.norm(robot_poses[i] - robot_poses[j])
97
dist_table[i,j] = dist_temp
98
dist_table[j,i] = dist_temp
99
if dist_temp > comm_range:
100
conn_table[i,j] = 0
101
conn_table[j,i] = 0
102
else:
103
conn_table[i,j] = 1
104
conn_table[j,i] = 1
105
conn_lists[i].append(j)
106
conn_lists[j].append(i)
107
dist_conn_update() # update the distances and connections
108
disp_poses = [] # display positions
109
# function for all simulations, update the display positions
110
def disp_poses_update():
111
global disp_poses
112
poses_temp = robot_poses / world_side_length
113
poses_temp[:,1] = 1.0 - poses_temp[:,1]
114
poses_temp = poses_temp * screen_side_length
115
disp_poses = poses_temp.astype(int) # convert to int and assign to disp_poses
116
disp_poses_update()
117
# deciding the seed robots, used in simulations with moving robots
118
seed_percentage = 0.1 # the percentage of seed robots in the swarm
119
seed_quantity = min(max(int(swarm_size*seed_percentage), 1), swarm_size)
120
# no smaller than 1, and no larger than swarm_size
121
robot_seeds = [False for i in range(swarm_size)] # whether a robot is a seed robot
122
# only seed robot can initialize the forming a new group
123
seed_list_temp = np.arange(swarm_size)
124
np.random.shuffle(seed_list_temp)
125
for i in seed_list_temp[:seed_quantity]:
126
robot_seeds[i] = True
127
128
# visualization configuration
129
color_white = (255,255,255)
130
color_black = (0,0,0)
131
color_grey = (128,128,128)
132
color_red = (255,0,0)
133
distinct_color_set = ((230,25,75), (60,180,75), (255,225,25), (0,130,200), (245,130,48),
134
(145,30,180), (70,240,240), (240,50,230), (210,245,60), (250,190,190),
135
(0,128,128), (230,190,255), (170,110,40), (128,0,0),
136
(170,255,195), (128,128,0), (0,0,128))
137
color_quantity = 17
138
robot_size = 5
139
robot_empty_width = 2
140
conn_width = 2
141
# sizes for consensus simulation
142
robot_size_consensus = 7
143
conn_width_thin_consensus = 2
144
conn_width_thick_consensus = 4
145
146
# set up the simulation window
147
pygame.init()
148
font = pygame.font.SysFont("Cabin", 12)
149
icon = pygame.image.load("icon_geometry_art.jpg")
150
pygame.display.set_icon(icon)
151
screen = pygame.display.set_mode(screen_size)
152
pygame.display.set_caption("Demo 2")
153
# draw the network
154
screen.fill(color_white)
155
for i in range(swarm_size):
156
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size,
157
robot_empty_width)
158
pygame.display.update()
159
160
# pause to check the network before the simulations, or for screen recording
161
raw_input("<Press Enter to continue>")
162
163
# function for simulation 1, find the closest robot to a host robot
164
# use global variable "dist_table"
165
def S1_closest_robot(robot_host, robot_neighbors):
166
# "robot_host": the robot to measure distance from
167
# "robot_neighbors": a list of robots to be compared with
168
robot_closest = robot_neighbors[0]
169
dist_closest = dist_table[robot_host,robot_closest]
170
for i in robot_neighbors[1:]:
171
dist_temp = dist_table[robot_host,i]
172
if dist_temp < dist_closest:
173
robot_closest = i
174
dist_closest = dist_temp
175
return robot_closest
176
177
# function for simulation 1, group robots by their group ids, and find the largest group
178
def S1_robot_grouping(robot_list, robot_group_ids, groups):
179
# the input list 'robot_list' should not be empty
180
groups_temp = {} # key is group id, value is list of robots
181
for i in robot_list:
182
group_id_temp = robot_group_ids[i]
183
if group_id_temp not in groups_temp.keys():
184
groups_temp[group_id_temp] = [i]
185
else:
186
groups_temp[group_id_temp].append(i)
187
group_id_max = -1 # the group with most members
188
# regardless of only one group or multiple groups in groups_temp
189
if len(groups_temp.keys()) > 1: # there is more than one group
190
# find the largest group and disassemble the rest
191
group_id_max = groups_temp.keys()[0]
192
size_max = len(groups[group_id_max][0])
193
for group_id_temp in groups_temp.keys()[1:]:
194
size_temp = len(groups[group_id_temp][0])
195
if size_temp > size_max:
196
group_id_max = group_id_temp
197
size_max = size_temp
198
else: # only one group, automatically the largest one
199
group_id_max = groups_temp.keys()[0]
200
return groups_temp, group_id_max
201
202
# general function to reset radian angle to [-pi, pi)
203
def reset_radian(radian):
204
while radian >= math.pi:
205
radian = radian - 2*math.pi
206
while radian < -math.pi:
207
radian = radian + 2*math.pi
208
return radian
209
210
# general function to steer robot away from wall if out of boundary (following physics)
211
# use global variable "world_side_length"
212
def robot_boundary_check(robot_pos, robot_ori):
213
new_ori = robot_ori
214
if robot_pos[0] >= world_side_length: # outside of right boundary
215
if math.cos(new_ori) > 0:
216
new_ori = reset_radian(2*(math.pi/2) - new_ori)
217
# further check if new angle is too much perpendicular
218
if new_ori > 0:
219
if (math.pi - new_ori) < perp_thres:
220
new_ori = new_ori - devia_angle
221
else:
222
if (new_ori + math.pi) < perp_thres:
223
new_ori = new_ori + devia_angle
224
elif robot_pos[0] <= 0: # outside of left boundary
225
if math.cos(new_ori) < 0:
226
new_ori = reset_radian(2*(math.pi/2) - new_ori)
227
if new_ori > 0:
228
if new_ori < perp_thres:
229
new_ori = new_ori + devia_angle
230
else:
231
if (-new_ori) < perp_thres:
232
new_ori = new_ori - devia_angle
233
if robot_pos[1] >= world_side_length: # outside of top boundary
234
if math.sin(new_ori) > 0:
235
new_ori = reset_radian(2*(0) - new_ori)
236
if new_ori > -math.pi/2:
237
if (new_ori + math.pi/2) < perp_thres:
238
new_ori = new_ori + devia_angle
239
else:
240
if (-math.pi/2 - new_ori) < perp_thres:
241
new_ori = new_ori - devia_angle
242
elif robot_pos[1] <= 0: # outside of bottom boundary
243
if math.sin(new_ori) < 0:
244
new_ori = reset_radian(2*(0) - new_ori)
245
if new_ori > math.pi/2:
246
if (new_ori - math.pi/2) < perp_thres:
247
new_ori = new_ori + devia_angle
248
else:
249
if (math.pi/2 - new_ori) < perp_thres:
250
new_ori = new_ori - devia_angle
251
return new_ori
252
253
########### simulation 1: aggregate together to form a random loop ###########
254
255
print("##### simulation 1: loop formation #####")
256
257
# robot perperties
258
# all robots start with state '-1'
259
robot_states = np.array([-1 for i in range(swarm_size)])
260
# '-1' for wandering around, ignoring all connections
261
# '0' for wandering around, available to connection
262
# '1' for in a group, transit state, only one key neighbor
263
# '2' for in a group, both key neighbors secured
264
n1_life_lower = 2 # inclusive
265
n1_life_upper = 6 # exclusive
266
robot_n1_lives = np.random.uniform(n1_life_lower, n1_life_upper, swarm_size)
267
robot_oris = np.random.rand(swarm_size) * 2 * math.pi - math.pi # in range of [-pi, pi)
268
robot_key_neighbors = [[] for i in range(swarm_size)] # key neighbors for robot on loop
269
# for state '1' robot: one key neighbor
270
# for state '2' robot: two key neighbor on its left and right sides
271
# exception is the group has only two members, one key neighbor for each
272
273
# group properties
274
groups = {}
275
# key is the group id, value is a list, in the list:
276
# [0]: a list of robots in the group, both state '1' and '2'
277
# [1]: remaining life time of the group
278
# [2]: whether or not being the dominant group
279
life_incre = 5 # number of seconds added to the life of a group when new robot joins
280
group_id_upper = swarm_size # upper limit of group id
281
robot_group_ids = np.array([-1 for i in range(swarm_size)]) # group id for the robots
282
# '-1' for not in a group
283
284
# movement configuration
285
step_moving_dist = 0.05 # should be smaller than destination distance error
286
destination_error = 0.1
287
mov_vec_ratio = 0.5 # ratio used when calculating mov vector
288
# spring constants in SMA
289
linear_const = 1.0
290
bend_const = 0.8
291
disp_coef = 0.5
292
# for avoiding space too small on loop
293
space_good_thres = desired_space * 0.85
294
295
# the loop for simulation 1
296
sim_haulted = False
297
time_last = pygame.time.get_ticks()
298
time_now = time_last
299
frame_period = 50
300
sim_freq_control = True
301
iter_count = 0
302
loop_formed = False
303
ending_period = 1.0 # grace period
304
print("swarm robots are forming a random loop ...")
305
while True:
306
for event in pygame.event.get():
307
if event.type == pygame.QUIT: # close window button is clicked
308
print("program exit in simulation 1")
309
sys.exit() # exit the entire program
310
if event.type == pygame.KEYUP:
311
if event.key == pygame.K_SPACE:
312
sim_haulted = not sim_haulted # reverse the pause flag
313
if sim_haulted: continue
314
315
# simulation frequency control
316
if sim_freq_control:
317
time_now = pygame.time.get_ticks()
318
if (time_now - time_last) > frame_period:
319
time_last = time_now
320
else:
321
continue
322
323
# increase iteration count
324
iter_count = iter_count + 1
325
326
# state transition variables
327
st_n1to0 = [] # robot '-1' gets back to '0' after life time ends
328
# list of robots changing to '0' from '-1'
329
st_gton1 = [] # group disassembles either because life expires, or triggered by others
330
# list of groups to be disassembled
331
st_0to1 = {} # robot '0' detects robot'2', join its group
332
# key is the robot '0', value is the group id
333
st_0to2 = {} # robot '0' detects another robot '0', forming a new group
334
# key is the robot '0', value is the other neighbor robot '0'
335
st_1to2 = {} # robot '1' finds another key neighbor, becoming '2'
336
# key is the left side of the slot, value is a list of robots intend to join
337
# the left side of the slot may or may not be the current key neighbor of robot '1'
338
339
# check state transitions, and schedule the tasks
340
dist_conn_update()
341
for i in range(swarm_size):
342
if robot_states[i] == -1: # for host robot with state '-1'
343
if robot_n1_lives[i] < 0:
344
st_n1to0.append(i)
345
else:
346
if len(conn_lists[i]) == 0: continue
347
state2_list = []
348
state1_list = []
349
for j in conn_lists[i]:
350
if robot_states[j] == 2:
351
state2_list.append(j)
352
elif robot_states[j] == 1:
353
state1_list.append(j)
354
# either disassemble minority groups, or get repelled by robot '2'
355
if len(state2_list) + len(state1_list) != 0:
356
groups_local, group_id_max = S1_robot_grouping(
357
state2_list + state1_list, robot_group_ids, groups)
358
if len(groups_local.keys()) > 1:
359
# disassemble all groups except the largest one
360
for group_id_temp in groups_local.keys():
361
if ((group_id_temp != group_id_max) and
362
(group_id_temp not in st_gton1)):
363
# schedule to disassemble this group
364
st_gton1.append(group_id_temp)
365
else:
366
# state '-1' robot can only be repelled away by state '2' robot
367
if len(state2_list) != 0:
368
# find the closest neighbor in groups_local[group_id_max]
369
robot_closest = S1_closest_robot(i, state2_list)
370
# change moving direction opposing the closest robot
371
vect_temp = robot_poses[i] - robot_poses[robot_closest]
372
robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
373
elif robot_states[i] == 0: # for host robot with state '0'
374
if len(conn_lists[i]) == 0: continue
375
state2_list = []
376
state1_list = []
377
state0_list = []
378
for j in conn_lists[i]:
379
if robot_states[j] == 2:
380
state2_list.append(j)
381
elif robot_states[j] == 1:
382
state1_list.append(j)
383
elif robot_states[j] == 0:
384
state0_list.append(j)
385
state2_quantity = len(state2_list)
386
state1_quantity = len(state1_list)
387
state0_quantity = len(state0_list)
388
# disassemble minority groups if there are multiple groups
389
if state2_quantity + state1_quantity > 1:
390
# there is in-group robot in the neighbors
391
groups_local, group_id_max = S1_robot_grouping(state2_list+state1_list,
392
robot_group_ids, groups)
393
# disassmeble all groups except the largest one
394
for group_id_temp in groups_local.keys():
395
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
396
st_gton1.append(group_id_temp) # schedule to disassemble this group
397
# responses to the state '2' and '0' robots
398
if state2_quantity != 0:
399
# join the group with state '2' robots
400
if state2_quantity == 1: # only one state '2' robot
401
# join the group of the state '2' robot
402
st_0to1[i] = robot_group_ids[state2_list[0]]
403
robot_key_neighbors[i] = [state2_list[0]] # add key neighbor
404
else: # multiple state '2' robots
405
# it's possible that the state '2' robots are in different groups
406
# find the closest one in the largest group, and join the group
407
groups_local, group_id_max = S1_robot_grouping(state2_list,
408
robot_group_ids, groups)
409
robot_closest = S1_closest_robot(i, groups_local[group_id_max])
410
st_0to1[i] = group_id_max
411
robot_key_neighbors[i] = [robot_closest] # add key neighbor
412
elif state0_quantity != 0:
413
# form new group with state '0' robots
414
st_0to2[i] = S1_closest_robot(i, state0_list)
415
elif (robot_states[i] == 1) or (robot_states[i] == 2):
416
# disassemble the minority groups
417
state12_list = [] # list of state '1' and '2' robots in the list
418
has_other_group = False
419
host_group_id = robot_group_ids[i]
420
for j in conn_lists[i]:
421
if (robot_states[j] == 1) or (robot_states[j] == 2):
422
state12_list.append(j)
423
if robot_group_ids[j] != host_group_id:
424
has_other_group = True
425
if has_other_group:
426
groups_local, group_id_max = S1_robot_grouping(state12_list,
427
robot_group_ids, groups)
428
for group_id_temp in groups_local.keys():
429
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
430
st_gton1.append(group_id_temp) # schedule to disassemble this group
431
# check if the other key neighbor of state '1' robot appears
432
if robot_states[i] == 1:
433
key = robot_key_neighbors[i][0]
434
slot_left = -1 # to indicate if a slot is available
435
if len(robot_key_neighbors[key]) == 1:
436
# this is a group of only two members
437
key_other = robot_key_neighbors[key][0]
438
if key_other in conn_lists[i]:
439
join_slot = [key, key_other] # the slot robot i is going to join
440
# find the left side of the join slot
441
slot_vect = robot_poses[join_slot[1]] - robot_poses[join_slot[0]]
442
join_vect = robot_poses[i] - robot_poses[join_slot[0]]
443
if np.dot(join_vect, slot_vect) > 0:
444
slot_left = join_slot[0]
445
else:
446
slot_left = join_slot[1]
447
else: # a normal group with at least three members
448
key_left = robot_key_neighbors[key][0]
449
key_right = robot_key_neighbors[key][1]
450
key_left_avail = key_left in conn_lists[i]
451
key_right_avail = key_right in conn_lists[i]
452
if key_left_avail or key_right_avail:
453
if (not key_left_avail) and key_right_avail:
454
slot_left = key
455
elif key_left_avail and (not key_right_avail):
456
slot_left = key_left
457
else: # both sides are available
458
robot_closest = S1_closest_robot(i, [key_left, key_right])
459
if robot_closest == key_left:
460
slot_left = key_left
461
else:
462
slot_left = key
463
# schedule the task to join the slot
464
if slot_left != -1:
465
# becoming state '2'
466
if slot_left in st_1to2.keys():
467
st_1to2[slot_left].append(i)
468
else:
469
st_1to2[slot_left] = [i]
470
471
# check the life time of the groups; schedule disassembling if expired
472
for group_id_temp in groups.keys():
473
if groups[group_id_temp][1] < 0: # life time of a group ends
474
if group_id_temp not in st_gton1:
475
st_gton1.append(group_id_temp)
476
477
# process the state transitions
478
# 1.st_1to2, state '1' becomes state '2'
479
for slot_left in st_1to2.keys():
480
slot_right = robot_key_neighbors[slot_left][-1]
481
joiner = -1 # the accepted joiner in this position
482
if len(st_1to2[slot_left]) == 1:
483
joiner = st_1to2[slot_left][0]
484
else:
485
# find the joiner which is farthest from slot_left
486
dist_max = 0
487
for joiner_temp in st_1to2[slot_left]:
488
dist_temp = dist_table[slot_left,joiner_temp]
489
if dist_temp > dist_max:
490
dist_max = dist_temp
491
joiner = joiner_temp
492
# plug in the new state '2' member
493
# update the robot properties
494
group_id_temp = robot_group_ids[slot_left]
495
robot_states[joiner] = 2
496
robot_group_ids[joiner] = group_id_temp
497
robot_key_neighbors[joiner] = [slot_left, slot_right]
498
if len(robot_key_neighbors[slot_left]) == 1:
499
robot_key_neighbors[slot_left] = [slot_right, joiner]
500
robot_key_neighbors[slot_right] = [joiner, slot_left]
501
else:
502
robot_key_neighbors[slot_left][1] = joiner
503
robot_key_neighbors[slot_right][0] = joiner
504
# no need to update the group properties
505
# 2.st_0to1, robot '0' joins a group, becoming '1'
506
for joiner in st_0to1.keys():
507
group_id_temp = st_0to1[joiner]
508
# update the robot properties
509
robot_states[joiner] = 1
510
robot_group_ids[joiner] = group_id_temp
511
# update the group properties
512
groups[group_id_temp][0].append(joiner)
513
groups[group_id_temp][1] = groups[group_id_temp][1] + life_incre
514
# 3.st_0to2, robot '0' forms new group with '0', both becoming '2'
515
while len(st_0to2.keys()) != 0:
516
pair0 = st_0to2.keys()[0]
517
pair1 = st_0to2[pair0]
518
st_0to2.pop(pair0)
519
if (pair1 in st_0to2.keys()) and (st_0to2[pair1] == pair0):
520
st_0to2.pop(pair1)
521
# only forming a group if there is at least one seed robot in the pair
522
if robot_seeds[pair0] or robot_seeds[pair1]:
523
# forming new group for robot pair0 and pair1
524
group_id_temp = np.random.randint(0, group_id_upper)
525
while group_id_temp in groups.keys():
526
group_id_temp = np.random.randint(0, group_id_upper)
527
# update properties of the robots
528
robot_states[pair0] = 2
529
robot_states[pair1] = 2
530
robot_group_ids[pair0] = group_id_temp
531
robot_group_ids[pair1] = group_id_temp
532
robot_key_neighbors[pair0] = [pair1]
533
robot_key_neighbors[pair1] = [pair0]
534
# update properties of the group
535
groups[group_id_temp] = [[pair0, pair1], life_incre*2, False]
536
# 4.st_gton1, groups get disassembled, life time ends or triggered by others
537
for group_id_temp in st_gton1:
538
for robot_temp in groups[group_id_temp][0]:
539
robot_states[robot_temp] = -1
540
robot_n1_lives[robot_temp] = np.random.uniform(n1_life_lower, n1_life_upper)
541
robot_group_ids[robot_temp] = -1
542
robot_oris[robot_temp] = np.random.rand() * 2 * math.pi - math.pi
543
robot_key_neighbors[robot_temp] = []
544
groups.pop(group_id_temp)
545
# 5.st_n1to0, life time of robot '-1' ends, get back to '0'
546
for robot_temp in st_n1to0:
547
robot_states[robot_temp] = 0
548
549
# check if a group becomes dominant
550
for group_id_temp in groups.keys():
551
if len(groups[group_id_temp][0]) > swarm_size/2.0:
552
groups[group_id_temp][2] = True
553
else:
554
groups[group_id_temp][2] = False
555
556
# update the physics
557
robot_poses_t = np.copy(robot_poses) # as old poses
558
no_state1_robot = True
559
for i in range(swarm_size):
560
# adjusting moving direction for state '1' and '2' robots
561
if robot_states[i] == 1:
562
no_state1_robot = False
563
# rotating around its key neighbor, get closer to the other key neighbor
564
center = robot_key_neighbors[i][0] # the center robot
565
if dist_table[i,center] > (desired_space + step_moving_dist):
566
# moving toward the center robot
567
vect_temp = robot_poses_t[center] - robot_poses_t[i]
568
robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
569
elif (dist_table[i,center] + step_moving_dist) < desired_space:
570
# moving away from the center robot
571
vect_temp = robot_poses_t[i] - robot_poses_t[center]
572
robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
573
else:
574
# moving tangent along the circle of radius of "desired_space"
575
# find the rotating direction to the closer potential neighbor
576
rotate_dir = 0 # 1 for ccw, -1 for cw
577
key_next = -1 # rotating toward to key_next
578
if len(robot_key_neighbors[center]) == 1:
579
key_next = robot_key_neighbors[center][0]
580
else:
581
key_next = S1_closest_robot(i, robot_key_neighbors[center])
582
vect_i = robot_poses_t[i] - robot_poses_t[center]
583
vect_next = robot_poses_t[key_next] - robot_poses_t[center]
584
if np.cross(vect_i, vect_next) > 0:
585
rotate_dir = 1 # should rotate ccw
586
else:
587
rotate_dir = -1 # should rotate cw
588
# calculate the new moving direction
589
robot_oris[i] = math.atan2(vect_i[1], vect_i[0])
590
int_angle_temp = math.acos((math.pow(dist_table[i,center],2) +
591
math.pow(step_moving_dist,2) - math.pow(desired_space,2)) /
592
(2.0*dist_table[i,center]*step_moving_dist))
593
robot_oris[i] = reset_radian(robot_oris[i] +
594
rotate_dir*(math.pi - int_angle_temp))
595
elif robot_states[i] == 2:
596
# adjusting position to maintain the loop
597
if len(robot_key_neighbors[i]) == 1:
598
# situation that only two robots are in the group
599
j = robot_key_neighbors[i][0]
600
if abs(dist_table[i,j] - desired_space) < destination_error:
601
continue # stay in position if within destination error
602
else:
603
if dist_table[i,j] > desired_space:
604
robot_oris[i] = math.atan2(robot_poses_t[j,1] - robot_poses_t[i,1],
605
robot_poses_t[j,0] - robot_poses_t[i,0])
606
else:
607
robot_oris[i] = math.atan2(robot_poses_t[i,1] - robot_poses_t[j,1],
608
robot_poses_t[i,0] - robot_poses_t[j,0])
609
else:
610
# normal situation with at least three members in the group
611
group_id_temp = robot_group_ids[i]
612
state2_quantity = 0 # number of state '2' robots
613
for robot_temp in groups[group_id_temp][0]:
614
if robot_states[robot_temp] == 2:
615
state2_quantity = state2_quantity + 1
616
desired_angle = math.pi - 2*math.pi / state2_quantity
617
# use the SMA algorithm to achieve the desired interior angle
618
left_key = robot_key_neighbors[i][0]
619
right_key = robot_key_neighbors[i][1]
620
vect_l = (robot_poses_t[left_key] - robot_poses_t[i]) / dist_table[i,left_key]
621
vect_r = (robot_poses_t[right_key] - robot_poses_t[i]) / dist_table[i,right_key]
622
vect_lr = robot_poses_t[right_key] - robot_poses_t[left_key]
623
vect_lr_dist = np.linalg.norm(vect_lr)
624
vect_in = np.array([-vect_lr[1], vect_lr[0]]) / vect_lr_dist
625
inter_curr = math.acos(np.dot(vect_l, vect_r)) # interior angle
626
if np.cross(vect_r, vect_l) < 0:
627
inter_curr = 2*math.pi - inter_curr
628
fb_vect = np.zeros(2) # feedback vector to accumulate spring effects
629
fb_vect = fb_vect + ((dist_table[i,left_key] - desired_space) *
630
linear_const * vect_l)
631
fb_vect = fb_vect + ((dist_table[i,right_key] - desired_space) *
632
linear_const * vect_r)
633
fb_vect = fb_vect + ((desired_angle - inter_curr) *
634
bend_const * vect_in)
635
if (np.linalg.norm(fb_vect)*disp_coef < destination_error and
636
dist_table[i,left_key] > space_good_thres and
637
dist_table[i,right_key] > space_good_thres):
638
# stay in position if within destination error, and space are good
639
continue
640
else:
641
robot_oris[i] = math.atan2(fb_vect[1], fb_vect[0])
642
# check if out of boundaries
643
if (robot_states[i] == -1) or (robot_states[i] == 0):
644
# only applies for state '-1' and '0'
645
robot_oris[i] = robot_boundary_check(robot_poses_t[i], robot_oris[i])
646
# update one step of move
647
robot_poses[i] = robot_poses_t[i] + (step_moving_dist *
648
np.array([math.cos(robot_oris[i]), math.sin(robot_oris[i])]))
649
650
# update the graphics
651
disp_poses_update()
652
screen.fill(color_white)
653
# draw the robots of states '-1' and '0'
654
for i in range(swarm_size):
655
if robot_seeds[i]:
656
color_temp = color_red
657
else:
658
color_temp = color_grey
659
if robot_states[i] == -1: # empty circle for state '-1' robot
660
pygame.draw.circle(screen, color_temp, disp_poses[i],
661
robot_size, robot_empty_width)
662
elif robot_states[i] == 0: # full circle for state '0' robot
663
pygame.draw.circle(screen, color_temp, disp_poses[i],
664
robot_size, 0)
665
# draw the in-group robots by group
666
for group_id_temp in groups.keys():
667
if groups[group_id_temp][2]:
668
# highlight the dominant group with black color
669
color_group = color_black
670
else:
671
color_group = color_grey
672
conn_draw_sets = [] # avoid draw same connection two times
673
# draw the robots and connections in the group
674
for i in groups[group_id_temp][0]:
675
for j in robot_key_neighbors[i]:
676
if set([i,j]) not in conn_draw_sets:
677
pygame.draw.line(screen, color_group, disp_poses[i],
678
disp_poses[j], conn_width)
679
conn_draw_sets.append(set([i,j]))
680
# draw robots in the group
681
if robot_seeds[i]: # force color red for seed robot
682
pygame.draw.circle(screen, color_red, disp_poses[i],
683
robot_size, 0)
684
else:
685
pygame.draw.circle(screen, color_group, disp_poses[i],
686
robot_size, 0)
687
pygame.display.update()
688
689
# reduce life time of robot '-1' and groups
690
for i in range(swarm_size):
691
if robot_states[i] == -1:
692
robot_n1_lives[i] = robot_n1_lives[i] - frame_period/1000.0
693
for group_id_temp in groups.keys():
694
if not groups[group_id_temp][2]: # skip dominant group
695
groups[group_id_temp][1] = groups[group_id_temp][1] - frame_period/1000.0
696
697
# check exit condition of simulation 1
698
if not loop_formed:
699
if ((len(groups.keys()) == 1) and (len(groups.values()[0][0]) == swarm_size)
700
and no_state1_robot):
701
loop_formed = True
702
if loop_formed:
703
if ending_period <= 0:
704
print("simulation 1 is finished")
705
if manual_mode: raw_input("<Press Enter to continue>")
706
print("") # empty line
707
break
708
else:
709
ending_period = ending_period - frame_period/1000.0
710
711
# check if the loop is complete; calculate robots' order on loop
712
loop_set = set() # set of robots on the loop
713
robot_starter = 0
714
robot_curr = 0
715
loop_set.add(robot_curr)
716
robot_loop_orders = np.zeros(swarm_size) # robot's order on loop
717
order_count = 0
718
while (robot_key_neighbors[robot_curr][1] != robot_starter):
719
robot_next = robot_key_neighbors[robot_curr][1]
720
loop_set.add(robot_next)
721
order_count = order_count + 1
722
robot_loop_orders[robot_next] = order_count
723
robot_curr = robot_next
724
robot_loop_orders = robot_loop_orders.astype(int) # somehow needs this line
725
if (len(loop_set) != swarm_size):
726
print("loop is incomplete after loop formation")
727
sys.exit()
728
729
# # store the variable "robot_poses", "robot_key_neighbors", and "robot_loop_orders"
730
# # tmp_filepath = os.path.join('tmp', 'demo2_30_robot_poses')
731
# tmp_filepath = os.path.join('tmp', 'demo2_100_robot_poses')
732
# with open(tmp_filepath, 'w') as f:
733
# pickle.dump([robot_poses, robot_key_neighbors, robot_loop_orders], f)
734
# raw_input("<Press Enter to continue>")
735
# sys.exit()
736
737
# # restore variable "robot_poses", "robot_key_neighbors", and "robot_loop_orders"
738
# # tmp_filepath = os.path.join('tmp', 'demo2_30_robot_poses')
739
# tmp_filepath = os.path.join('tmp', 'demo2_100_robot_poses')
740
# with open(tmp_filepath) as f:
741
# robot_poses, robot_key_neighbors, robot_loop_orders = pickle.load(f)
742
743
# simulation 2 and 3 will run repeatedly since here
744
while True:
745
746
########### simulation 2: consensus decision making for target loop shape ###########
747
748
print("##### simulation 2: consensus decision making #####")
749
750
# shift the robots to the middle of the window
751
x_max, y_max = np.amax(robot_poses, axis=0)
752
x_min, y_min = np.amin(robot_poses, axis=0)
753
robot_middle = np.array([(x_max+x_min)/2.0, (y_max+y_min)/2.0])
754
world_middle = np.array([world_side_length/2.0, world_side_length/2.0])
755
for i in range(swarm_size):
756
robot_poses[i] = robot_poses[i] - robot_middle + world_middle
757
758
# draw the network for the first time
759
disp_poses_update()
760
screen.fill(color_white)
761
for i in range(swarm_size):
762
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size, 0)
763
pygame.draw.line(screen, color_black, disp_poses[i],
764
disp_poses[robot_key_neighbors[i][1]], conn_width)
765
pygame.display.update()
766
767
# initialize the decision making variables
768
shape_decision = -1
769
deci_dist = np.random.rand(swarm_size, shape_quantity)
770
sum_temp = np.sum(deci_dist, axis=1)
771
for i in range(swarm_size):
772
deci_dist[i] = deci_dist[i] / sum_temp[i]
773
deci_domi = np.argmax(deci_dist, axis=1)
774
groups = [] # group robots by local consensus
775
robot_group_sizes = [0 for i in range(swarm_size)]
776
# color assignment
777
color_initialized = False
778
deci_colors = [-1 for i in range(shape_quantity)]
779
color_assigns = [0 for i in range(color_quantity)]
780
group_colors = []
781
robot_colors = [0 for i in range(swarm_size)]
782
# decision making control variables
783
dist_diff_thres = 0.3
784
dist_diff_ratio = [0.0 for i in range(swarm_size)]
785
dist_diff_power = 0.3
786
787
# the loop for simulation 2
788
sim_haulted = False
789
time_last = pygame.time.get_ticks()
790
time_now = time_last
791
frame_period = 500
792
sim_freq_control = True
793
iter_count = 0
794
sys.stdout.write("iteration {}".format(iter_count))
795
sys.stdout.flush()
796
while True:
797
for event in pygame.event.get():
798
if event.type == pygame.QUIT: # close window button is clicked
799
print("program exit in simulation 2")
800
sys.exit() # exit the entire program
801
if event.type == pygame.KEYUP:
802
if event.key == pygame.K_SPACE:
803
sim_haulted = not sim_haulted # reverse the pause flag
804
if sim_haulted: continue
805
806
# simulation frequency control
807
if sim_freq_control:
808
time_now = pygame.time.get_ticks()
809
if (time_now - time_last) > frame_period:
810
time_last = time_now
811
else:
812
continue
813
814
# increase iteration count
815
iter_count = iter_count + 1
816
sys.stdout.write("\riteration {}".format(iter_count))
817
sys.stdout.flush()
818
819
# update the dominant decision for all robot
820
deci_domi = np.argmax(deci_dist, axis=1)
821
# update the groups
822
robot_starter = 0
823
robot_curr = 0
824
groups = [[robot_curr]] # empty the group container
825
# slice the loop at the connection before id '0' robot
826
while (robot_key_neighbors[robot_curr][1] != robot_starter):
827
robot_next = robot_key_neighbors[robot_curr][1]
828
if (deci_domi[robot_curr] == deci_domi[robot_next]):
829
groups[-1].append(robot_next)
830
else:
831
groups.append([robot_next])
832
robot_curr = robot_next
833
# check if the two groups on the slicing point are in same group
834
if (len(groups) > 1 and deci_domi[0] == deci_domi[robot_key_neighbors[0][0]]):
835
# combine the last group to the first group
836
for i in reversed(groups[-1]):
837
groups[0].insert(0,i)
838
groups.pop(-1)
839
# the decisions for the groups
840
group_deci = [deci_domi[groups[i][0]] for i in range(len(groups))]
841
# update group sizes for robots
842
for group_temp in groups:
843
group_temp_size = len(group_temp)
844
for i in group_temp:
845
robot_group_sizes[i] = group_temp_size
846
847
# update colors for the decisions
848
if not color_initialized:
849
color_initialized = True
850
color_set = range(color_quantity)
851
deci_set = set(group_deci)
852
for deci in deci_set:
853
if len(color_set) == 0:
854
color_set = range(color_quantity)
855
chosen_color = np.random.choice(color_set)
856
color_set.remove(chosen_color)
857
deci_colors[deci] = chosen_color
858
color_assigns[chosen_color] = color_assigns[chosen_color] + 1
859
else:
860
# remove the color for a decision, if it's no longer the decision of any group
861
deci_set = set(group_deci)
862
for deci_temp in range(shape_quantity):
863
color_temp = deci_colors[deci_temp] # corresponding color for deci_temp
864
if (color_temp != -1 and deci_temp not in deci_set):
865
color_assigns[color_temp] = color_assigns[color_temp] - 1
866
deci_colors[deci_temp] = -1
867
# assign color for a new decision
868
color_set = []
869
for i in range(len(groups)):
870
if deci_colors[group_deci[i]] == -1:
871
if len(color_set) == 0:
872
# construct a new color set
873
color_assigns_min = min(color_assigns)
874
for color_temp in range(color_quantity):
875
if color_assigns[color_temp] == color_assigns_min:
876
color_set.append(color_temp)
877
# if here, the color set is good to go
878
chosen_color = np.random.choice(color_set)
879
color_set.remove(chosen_color)
880
deci_colors[group_deci[i]] = chosen_color
881
color_assigns[chosen_color] = color_assigns[chosen_color] + 1
882
# update the colors for the groups and robots
883
group_colors = []
884
for i in range(len(groups)):
885
color_temp = deci_colors[group_deci[i]]
886
group_colors.append(color_temp)
887
for j in groups[i]:
888
robot_colors[j] = color_temp
889
890
# decision distribution evolution
891
converged_all = True
892
deci_dist_t = np.copy(deci_dist) # deep copy the 'deci_dist'
893
for i in range(swarm_size):
894
i_l = robot_key_neighbors[i][0] # index of neighbor on the left
895
i_r = robot_key_neighbors[i][1] # index of neighbor on the right
896
# deciding if two neighbors have converged ideas with host robot
897
converged_l = False
898
if (deci_domi[i_l] == deci_domi[i]): converged_l = True
899
converged_r = False
900
if (deci_domi[i_r] == deci_domi[i]): converged_r = True
901
# weighted averaging depending on group property
902
if converged_l and converged_r: # all three robots are locally converged
903
# step 1: take equal weight average on all three distributions
904
deci_dist[i] = deci_dist_t[i_l] + deci_dist_t[i] + deci_dist_t[i_r]
905
dist_sum = np.sum(deci_dist[i])
906
deci_dist[i] = deci_dist[i] / dist_sum
907
# step 2: increase the unipolarity by applying the linear multiplier
908
dist_diff = [np.linalg.norm(deci_dist_t[i_l]-deci_dist_t[i], 1),
909
np.linalg.norm(deci_dist_t[i_r]-deci_dist_t[i], 1),
910
np.linalg.norm(deci_dist_t[i_l]-deci_dist_t[i_r], 1)]
911
dist_diff_max = max(dist_diff) # maximum distribution difference
912
if dist_diff_max < dist_diff_thres:
913
dist_diff_ratio[i] = dist_diff_max/dist_diff_thres # for debugging
914
small_end = 1.0/shape_quantity * np.power(dist_diff_max/dist_diff_thres,
915
dist_diff_power)
916
large_end = 2.0/shape_quantity - small_end
917
# sort the magnitude of processed distribution
918
dist_t = np.copy(deci_dist[i]) # temporary distribution
919
sort_index = range(shape_quantity)
920
for j in range(shape_quantity-1): # bubble sort, ascending order
921
for k in range(shape_quantity-1-j):
922
if dist_t[k] > dist_t[k+1]:
923
# exchange values in 'dist_t'
924
temp = dist_t[k]
925
dist_t[k] = dist_t[k+1]
926
dist_t[k+1] = temp
927
# exchange values in 'sort_index'
928
temp = sort_index[k]
929
sort_index[k] = sort_index[k+1]
930
sort_index[k+1] = temp
931
# applying the linear multiplier
932
dist_sum = 0
933
for j in range(shape_quantity):
934
multiplier = (small_end +
935
float(j)/(shape_quantity-1) * (large_end-small_end))
936
deci_dist[i,sort_index[j]] = deci_dist[i,sort_index[j]] * multiplier
937
dist_sum = np.sum(deci_dist[i])
938
deci_dist[i] = deci_dist[i] / dist_sum
939
else:
940
dist_diff_ratio[i] = 1.0 # for debugging, ratio overflowed
941
else: # at least one side has not converged yet
942
if converged_all: converged_all = False
943
dist_diff_ratio[i] = -1.0 # indicating linear multiplier was not used
944
# take unequal weight in the averaging process based on group property
945
group_size_l = robot_group_sizes[i_l]
946
group_size_r = robot_group_sizes[i_r]
947
# weight on left is group_size_l, on host is 1, on right is group_size_r
948
deci_dist[i] = (robot_group_sizes[i_l] * deci_dist_t[i_l] +
949
deci_dist_t[i] +
950
robot_group_sizes[i_r] * deci_dist_t[i_r])
951
dist_sum = np.sum(deci_dist[i])
952
deci_dist[i] = deci_dist[i] / dist_sum
953
954
# update the graphics
955
screen.fill(color_white)
956
# draw the connections first
957
for i in range(swarm_size):
958
i_next = robot_key_neighbors[i][1]
959
if (deci_domi[i] == deci_domi[i_next]):
960
pygame.draw.line(screen, distinct_color_set[robot_colors[i]],
961
disp_poses[i], disp_poses[i_next], conn_width_thick_consensus)
962
else:
963
pygame.draw.line(screen, color_black, disp_poses[i], disp_poses[i_next],
964
conn_width_thin_consensus)
965
# draw the robots
966
for i in range(swarm_size):
967
pygame.draw.circle(screen, distinct_color_set[robot_colors[i]], disp_poses[i],
968
robot_size_consensus, 0)
969
pygame.display.update()
970
971
# check exit condition for simulations 2
972
if converged_all:
973
shape_decision = deci_domi[0]
974
print("") # move cursor to the new line
975
print("converged to decision {}: {}".format(shape_decision,
976
shape_catalog[shape_decision]))
977
print("simulation 2 is finished")
978
if manual_mode: raw_input("<Press Enter to continue>")
979
print("") # empty line
980
break
981
982
########### simulation 3: role assignment and loop reshape ###########
983
984
print("##### simulation 3: role assignment & loop reshape #####")
985
986
print("chosen shape {}: {}".format(shape_decision, shape_catalog[shape_decision]))
987
988
# force the choice of shape, for video recording
989
if len(force_shape_set) == 0: force_shape_set = range(shape_quantity)
990
force_choice = np.random.choice(force_shape_set)
991
force_shape_set.remove(force_choice)
992
shape_decision = force_choice
993
print("force shape to {}: {} (for video recording)".format(shape_decision,
994
shape_catalog[shape_decision]))
995
996
# read the loop shape from file
997
filename = str(swarm_size) + "-" + shape_catalog[shape_decision]
998
filepath = os.path.join(os.getcwd(), loop_folder, filename)
999
if os.path.isfile(filepath):
1000
with open(filepath, 'r') as f:
1001
target_poses = pickle.load(f)
1002
else:
1003
print("fail to locate shape file: {}".format(filepath))
1004
sys.exit()
1005
# calculate the interior angles for the robots(instead of target positions)
1006
inter_target = np.zeros(swarm_size)
1007
for i in range(swarm_size): # i on the target loop
1008
i_l = (i-1)%swarm_size
1009
i_r = (i+1)%swarm_size
1010
vect_l = target_poses[i_l] - target_poses[i]
1011
vect_r = target_poses[i_r] - target_poses[i]
1012
dist_l = np.linalg.norm(vect_l)
1013
dist_r = np.linalg.norm(vect_r)
1014
inter_target[i] = math.acos(np.around(
1015
np.dot(vect_l, vect_r) / (dist_l * dist_r) ,6))
1016
if np.cross(vect_r, vect_l) < 0:
1017
inter_target[i] = 2*math.pi - inter_target[i]
1018
1019
# draw the network for the first time
1020
screen.fill(color_white)
1021
for i in range(swarm_size):
1022
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size, 0)
1023
pygame.draw.line(screen, color_black, disp_poses[i],
1024
disp_poses[robot_key_neighbors[i][1]], conn_width)
1025
pygame.display.update()
1026
1027
# initialize the decision making variables
1028
pref_dist = np.random.rand(swarm_size, swarm_size)
1029
sum_temp = np.sum(pref_dist, axis=1)
1030
for i in range(swarm_size):
1031
pref_dist[i] = pref_dist[i] / sum_temp[i]
1032
deci_domi = np.argmax(pref_dist, axis=1)
1033
groups = [] # group robots by local consensus
1034
robot_group_sizes = [0 for i in range(swarm_size)]
1035
# color assignment
1036
color_initialized = False
1037
deci_colors = [-1 for i in range(swarm_size)]
1038
color_assigns = [0 for i in range(color_quantity)]
1039
group_colors = []
1040
robot_colors = [0 for i in range(swarm_size)]
1041
# decision making control variables
1042
dist_diff_thres = 0.3
1043
dist_diff_ratio = [0.0 for i in range(swarm_size)]
1044
dist_diff_power = 0.3
1045
1046
# formation control variables
1047
inter_err_thres = 0.1
1048
inter_target_circle = math.pi - 2*math.pi/swarm_size # interior angle for circle
1049
formation_stretched = False # whether the stretching process is done
1050
formation_stretched_err = inter_target_circle*0.1
1051
1052
# spring constants in SMA
1053
linear_const = 1.0
1054
bend_const = 0.8
1055
disp_coef = 0.05
1056
1057
# the loop for simulation 3
1058
sim_haulted = False
1059
time_last = pygame.time.get_ticks()
1060
time_now = time_last
1061
frame_period = 200
1062
sim_freq_control = True
1063
print("loop is stretching ...")
1064
iter_count = 0
1065
# sys.stdout.write("iteration {}".format(iter_count))
1066
# sys.stdout.flush()
1067
while True:
1068
for event in pygame.event.get():
1069
if event.type == pygame.QUIT: # close window button is clicked
1070
print("program exit in simulation 3")
1071
sys.exit() # exit the entire program
1072
if event.type == pygame.KEYUP:
1073
if event.key == pygame.K_SPACE:
1074
sim_haulted = not sim_haulted # reverse the pause flag
1075
if sim_haulted: continue
1076
1077
# simulation frequency control
1078
if sim_freq_control:
1079
time_now = pygame.time.get_ticks()
1080
if (time_now - time_last) > frame_period:
1081
time_last = time_now
1082
else:
1083
continue
1084
1085
# increase iteration count
1086
iter_count = iter_count + 1
1087
# sys.stdout.write("\riteration {}".format(iter_count))
1088
# sys.stdout.flush()
1089
1090
# update the dominant decision for all robot
1091
deci_domi = np.argmax(pref_dist, axis=1)
1092
# update the groups
1093
robot_starter = 0
1094
robot_curr = 0
1095
groups = [[robot_curr]] # empty the group container
1096
# slice the loop at the connection before id '0' robot
1097
while (robot_key_neighbors[robot_curr][1] != robot_starter):
1098
robot_next = robot_key_neighbors[robot_curr][1]
1099
if (deci_domi[robot_curr]+1)%swarm_size == deci_domi[robot_next]:
1100
groups[-1].append(robot_next)
1101
else:
1102
groups.append([robot_next])
1103
robot_curr = robot_next
1104
# check if the two groups on the slicing point are in same group
1105
if len(groups) > 1 and (deci_domi[0] ==
1106
(deci_domi[robot_key_neighbors[0][0]]+1)%swarm_size):
1107
# combine the last group to the first group
1108
for i in reversed(groups[-1]):
1109
groups[0].insert(0,i)
1110
groups.pop(-1)
1111
# the decisions for the groups
1112
group_deci = [(deci_domi[group_temp[0]] -
1113
robot_loop_orders[group_temp[0]]) % swarm_size for group_temp in groups]
1114
# update group sizes for robots
1115
for group_temp in groups:
1116
group_temp_size = len(group_temp)
1117
for i in group_temp:
1118
robot_group_sizes[i] = group_temp_size
1119
1120
# update colors for the decisions
1121
if not color_initialized:
1122
color_initialized = True
1123
color_set = range(color_quantity)
1124
deci_set = set(group_deci)
1125
for deci in deci_set:
1126
if len(color_set) == 0:
1127
color_set = range(color_quantity)
1128
chosen_color = np.random.choice(color_set)
1129
color_set.remove(chosen_color)
1130
deci_colors[deci] = chosen_color
1131
color_assigns[chosen_color] = color_assigns[chosen_color] + 1
1132
else:
1133
# remove the color for a decision, if it's no longer the decision of any group
1134
deci_set = set(group_deci)
1135
for deci_temp in range(swarm_size):
1136
color_temp = deci_colors[deci_temp] # corresponding color for deci_temp
1137
if (color_temp != -1 and deci_temp not in deci_set):
1138
color_assigns[color_temp] = color_assigns[color_temp] - 1
1139
deci_colors[deci_temp] = -1
1140
# assign color for a new decision
1141
color_set = []
1142
for i in range(len(groups)):
1143
if deci_colors[group_deci[i]] == -1:
1144
if len(color_set) == 0:
1145
# construct a new color set
1146
color_assigns_min = min(color_assigns)
1147
for color_temp in range(color_quantity):
1148
if color_assigns[color_temp] == color_assigns_min:
1149
color_set.append(color_temp)
1150
# if here, the color set is good to go
1151
chosen_color = np.random.choice(color_set)
1152
color_set.remove(chosen_color)
1153
deci_colors[group_deci[i]] = chosen_color
1154
color_assigns[chosen_color] = color_assigns[chosen_color] + 1
1155
# update the colors for the groups and robots
1156
group_colors = []
1157
for i in range(len(groups)):
1158
color_temp = deci_colors[group_deci[i]]
1159
group_colors.append(color_temp)
1160
for j in groups[i]:
1161
robot_colors[j] = color_temp
1162
1163
# decision distribution evolution
1164
converged_all = True
1165
pref_dist_t = np.copy(pref_dist) # deep copy the 'pref_dist'
1166
for i in range(swarm_size):
1167
i_l = robot_key_neighbors[i][0] # index of neighbor on the left
1168
i_r = robot_key_neighbors[i][1] # index of neighbor on the right
1169
# shift distribution from left neighbor
1170
dist_l = list(pref_dist_t[i_l, 0:swarm_size-1])
1171
dist_l.insert(0, pref_dist_t[i_l,-1])
1172
dist_l = np.array(dist_l)
1173
# shift distribution from right neighbor
1174
dist_r = list(pref_dist_t[i_r, 1:swarm_size])
1175
dist_r.append(pref_dist_t[i_r,0])
1176
dist_r = np.array(dist_r)
1177
# deciding if two neighbors have converged ideas with host robot
1178
converged_l = False
1179
if ((deci_domi[i_l]+1)%swarm_size == deci_domi[i]): converged_l = True
1180
converged_r = False
1181
if ((deci_domi[i_r]-1)%swarm_size == deci_domi[i]): converged_r = True
1182
# weighted averaging depending on group property
1183
if converged_l and converged_r: # all three robots are locally converged
1184
# step 1: take equal weight average on all three distributions
1185
pref_dist[i] = dist_l + pref_dist_t[i] + dist_r
1186
dist_sum = np.sum(pref_dist[i])
1187
pref_dist[i] = pref_dist[i] / dist_sum
1188
# step 2: increase the unipolarity by applying the linear multiplier
1189
dist_diff = [np.linalg.norm(pref_dist_t[i]-dist_l, 1),
1190
np.linalg.norm(pref_dist_t[i]-dist_r, 1),
1191
np.linalg.norm(dist_l-dist_r, 1),]
1192
dist_diff_max = max(dist_diff) # maximum distribution difference
1193
if dist_diff_max < dist_diff_thres:
1194
dist_diff_ratio[i] = dist_diff_max/dist_diff_thres # for debugging
1195
small_end = 1.0/swarm_size * np.power(dist_diff_max/dist_diff_thres,
1196
dist_diff_power)
1197
large_end = 2.0/swarm_size - small_end
1198
# sort the magnitude of processed distribution
1199
dist_t = np.copy(pref_dist[i]) # temporary distribution
1200
sort_index = range(swarm_size)
1201
for j in range(swarm_size-1): # bubble sort, ascending order
1202
for k in range(swarm_size-1-j):
1203
if dist_t[k] > dist_t[k+1]:
1204
# exchange values in 'dist_t'
1205
temp = dist_t[k]
1206
dist_t[k] = dist_t[k+1]
1207
dist_t[k+1] = temp
1208
# exchange values in 'sort_index'
1209
temp = sort_index[k]
1210
sort_index[k] = sort_index[k+1]
1211
sort_index[k+1] = temp
1212
# applying the linear multiplier
1213
dist_sum = 0
1214
for j in range(swarm_size):
1215
multiplier = (small_end + float(j)/(swarm_size-1) *
1216
(large_end-small_end))
1217
pref_dist[i,sort_index[j]] = pref_dist[i,sort_index[j]] * multiplier
1218
dist_sum = np.sum(pref_dist[i])
1219
pref_dist[i] = pref_dist[i] / dist_sum
1220
else:
1221
dist_diff_ratio[i] = 1.0 # for debugging, ratio overflowed
1222
else: # at least one side has not converged yet
1223
if converged_all: converged_all = False
1224
dist_diff_ratio[i] = -1.0 # indicating linear multiplier was not used
1225
# take unequal weight in the averaging process based on group property
1226
group_size_l = robot_group_sizes[i_l]
1227
group_size_r = robot_group_sizes[i_r]
1228
# weight on left is group_size_l, on host is 1, on right is group_size_r
1229
pref_dist[i] = (robot_group_sizes[i_l] * dist_l +
1230
pref_dist_t[i] +
1231
robot_group_sizes[i_r] * dist_r)
1232
dist_sum = np.sum(pref_dist[i])
1233
pref_dist[i] = pref_dist[i] / dist_sum
1234
1235
# update the physics
1236
robot_poses_t = np.copy(robot_poses) # as old poses
1237
inter_curr = np.zeros(swarm_size)
1238
for i in range(swarm_size):
1239
i_l = robot_key_neighbors[i][0]
1240
i_r = robot_key_neighbors[i][1]
1241
# vectors
1242
vect_l = robot_poses_t[i_l] - robot_poses_t[i]
1243
vect_r = robot_poses_t[i_r] - robot_poses_t[i]
1244
vect_lr = robot_poses_t[i_r] - robot_poses_t[i_l]
1245
# distances
1246
dist_l = np.linalg.norm(vect_l)
1247
dist_r = np.linalg.norm(vect_r)
1248
dist_lr = np.linalg.norm(vect_lr)
1249
# unit vectors
1250
u_vect_l = vect_l / dist_l
1251
u_vect_r = vect_r / dist_r
1252
u_vect_in = np.array([-vect_lr[1], vect_lr[0]]) / dist_lr
1253
# calculate current interior angle
1254
inter_curr[i] = math.acos(np.around(
1255
np.dot(vect_l, vect_r) / (dist_l * dist_r), 6))
1256
if np.cross(vect_r, vect_l) < 0:
1257
inter_curr[i] = 2*math.pi - inter_curr[i]
1258
# feedback vector for the SMA algorithm
1259
fb_vect = np.zeros(2)
1260
fb_vect = fb_vect + (dist_l - desired_space) * linear_const * u_vect_l
1261
fb_vect = fb_vect + (dist_r - desired_space) * linear_const * u_vect_r
1262
if formation_stretched:
1263
fb_vect = (fb_vect +
1264
(inter_target[deci_domi[i]] - inter_curr[i]) * bend_const * u_vect_in)
1265
else:
1266
fb_vect = (fb_vect +
1267
(inter_target_circle - inter_curr[i]) * bend_const * u_vect_in)
1268
# update one step of position
1269
robot_poses[i] = robot_poses_t[i] + disp_coef * fb_vect
1270
1271
# check if the stretching process is done
1272
if not formation_stretched:
1273
formation_stretched = True
1274
for i in range(swarm_size):
1275
if abs(inter_curr[i] - inter_target_circle) > formation_stretched_err:
1276
formation_stretched = False
1277
break
1278
if formation_stretched:
1279
# stretching finished
1280
print("loop is reshaping to " + shape_catalog[shape_decision] + " ...")
1281
1282
# update the graphics
1283
disp_poses_update()
1284
screen.fill(color_white)
1285
# draw the connections first
1286
for i in range(swarm_size):
1287
i_next = robot_key_neighbors[i][1]
1288
if ((deci_domi[i]+1)%swarm_size == deci_domi[i_next]):
1289
pygame.draw.line(screen, distinct_color_set[robot_colors[i]],
1290
disp_poses[i], disp_poses[i_next], conn_width)
1291
else:
1292
pygame.draw.line(screen, color_black, disp_poses[i], disp_poses[i_next],
1293
conn_width)
1294
# draw the robots
1295
for i in range(swarm_size):
1296
pygame.draw.circle(screen, distinct_color_set[robot_colors[i]], disp_poses[i],
1297
robot_size, 0)
1298
pygame.display.update()
1299
1300
# calculate the maximum error of interior angle
1301
inter_err_max = 0
1302
for i in range(swarm_size):
1303
err_curr = abs(inter_curr[i] - inter_target[deci_domi[i]])
1304
if err_curr > inter_err_max: inter_err_max = err_curr
1305
1306
# check exit condition of simulation 3
1307
if converged_all and inter_err_max < inter_err_thres:
1308
print("simulation 3 is finished")
1309
if manual_mode: raw_input("<Press Enter to continue>")
1310
print("") # empty line
1311
break
1312
1313
1314
1315