Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
yangliu28
GitHub Repository: yangliu28/swarm_formation_sim
Path: blob/master/demo_1.py
104 views
1
# This first demo shows how a robot swarm can autonomously choose a loop shape and form the
2
# shape in a distributed manner, without central control. Two consensus processes, decision
3
# making and role assignment, are performed consecutively with a fixed but arbitrary network.
4
5
# input arguments:
6
# '-n': number of robots
7
# '--manual': manual mode, press ENTER to proceed between simulations
8
9
# Description:
10
# Starting dispersed in random positions, the swarm aggregates together arbitrarily to form
11
# a connected network. Consensus decision making is performed with this network fixed, making
12
# a collective decision of which loop the swarm should form. Then with the same network, role
13
# assignment is performed, assigning the target positions to each robot. After these two
14
# consensus processes are done, the swarm disperses and aggregates again, this time aiming to
15
# form a loop with robots at their designated positions. The climbing method is used to permutate
16
# the robots on the loop. When the robots get their target positions, they dyanmically adjust
17
# the local shape so the loop deforms to the target one. The above steps will be ran repeatedly.
18
19
# the simulations that run consecutively
20
# Simulation 1: aggregate together to form a random network
21
# Simulation 2: consensus decision making of target loop shape
22
# Simulation 3: consensus role assignment for the loop shape
23
# Simulation 4: loop formation with designated role assignment
24
# Simulation 5: loop reshaping to chosen shape
25
26
# message relay in the role assignment
27
# Note that message transmission is simulated only in the role assignment. Because message
28
# relay is implemented to make this simulation possible, therefore communication between robots
29
# becomes important, and steps of message transmissions are simulated. However the delay caused
30
# by communication is skipped in other simulations.
31
32
# "seed" robot mechanism
33
# In simulation 1, a new mechanism is added to accelerate the aggregation. Previous, each
34
# robot in the swarm can start a new group with another robot. The result is that, often there
35
# are too many small local groups spread out the space, decrease the chance of a large group
36
# being formed. The new method is asigning certain number of robots to be "seed" robot. Only
37
# seed robot can initialize the forming of a new group, so as to avoid too many local groups.
38
# The percentage of seed robots is a new parameter to study, small percentage results in less
39
# robustness of the aggregation process, large percentage results in slow aggregation process.
40
41
# When a robot travels almost parallel to the boundaries, sometimes it takes unnecessarily long
42
# time for it to reach a group. To avoid that, every time a robot is bounced away by the wall,
43
# if the leaving direction is too perpendicular, a deviation angle is added to deviation the
44
# robot.
45
46
# 04/11/2018
47
# In simulation of aggregating to random network, the robots used to need only one connection
48
# to stay stable in the group. The result is that the final network topology is always tree
49
# branch like. Most robots are only serial connected. This topology is often rated as low
50
# connectivity, and takes longer time for the consensus processes. Although the unintended
51
# advantage is that the robots are more easily caught in the tree network.
52
# Advised by Dr. Lee, it is better that the final network look like the triangle grid network.
53
# In this way the swarm robots will have more evenly distributed coverage over the space.
54
# To implement this: when in a group, if a robot has two or more connections, it moves to the
55
# destination calculated in the old way. If it has only one connection, it will rotate around
56
# counter-clockwise around that neighbor robot, untill it finds another neighbor. Again, there
57
# is an exception that the group itself has only two members.
58
59
# 05/19/2018
60
# Change color plan for the shape formation in simulation 1 and 4, use red color for seed robot
61
# regardless of its states. Use black for dominant group robots, and use grey for the rest.
62
# The same to the simulation 1 in demo 2.
63
# As I later find out, it's not the color that make seed robot hard to distinguish, it's because
64
# the size of the dot is small.
65
66
67
from __future__ import print_function
68
import pygame
69
import sys, os, getopt, math, random
70
import numpy as np
71
import pickle # for storing variables
72
73
swarm_size = 30 # default size of the swarm
74
manual_mode = False # manually press enter key to proceed between simulations
75
76
# read command line options
77
try:
78
opts, args = getopt.getopt(sys.argv[1:], 'n:', ['manual'])
79
except getopt.GetoptError as err:
80
print(str(err))
81
sys.exit()
82
for opt,arg in opts:
83
if opt == '-n':
84
swarm_size = int(arg)
85
elif opt == '--manual':
86
manual_mode = True
87
88
# conversion between physical and display world sizes
89
# To best display any robot swarm in its appropriate window size, and have enough physical
90
# space for the robots to move around, it has been made that the ratio from unit world size
91
# to unit display size is fixed. The desired physical space between robots when they do shape
92
# formation is also fixed. So a small swarm will have small physical world, and a linearly
93
# small display window; vice versa for a large swarm.
94
# If the size of the swarm is proportional to the side length of the world, the area of the
95
# world will grow too fast. If the swarm size is proportional to the area of the world, when
96
# the size of the swarm grow large, it won't be able to be fitted in if performing a line or
97
# circle formation. A compromise is to make swarm size proportional to the side length to the
98
# power exponent between 1 and 2.
99
power_exponent = 1.3 # between 1.0 and 2.0
100
# the larger the parameter, the slower the windows grows with swarm size; vice versa
101
# for converting from physical world to display world
102
pixels_per_length = 50 # this is to be fixed
103
# calculate world_side_coef from a desired screen size for 30 robots
104
def cal_world_side_coef():
105
desired_screen_size = 400 # desired screen size for 30 robots
106
desired_world_size = float(desired_screen_size) / pixels_per_length
107
return desired_world_size / pow(30, 1/power_exponent)
108
world_side_coef = cal_world_side_coef()
109
world_side_length = world_side_coef * pow(swarm_size, 1/power_exponent)
110
world_size = (world_side_length, world_side_length) # square physical world
111
112
# screen size calculated from world size
113
screen_side_length = int(pixels_per_length * world_side_length)
114
screen_size = (screen_side_length, screen_side_length) # square display world
115
116
# formation configuration
117
comm_range = 0.65 # communication range in the world
118
desired_space_ratio = 0.8 # ratio of the desired space to the communication range
119
# should be larger than 1/1.414=0.71, to avoid connections crossing each other
120
desired_space = comm_range * desired_space_ratio
121
# deviate robot heading, so as to avoid robot travlling perpendicular to the walls
122
perp_thres = math.pi/18 # threshold, range from the perpendicular line
123
devia_angle = math.pi/9 # deviate these much angle from perpendicualr line
124
# consensus configuration
125
loop_folder = "loop-data2" # folder to store the loop shapes
126
shape_catalog = ["airplane", "circle", "cross", "goblet", "hand", "K", "lamp", "square",
127
"star", "triangle", "wrench"]
128
shape_quantity = len(shape_catalog) # the number of decisions
129
shape_decision = -1 # the index of chosen decision, in range(shape_quantity)
130
# also the index in shape_catalog
131
assignment_scheme = np.zeros(swarm_size)
132
# variable to force shape to different choices, for video recording
133
force_shape_set = range(shape_quantity)
134
135
# robot properties
136
robot_poses = np.random.rand(swarm_size, 2) * world_side_length # initialize the robot poses
137
dist_table = np.zeros((swarm_size, swarm_size)) # distances between robots
138
conn_table = np.zeros((swarm_size, swarm_size)) # connections between robots
139
# 0 for disconnected, 1 for connected
140
conn_lists = [[] for i in range(swarm_size)] # lists of robots connected
141
# function for all simulations, update the distances and connections between the robots
142
def dist_conn_update():
143
global dist_table
144
global conn_table
145
global conn_lists
146
conn_lists = [[] for i in range(swarm_size)] # empty the lists
147
for i in range(swarm_size):
148
for j in range(i+1, swarm_size):
149
dist_temp = np.linalg.norm(robot_poses[i] - robot_poses[j])
150
dist_table[i,j] = dist_temp
151
dist_table[j,i] = dist_temp
152
if dist_temp > comm_range:
153
conn_table[i,j] = 0
154
conn_table[j,i] = 0
155
else:
156
conn_table[i,j] = 1
157
conn_table[j,i] = 1
158
conn_lists[i].append(j)
159
conn_lists[j].append(i)
160
dist_conn_update() # update the distances and connections
161
disp_poses = [] # display positions
162
# function for all simulations, update the display positions
163
def disp_poses_update():
164
global disp_poses
165
poses_temp = robot_poses / world_side_length
166
poses_temp[:,1] = 1.0 - poses_temp[:,1]
167
poses_temp = poses_temp * screen_side_length
168
disp_poses = poses_temp.astype(int) # convert to int and assign to disp_poses
169
disp_poses_update()
170
# deciding the seed robots, used in simulations with moving robots
171
seed_percentage = 0.1 # the percentage of seed robots in the swarm
172
seed_quantity = min(max(int(swarm_size*seed_percentage), 1), swarm_size)
173
# no smaller than 1, and no larger than swarm_size
174
robot_seeds = [False for i in range(swarm_size)] # whether a robot is a seed robot
175
# only seed robot can initialize the forming a new group
176
seed_list_temp = np.arange(swarm_size)
177
np.random.shuffle(seed_list_temp)
178
for i in seed_list_temp[:seed_quantity]:
179
robot_seeds[i] = True
180
181
# visualization configuration
182
color_white = (255,255,255)
183
color_black = (0,0,0)
184
color_grey = (128,128,128)
185
color_red = (255,0,0)
186
# distinct_color_set = ((230,25,75), (60,180,75), (255,225,25), (0,130,200), (245,130,48),
187
# (145,30,180), (70,240,240), (240,50,230), (210,245,60), (250,190,190),
188
# (0,128,128), (230,190,255), (170,110,40), (255,250,200), (128,0,0),
189
# (170,255,195), (128,128,0), (255,215,180), (0,0,128), (128,128,128))
190
distinct_color_set = ((230,25,75), (60,180,75), (255,225,25), (0,130,200), (245,130,48),
191
(145,30,180), (70,240,240), (240,50,230), (210,245,60), (250,190,190),
192
(0,128,128), (230,190,255), (170,110,40), (128,0,0),
193
(170,255,195), (128,128,0), (0,0,128))
194
color_quantity = 17
195
# sizes for formation simulations
196
robot_size_formation = 5 # robot size in formation simulations
197
robot_width_empty = 2
198
conn_width_formation = 2 # connection line width in formation simulations
199
# sizes for consensus simulations
200
robot_size_consensus = 7 # robot size in consensus simulatiosn
201
conn_width_thin_consensus = 2 # thin connection line in consensus simulations
202
conn_width_thick_consensus = 4 # thick connection line in consensus simulations
203
robot_ring_size = 9 # extra ring on robot in consensus simulations
204
# the sizes for formation and consensus simulations are set to same for visual consistency
205
206
# set up the simulation window
207
pygame.init()
208
font = pygame.font.SysFont("Cabin", 12)
209
icon = pygame.image.load("icon_geometry_art.jpg")
210
pygame.display.set_icon(icon)
211
screen = pygame.display.set_mode(screen_size)
212
pygame.display.set_caption("Demo 1")
213
# draw the network
214
screen.fill(color_white)
215
for i in range(swarm_size):
216
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size_formation,
217
robot_width_empty)
218
# pygame.draw.circle(screen, color_black, disp_poses[i],
219
# int(comm_range*pixels_per_length), 1)
220
pygame.display.update()
221
222
# pause to check the network before the simulations, or for screen recording
223
raw_input("<Press Enter to continue>")
224
225
# function for simulation 1 and 4, group robots by their group ids, and find the largest group
226
def S14_robot_grouping(robot_list, robot_group_ids, groups):
227
# the input list 'robot_list' should not be empty
228
groups_temp = {} # key is group id, value is list of robots
229
for i in robot_list:
230
group_id_temp = robot_group_ids[i]
231
if group_id_temp not in groups_temp.keys():
232
groups_temp[group_id_temp] = [i]
233
else:
234
groups_temp[group_id_temp].append(i)
235
group_id_max = -1 # the group with most members
236
# regardless of only one group or multiple groups in groups_temp
237
if len(groups_temp.keys()) > 1: # there is more than one group
238
# find the largest group and disassemble the rest
239
group_id_max = groups_temp.keys()[0]
240
size_max = len(groups[group_id_max][0])
241
for group_id_temp in groups_temp.keys()[1:]:
242
size_temp = len(groups[group_id_temp][0])
243
if size_temp > size_max:
244
group_id_max = group_id_temp
245
size_max = size_temp
246
else: # only one group, automatically the largest one
247
group_id_max = groups_temp.keys()[0]
248
return groups_temp, group_id_max
249
250
# function for simulation 1 and 4, find the closest robot to a host robot
251
# use global variable "dist_table"
252
def S14_closest_robot(robot_host, robot_neighbors):
253
# "robot_host": the robot to measure distance from
254
# "robot_neighbors": a list of robots to be compared with
255
robot_closest = robot_neighbors[0]
256
dist_closest = dist_table[robot_host,robot_closest]
257
for i in robot_neighbors[1:]:
258
dist_temp = dist_table[robot_host,i]
259
if dist_temp < dist_closest:
260
robot_closest = i
261
dist_closest = dist_temp
262
return robot_closest
263
264
# general function to normalize a numpy vector
265
def normalize(v):
266
norm = np.linalg.norm(v)
267
if norm == 0:
268
return v
269
return v/norm
270
271
# general function to reset radian angle to [-pi, pi)
272
def reset_radian(radian):
273
while radian >= math.pi:
274
radian = radian - 2*math.pi
275
while radian < -math.pi:
276
radian = radian + 2*math.pi
277
return radian
278
279
# general function to steer robot away from wall if out of boundary (following physics)
280
# use global variable "world_side_length"
281
def robot_boundary_check(robot_pos, robot_ori):
282
new_ori = robot_ori
283
if robot_pos[0] >= world_side_length: # outside of right boundary
284
if math.cos(new_ori) > 0:
285
new_ori = reset_radian(2*(math.pi/2) - new_ori)
286
# further check if new angle is too much perpendicular
287
if new_ori > 0:
288
if (math.pi - new_ori) < perp_thres:
289
new_ori = new_ori - devia_angle
290
else:
291
if (new_ori + math.pi) < perp_thres:
292
new_ori = new_ori + devia_angle
293
elif robot_pos[0] <= 0: # outside of left boundary
294
if math.cos(new_ori) < 0:
295
new_ori = reset_radian(2*(math.pi/2) - new_ori)
296
if new_ori > 0:
297
if new_ori < perp_thres:
298
new_ori = new_ori + devia_angle
299
else:
300
if (-new_ori) < perp_thres:
301
new_ori = new_ori - devia_angle
302
if robot_pos[1] >= world_side_length: # outside of top boundary
303
if math.sin(new_ori) > 0:
304
new_ori = reset_radian(2*(0) - new_ori)
305
if new_ori > -math.pi/2:
306
if (new_ori + math.pi/2) < perp_thres:
307
new_ori = new_ori + devia_angle
308
else:
309
if (-math.pi/2 - new_ori) < perp_thres:
310
new_ori = new_ori - devia_angle
311
elif robot_pos[1] <= 0: # outside of bottom boundary
312
if math.sin(new_ori) < 0:
313
new_ori = reset_radian(2*(0) - new_ori)
314
if new_ori > math.pi/2:
315
if (new_ori - math.pi/2) < perp_thres:
316
new_ori = new_ori + devia_angle
317
else:
318
if (math.pi/2 - new_ori) < perp_thres:
319
new_ori = new_ori - devia_angle
320
return new_ori
321
322
# # general function to steer robot away from wall if out of boundary (in random direction)
323
# # use global variable "world_side_length"
324
# def robot_boundary_check(robot_pos, robot_ori):
325
# new_ori = robot_ori
326
# if robot_pos[0] >= world_side_length: # outside of right boundary
327
# if math.cos(new_ori) > 0:
328
# new_ori = reset_radian(math.pi/2 + np.random.uniform(0,math.pi))
329
# elif robot_pos[0] <= 0: # outside of left boundary
330
# if math.cos(new_ori) < 0:
331
# new_ori = reset_radian(-math.pi/2 + np.random.uniform(0,math.pi))
332
# if robot_pos[1] >= world_side_length: # outside of top boundary
333
# if math.sin(new_ori) > 0:
334
# new_ori = reset_radian(-math.pi + np.random.uniform(0,math.pi))
335
# elif robot_pos[1] <= 0: # outside of bottom boundary
336
# if math.sin(new_ori) < 0:
337
# new_ori = reset_radian(0 + np.random.uniform(0,math.pi))
338
# return new_ori
339
340
# main loop of the program that run the set of simulations infinitely
341
# this loop does not exit unless error thrown out or manually terminated from terminal
342
while True:
343
344
########### simulation 1: aggregate together to form a random network ###########
345
346
print("##### simulation 1: network aggregation #####")
347
348
# (switching from using 'status' to using 'state': state here refers to being in one
349
# condition from many options, like whether in a group, whether available for connection.
350
# Status usually refers in a series of predefined stages, which goes one way from start
351
# to the end, like referring the progress of a project. While my state may jump back and
352
# forth. It's controversial of which one to use, but 'state' is what I choose.)
353
354
# robot perperties
355
# all robots start with state '-1', wandering around and ignoring connections
356
robot_states = np.array([-1 for i in range(swarm_size)])
357
# '-1' for being single, moving around, not available for connection
358
# '0' for being single, moving around, available for connection
359
# '1' for in a group, adjust position for maintaining connections
360
n1_life_lower = 2 # inclusive
361
n1_life_upper = 6 # exclusive
362
robot_n1_lives = np.random.uniform(n1_life_lower, n1_life_upper, swarm_size)
363
robot_oris = np.random.rand(swarm_size) * 2 * math.pi - math.pi # in range of [-pi, pi)
364
365
# group properties
366
groups = {}
367
# key is the group id, value is a list, in the list:
368
# [0]: a list of robots in the group
369
# [1]: remaining life time of the group
370
# [2]: whether or not being the dominant group
371
life_incre = 5 # number of seconds added to the life of a group when new robot joins
372
group_id_upper = swarm_size # group id is integer randomly chosen in [0, group_id_upper)
373
robot_group_ids = np.array([-1 for i in range(swarm_size)]) # group id of the robots
374
# '-1' for not in a group
375
376
# use moving distance in each simulation step, instead of robot velocity
377
# so to make it independent of simulation frequency control
378
step_moving_dist = 0.05 # should be smaller than destination distance error
379
destination_error = 0.08
380
mov_vec_ratio = 0.5 # ratio used when calculating mov vector
381
382
# the loop for simulation 1
383
sim_haulted = False
384
time_last = pygame.time.get_ticks()
385
time_now = time_last
386
frame_period = 50
387
sim_freq_control = True
388
iter_count = 0
389
# sys.stdout.write("iteration {}".format(iter_count)) # did nothing in iteration 0
390
print("swarm robots are aggregating to one network ...")
391
swarm_aggregated = False
392
ending_period = 3.0 # leave this much time to let robots settle after aggregation is dine
393
# # progress bar
394
# prog_bar_len = 30
395
# prog_pos = 0
396
# prog_step = 1
397
# prog_period = 1000
398
# prog_update_freq = int(prog_period/frame_period)
399
# prog_counter = 0
400
# sys.stdout.write("[" + prog_pos*"-" + "#" + (prog_bar_len-(prog_pos+1))*"-" + "]\r")
401
# sys.stdout.flush()
402
while True:
403
# close window button to exit the entire program;
404
# space key to pause this simulation
405
for event in pygame.event.get():
406
if event.type == pygame.QUIT: # close window button is clicked
407
print("program exit in simulation 1")
408
sys.exit() # exit the entire program
409
if event.type == pygame.KEYUP:
410
if event.key == pygame.K_SPACE:
411
sim_haulted = not sim_haulted # reverse the pause flag
412
if sim_haulted: continue
413
414
# simulation frequency control
415
if sim_freq_control:
416
time_now = pygame.time.get_ticks()
417
if (time_now - time_last) > frame_period:
418
time_last = time_now
419
else:
420
continue
421
422
# increase iteration count
423
iter_count = iter_count + 1
424
# sys.stdout.write("\riteration {}".format(iter_count))
425
# sys.stdout.flush()
426
427
# # progress bar
428
# prog_counter = prog_counter + 1
429
# if prog_counter > prog_update_freq:
430
# prog_counter == 0
431
# if prog_pos == 0: prog_step = 1 # goes forward
432
# elif prog_pos == (prog_bar_len-1): prog_step = -1 # goes backward
433
# prog_pos = prog_pos + prog_step
434
# sys.stdout.write("[" + prog_pos*"-" + "#" +
435
# (prog_bar_len-(prog_pos+1))*"-" + "]\r")
436
# sys.stdout.flush()
437
438
# state transition variables
439
st_n1to0 = [] # robot '-1' gets back to '0' after life time ends
440
# list of robots changing to '0' from '-1'
441
st_gton1 = [] # group disassembles either life expires, or triggered by others
442
# list of groups to be disassembled
443
st_0to1_join = {} # robot '0' detects robot '1' group, join the group
444
# key is the robot '0', value is the group id
445
st_0to1_new = {} # robot '0' detects another robot '0', forming new group
446
# key is the robot '0', value is the other neighbor robot '0'
447
448
# update the "relations" of the robots
449
dist_conn_update()
450
# check any state transition, and schedule the tasks
451
for i in range(swarm_size):
452
if robot_states[i] == -1: # for host robot with state '-1'
453
if robot_n1_lives[i] < 0:
454
st_n1to0.append(i) # life of '-1' ends, becoming '0'
455
else:
456
conn_temp = conn_lists[i][:] # a list of connections with only state '1'
457
for j in conn_lists[i]:
458
if robot_states[j] != 1:
459
conn_temp.remove(j)
460
if len(conn_temp) != 0:
461
groups_local, group_id_max = S14_robot_grouping(conn_temp,
462
robot_group_ids, groups)
463
# disassmeble all groups except the largest one
464
for group_id_temp in groups_local.keys():
465
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
466
st_gton1.append(group_id_temp) # schedule to disassemble this group
467
# find the closest neighbor in groups_local[group_id_max]
468
robot_closest = S14_closest_robot(i, groups_local[group_id_max])
469
# change moving direction opposing the closest robot
470
vect_temp = robot_poses[i] - robot_poses[robot_closest]
471
robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
472
elif robot_states[i] == 0: # for host robot with state '0'
473
state1_list = [] # list of state '1' robots in the connection list
474
state0_list = [] # list of state '0' robots in teh connection list
475
for j in conn_lists[i]:
476
# ignore state '-1' robots
477
if robot_states[j] == 1:
478
state1_list.append(j)
479
elif robot_states[j] == 0:
480
state0_list.append(j)
481
if len(state1_list) != 0:
482
# there is state '1' robot in the list, ignoring state '0' robot
483
groups_local, group_id_max = S14_robot_grouping(state1_list,
484
robot_group_ids, groups)
485
# disassmeble all groups except the largest one
486
for group_id_temp in groups_local.keys():
487
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
488
st_gton1.append(group_id_temp) # schedule to disassemble this group
489
# join the the group with the most members
490
st_0to1_join[i] = group_id_max
491
elif len(state0_list) != 0:
492
# there is no robot '1', but has robot '0'
493
# find the closest robot, schedule to start a new group with it
494
st_0to1_new[i] = S14_closest_robot(i, state0_list)
495
elif robot_states[i] == 1: # for host robot with state '1'
496
conn_temp = conn_lists[i][:] # a list of connections with only state '1'
497
has_other_group = False # whether there is robot '1' from other group
498
host_group_id = robot_group_ids[i] # group id of host robot
499
for j in conn_lists[i]:
500
if robot_states[j] != 1:
501
conn_temp.remove(j)
502
else:
503
if robot_group_ids[j] != host_group_id:
504
has_other_group = True
505
# disassemble the smaller groups
506
if has_other_group:
507
groups_local, group_id_max = S14_robot_grouping(conn_temp,
508
robot_group_ids, groups)
509
# disassmeble all groups except the largest one
510
for group_id_temp in groups_local.keys():
511
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
512
st_gton1.append(group_id_temp) # schedule to disassemble this group
513
else: # to be tested and deleted
514
print("robot state error")
515
sys.exit()
516
517
# check the life time of the groups; if expired, schedule disassemble
518
for group_id_temp in groups.keys():
519
if groups[group_id_temp][1] < 0: # life time of a group ends
520
if group_id_temp not in st_gton1:
521
st_gton1.append(group_id_temp)
522
523
# process the scheduled state transitions, different transition has different priority
524
# 1.st_0to1_join, robot '0' joins a group, becomes '1'
525
for robot_temp in st_0to1_join.keys():
526
group_id_temp = st_0to1_join[robot_temp] # the id of the group to join
527
# update properties of the robot
528
robot_states[robot_temp] = 1
529
robot_group_ids[robot_temp] = group_id_temp
530
# update properties of the group
531
groups[group_id_temp][0].append(robot_temp)
532
groups[group_id_temp][1] = groups[group_id_temp][1] + life_incre
533
# 2.st_gton1
534
for group_id_temp in st_gton1:
535
for robot_temp in groups[group_id_temp][0]:
536
robot_states[robot_temp] = -1
537
robot_n1_lives[robot_temp] = np.random.uniform(n1_life_lower, n1_life_upper)
538
robot_group_ids[robot_temp] = -1
539
robot_oris[robot_temp] = np.random.rand() * 2 * math.pi - math.pi
540
groups.pop(group_id_temp)
541
# 3.st_0to1_new
542
while len(st_0to1_new.keys()) != 0:
543
pair0 = st_0to1_new.keys()[0]
544
pair1 = st_0to1_new[pair0]
545
st_0to1_new.pop(pair0)
546
if (pair1 in st_0to1_new.keys()) and (st_0to1_new[pair1] == pair0):
547
st_0to1_new.pop(pair1)
548
# only forming a group if there is at least one seed robot in the pair
549
if robot_seeds[pair0] or robot_seeds[pair1]:
550
# forming new group for robot pair0 and pair1
551
group_id_temp = np.random.randint(0, group_id_upper)
552
while group_id_temp in groups.keys():
553
group_id_temp = np.random.randint(0, group_id_upper)
554
# update properties of the robots
555
robot_states[pair0] = 1
556
robot_states[pair1] = 1
557
robot_group_ids[pair0] = group_id_temp
558
robot_group_ids[pair1] = group_id_temp
559
# update properties of the group
560
groups[group_id_temp] = [[pair0, pair1], life_incre*2, False]
561
# 4.st_n1to0
562
for robot_temp in st_n1to0:
563
robot_states[robot_temp] = 0
564
565
# check if a group becomes dominant
566
for group_id_temp in groups.keys():
567
if len(groups[group_id_temp][0]) > swarm_size/2.0:
568
groups[group_id_temp][2] = True
569
else:
570
groups[group_id_temp][2] = False
571
572
# local connection lists for state '1' robots
573
local_conn_lists = [[] for i in range(swarm_size)] # connections in same group
574
robot_poses_t = np.copy(robot_poses) # as old poses
575
# update the physics
576
for i in range(swarm_size):
577
# change move direction only for robot '1', for adjusting location in group
578
if robot_states[i] == 1:
579
# find the neighbors in the same group
580
host_group_id = robot_group_ids[i]
581
for j in conn_lists[i]:
582
if (robot_states[j] == 1) and (robot_group_ids[j] == host_group_id):
583
local_conn_lists[i].append(j)
584
if len(local_conn_lists[i]) == 0: # should not happen after parameter tuning
585
printf("robot {} loses its group {}".format(i, host_group_id))
586
sys.exit()
587
# calculating the moving direction, based on neighbor situation
588
if (len(local_conn_lists[i]) == 1) and (len(groups[host_group_id][0]) > 2):
589
# If the robot has only one neighbor, and it is not the case that the group
590
# has only members, then the robot will try to secure another neighbor, by
591
# rotating counter-clockwise around this only neighbor.
592
center = local_conn_lists[i][0] # the center robot
593
# use the triangle of (desired_space, dist_table[i,center], step_moving_dist)
594
if dist_table[i,center] > (desired_space + step_moving_dist):
595
# moving toward the center robot
596
robot_oris[i] = math.atan2(robot_poses_t[center,1] - robot_poses_t[i,1],
597
robot_poses_t[center,0] - robot_poses_t[i,0])
598
elif (dist_table[i,center] + step_moving_dist) < desired_space:
599
# moving away from the center robot
600
robot_oris[i] = math.atan2(robot_poses_t[i,1] - robot_poses_t[center,1],
601
robot_poses_t[i,0] - robot_poses_t[center,0])
602
else:
603
# moving tangent along the circle of radius of "desired_space"
604
robot_oris[i] = math.atan2(robot_poses_t[i,1] - robot_poses_t[center,1],
605
robot_poses_t[i,0] - robot_poses_t[center,0])
606
# interior angle between dist_table[i,center] and step_moving_dist
607
int_angle_temp = math.acos((math.pow(dist_table[i,center],2) +
608
math.pow(step_moving_dist,2) - math.pow(desired_space,2)) /
609
(2.0*dist_table[i,center]*step_moving_dist))
610
robot_oris[i] = reset_radian(robot_oris[i] + (math.pi - int_angle_temp))
611
else: # the normal situation
612
# calculate the moving vector, and check if destination is within error range
613
mov_vec = np.zeros(2)
614
for j in local_conn_lists[i]: # accumulate the influence from all neighbors
615
mov_vec = mov_vec + (mov_vec_ratio * (dist_table[i,j] - desired_space) *
616
normalize(robot_poses_t[j] - robot_poses_t[i]))
617
if np.linalg.norm(mov_vec) < destination_error:
618
continue # skip the physics update if within destination error
619
else:
620
robot_oris[i] = math.atan2(mov_vec[1], mov_vec[0]) # change direction
621
# check if out of boundaries
622
robot_oris[i] = robot_boundary_check(robot_poses_t[i], robot_oris[i])
623
# update one step of move
624
robot_poses[i] = robot_poses_t[i] + (step_moving_dist *
625
np.array([math.cos(robot_oris[i]), math.sin(robot_oris[i])]))
626
627
# update the graphics
628
disp_poses_update()
629
screen.fill(color_white)
630
# draw the robots of states '-1' and '0'
631
for i in range(swarm_size):
632
if robot_seeds[i]:
633
color_temp = color_red
634
else:
635
color_temp = color_grey
636
if robot_states[i] == -1: # empty circle for state '-1' robot
637
pygame.draw.circle(screen, color_temp, disp_poses[i],
638
robot_size_formation, robot_width_empty)
639
elif robot_states[i] == 0: # full circle for state '0' robot
640
pygame.draw.circle(screen, color_temp, disp_poses[i],
641
robot_size_formation, 0)
642
# draw the in-group robots by each group
643
for group_id_temp in groups.keys():
644
if groups[group_id_temp][2]:
645
# highlight the dominant group with black color
646
color_group = color_black
647
else:
648
color_group = color_grey
649
conn_draw_sets = [] # avoid draw same connection two times
650
# draw the robots and connections in the group
651
for i in groups[group_id_temp][0]:
652
for j in local_conn_lists[i]:
653
if set([i,j]) not in conn_draw_sets:
654
pygame.draw.line(screen, color_group, disp_poses[i],
655
disp_poses[j], conn_width_formation)
656
conn_draw_sets.append(set([i,j]))
657
# draw robots in the group
658
if robot_seeds[i]: # force color red for seed robot
659
pygame.draw.circle(screen, color_red, disp_poses[i],
660
robot_size_formation, 0)
661
else:
662
pygame.draw.circle(screen, color_group, disp_poses[i],
663
robot_size_formation, 0)
664
pygame.display.update()
665
666
# reduce life time of robot '-1' and groups
667
for i in range(swarm_size):
668
if robot_states[i] == -1:
669
robot_n1_lives[i] = robot_n1_lives[i] - frame_period/1000.0
670
for group_id_temp in groups.keys():
671
if not groups[group_id_temp][2]: # skip dominant group
672
groups[group_id_temp][1] = groups[group_id_temp][1] - frame_period/1000.0
673
674
# check exit condition of simulation 1
675
if not swarm_aggregated:
676
if (len(groups.keys()) == 1) and (len(groups.values()[0][0]) == swarm_size):
677
swarm_aggregated = True # once aggregated, there is no turning back
678
if swarm_aggregated:
679
if ending_period <= 0:
680
print("simulation 1 is finished")
681
if manual_mode: raw_input("<Press Enter to continue>")
682
print("") # empty line
683
break
684
else:
685
ending_period = ending_period - frame_period/1000.0
686
687
# # store the variable "robot_poses"
688
# # tmp_filepath = os.path.join('tmp', 'demo1_30_robot_poses')
689
# tmp_filepath = os.path.join('tmp', 'demo1_100_robot_poses')
690
# with open(tmp_filepath, 'w') as f:
691
# pickle.dump(robot_poses, f)
692
# raw_input("<Press Enter to continue>")
693
# break
694
695
########### simulation 2: consensus decision making of target loop shape ###########
696
697
# # restore variable "robot_poses"
698
# # tmp_filepath = os.path.join('tmp', 'demo1_30_robot_poses')
699
# tmp_filepath = os.path.join('tmp', 'demo1_100_robot_poses')
700
# with open(tmp_filepath) as f:
701
# robot_poses = pickle.load(f)
702
703
print("##### simulation 2: decision making #####")
704
# "dist" in the variable may also refer to distribution
705
706
dist_conn_update() # need to update the network only once
707
# draw the network first time
708
disp_poses_update()
709
screen.fill(color_white)
710
for i in range(swarm_size):
711
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size_consensus, 0)
712
for j in range(i+1, swarm_size):
713
if conn_table[i,j]:
714
pygame.draw.line(screen, color_black, disp_poses[i], disp_poses[j],
715
conn_width_thin_consensus)
716
pygame.display.update()
717
718
# initialize the decision making variables
719
shape_decision = -1
720
deci_dist = np.random.rand(swarm_size, shape_quantity)
721
sum_temp = np.sum(deci_dist, axis=1)
722
for i in range(swarm_size):
723
deci_dist[i] = deci_dist[i] / sum_temp[i]
724
deci_domi = np.argmax(deci_dist, axis=1)
725
groups = [] # robots reach local consensus are in same group
726
robot_group_sizes = [0 for i in range(swarm_size)] # group size for each robot
727
# color assignments for the robots and decisions
728
color_initialized = False # whether color assignment has been done for the first time
729
deci_colors = [-1 for i in range(shape_quantity)]
730
color_assigns = [0 for i in range(color_quantity)]
731
group_colors = []
732
robot_colors = [0 for i in range(swarm_size)]
733
# decision making control variables
734
dist_diff_thres = 0.3
735
dist_diff_ratio = [0.0 for i in range(swarm_size)]
736
dist_diff_power = 0.3
737
738
# the loop for simulation 2
739
sim_haulted = False
740
time_last = pygame.time.get_ticks()
741
time_now = time_last
742
frame_period = 1000
743
sim_freq_control = True
744
iter_count = 0
745
sys.stdout.write("iteration {}".format(iter_count))
746
while True:
747
for event in pygame.event.get():
748
if event.type == pygame.QUIT: # close window button is clicked
749
print("program exit in simulation 2")
750
sys.exit() # exit the entire program
751
if event.type == pygame.KEYUP:
752
if event.key == pygame.K_SPACE:
753
sim_haulted = not sim_haulted # reverse the pause flag
754
if sim_haulted: continue
755
756
# simulation frequency control
757
if sim_freq_control:
758
time_now = pygame.time.get_ticks()
759
if (time_now - time_last) > frame_period:
760
time_last = time_now
761
else:
762
continue
763
764
# increase iteration count
765
iter_count = iter_count + 1
766
sys.stdout.write("\riteration {}".format(iter_count))
767
sys.stdout.flush()
768
769
# 1.update the dominant decision for all robots
770
deci_domi = np.argmax(deci_dist, axis=1)
771
772
# 2.update the groups
773
groups = [] # empty the group container
774
group_deci = [] # the exhibited decision of the groups
775
robot_pool = range(swarm_size) # a robot pool, to be assigned into groups
776
while len(robot_pool) != 0: # searching groups one by one from the global robot pool
777
# start a new group, with first robot in the robot_pool
778
first_member = robot_pool[0] # first member of this group
779
group_temp = [first_member] # current temporary group
780
robot_pool.pop(0) # pop out first robot in the pool
781
# a list of potential members for current group
782
# this list may increase when new members of group are discovered
783
p_members = list(conn_lists[first_member])
784
# an index for iterating through p_members, in searching group members
785
p_index = 0 # if it climbs to the end, the searching ends
786
# index of dominant decision for current group
787
current_domi = deci_domi[first_member]
788
# dynamically iterating through p_members with p_index
789
while p_index < len(p_members): # index still in valid range
790
if deci_domi[p_members[p_index]] == current_domi:
791
# a new member has been found
792
new_member = p_members[p_index] # get index of the new member
793
p_members.remove(new_member) # remove it from p_members list
794
# but not increase p_index, because new value in p_members will flush in
795
robot_pool.remove(new_member) # remove it from the global robot pool
796
group_temp.append(new_member) # add it to current group
797
# check if new potential members are available, due to new robot discovery
798
p_members_new = conn_lists[new_member] # new potential members
799
for member in p_members_new:
800
if member not in p_members: # should not already in p_members
801
if member not in group_temp: # should not in current group
802
if member in robot_pool: # should be available in global pool
803
# if conditions satisfied, it is qualified as a potential member
804
p_members.append(member) # append at the end
805
else:
806
# a boundary robot(share different decision) has been met
807
# leave it in p_members, will help to avoid checking back again on this robot
808
p_index = p_index + 1 # shift right one position
809
# all connected members for this group have been located
810
groups.append(group_temp) # append the new group
811
group_deci.append(deci_domi[first_member]) # append new group's exhibited decision
812
# update the colors for the exhibited decisions
813
if not color_initialized:
814
color_initialized = True
815
select_set = range(color_quantity) # the initial selecting set
816
all_deci_set = set(group_deci) # put all exhibited decisions in a set
817
for deci in all_deci_set: # avoid checking duplicate decisions
818
if len(select_set) == 0:
819
select_set = range(color_quantity) # start a new set to select from
820
chosen_color = np.random.choice(select_set)
821
select_set.remove(chosen_color)
822
deci_colors[deci] = chosen_color # assign the chosen color to decision
823
# increase the assignments of chosen color by 1
824
color_assigns[chosen_color] = color_assigns[chosen_color] + 1
825
else:
826
# remove the color for a decision, if it's no longer the decision of any group
827
all_deci_set = set(group_deci)
828
for i in range(shape_quantity):
829
if deci_colors[i] != -1: # there was a color assigned before
830
if i not in all_deci_set:
831
# decrease the assignments of chosen color by 1
832
color_assigns[deci_colors[i]] = color_assigns[deci_colors[i]] - 1
833
deci_colors[i] = -1 # remove the assigned color
834
# assign color for an exhibited decision if not assigned
835
select_set = [] # set of colors to select from, start from empty
836
for i in range(len(groups)):
837
if deci_colors[group_deci[i]] == -1:
838
if len(select_set) == 0:
839
# construct a new select_set
840
color_assigns_min = min(color_assigns)
841
color_assigns_temp = [j - color_assigns_min for j in color_assigns]
842
select_set = range(color_quantity)
843
for j in range(color_quantity):
844
if color_assigns_temp[j] != 0:
845
select_set.remove(j)
846
chosen_color = np.random.choice(select_set)
847
select_set.remove(chosen_color)
848
deci_colors[group_deci[i]] = chosen_color # assign the chosen color
849
# increase the assignments of chosen color by 1
850
color_assigns[chosen_color] = color_assigns[chosen_color] + 1
851
# update the colors for the groups
852
group_colors = []
853
for i in range(len(groups)):
854
group_colors.append(deci_colors[group_deci[i]])
855
856
# 3.update the group size for each robot
857
for i in range(len(groups)):
858
size_temp = len(groups[i])
859
color_temp = group_colors[i]
860
for robot in groups[i]:
861
robot_group_sizes[robot] = size_temp
862
robot_colors[robot] = color_temp # update the color for each robot
863
864
# the decision distribution evolution
865
converged_all = True # flag for convergence of entire network
866
deci_dist_t = np.copy(deci_dist) # deep copy of the 'deci_dist'
867
for i in range(swarm_size):
868
host_domi = deci_domi[i]
869
converged = True
870
for neighbor in conn_lists[i]:
871
if host_domi != deci_domi[neighbor]:
872
converged = False
873
break
874
# action based on convergence of dominant decision
875
if converged: # all neighbors have converged with host
876
# step 1: take equally weighted average on all distributions
877
# including host and all neighbors
878
deci_dist[i] = deci_dist_t[i]*1.0 # start with host itself
879
for neighbor in conn_lists[i]:
880
# accumulate neighbor's distribution
881
deci_dist[i] = deci_dist[i] + deci_dist_t[neighbor]
882
# normalize the distribution such that sum is 1.0
883
sum_temp = np.sum(deci_dist[i])
884
deci_dist[i] = deci_dist[i] / sum_temp
885
# step 2: increase the unipolarity by applying the linear multiplier
886
# (this step is only for when all neighbors are converged)
887
# First find the largest difference between two distributions in this block
888
# of robots, including the host and all its neighbors.
889
comb_pool = [i] + list(conn_lists[i]) # add host to a pool with its neighbors
890
# will be used to form combinations from this pool
891
comb_pool_len = len(comb_pool)
892
dist_diff = []
893
for j in range(comb_pool_len):
894
for k in range(j+1, comb_pool_len):
895
j_robot = comb_pool[j]
896
k_robot = comb_pool[k]
897
dist_diff.append(np.sum(abs(deci_dist[j_robot] - deci_dist[k_robot])))
898
dist_diff_max = max(dist_diff) # maximum distribution difference of all
899
if dist_diff_max < dist_diff_thres:
900
# distribution difference is small enough,
901
# that linear multiplier should be applied to increase unipolarity
902
dist_diff_ratio = dist_diff_max/dist_diff_thres
903
# Linear multiplier is generated from value of smaller and larger ends, the
904
# smaller end is positively related with dist_diff_ratio. The smaller the
905
# maximum distribution difference, the smaller the dist_diff_ratio, and the
906
# steeper the linear multiplier.
907
# '1.0/shape_quantity' is the average value of the linear multiplier
908
small_end = 1.0/shape_quantity * np.power(dist_diff_ratio, dist_diff_power)
909
large_end = 2.0/shape_quantity - small_end
910
# sort the magnitude of the current distribution
911
dist_temp = np.copy(deci_dist[i]) # temporary distribution
912
sort_index = range(shape_quantity)
913
for j in range(shape_quantity-1): # bubble sort, ascending order
914
for k in range(shape_quantity-1-j):
915
if dist_temp[k] > dist_temp[k+1]:
916
# exchange values in 'dist_temp'
917
temp = dist_temp[k]
918
dist_temp[k] = dist_temp[k+1]
919
dist_temp[k+1] = temp
920
# exchange values in 'sort_index'
921
temp = sort_index[k]
922
sort_index[k] = sort_index[k+1]
923
sort_index[k+1] = temp
924
# applying the linear multiplier
925
for j in range(shape_quantity):
926
multiplier = small_end + float(j)/(shape_quantity-1) * (large_end-small_end)
927
deci_dist[i][sort_index[j]] = deci_dist[i][sort_index[j]] * multiplier
928
# normalize the distribution such that sum is 1.0
929
sum_temp = np.sum(deci_dist[i])
930
deci_dist[i] = deci_dist[i]/sum_temp
931
else:
932
# not applying linear multiplier when distribution difference is large
933
pass
934
else: # at least one neighbor has different opinion with host
935
converged_all = False # the network is not converged
936
# take unequal weights in the averaging process based on group sizes
937
deci_dist[i] = deci_dist_t[i]*robot_group_sizes[i] # start with host itself
938
for neighbor in conn_lists[i]:
939
# accumulate neighbor's distribution
940
deci_dist[i] = deci_dist[i] + deci_dist_t[neighbor]*robot_group_sizes[neighbor]
941
# normalize the distribution
942
sum_temp = np.sum(deci_dist[i])
943
deci_dist[i] = deci_dist[i] / sum_temp
944
945
# update the graphics
946
screen.fill(color_white)
947
# draw the regualr connecting lines
948
for i in range(swarm_size):
949
for j in range(i+1, swarm_size):
950
if conn_table[i,j]:
951
pygame.draw.line(screen, color_black, disp_poses[i], disp_poses[j],
952
conn_width_thin_consensus)
953
# draw the connecting lines marking the groups
954
for i in range(len(groups)):
955
group_len = len(groups[i])
956
for j in range(group_len):
957
for k in range(j+1, group_len):
958
j_robot = groups[i][j]
959
k_robot = groups[i][k]
960
# check if two robots in one group is connected
961
if conn_table[j_robot,k_robot]:
962
pygame.draw.line(screen, distinct_color_set[group_colors[i]],
963
disp_poses[j_robot], disp_poses[k_robot], conn_width_thick_consensus)
964
# draw the robots as dots
965
for i in range(swarm_size):
966
pygame.draw.circle(screen, distinct_color_set[robot_colors[i]],
967
disp_poses[i], robot_size_consensus, 0)
968
pygame.display.update()
969
970
# check exit condition for simulations 2
971
if converged_all:
972
shape_decision = deci_domi[0]
973
print("") # move cursor to the new line
974
print("converged to decision {}: {}".format(shape_decision,
975
shape_catalog[shape_decision]))
976
print("simulation 2 is finished")
977
if manual_mode: raw_input("<Press Enter to continue>")
978
print("") # empty line
979
break
980
981
########### simulation 3: consensus role assignment for the loop shape ###########
982
983
print("##### simulation 3: role assignment #####")
984
985
dist_conn_update() # need to update the network only once
986
# draw the network first time
987
disp_poses_update()
988
screen.fill(color_white)
989
for i in range(swarm_size):
990
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size_consensus, 0)
991
for j in range(i+1, swarm_size):
992
if conn_table[i,j]:
993
pygame.draw.line(screen, color_black, disp_poses[i], disp_poses[j],
994
conn_width_thin_consensus)
995
pygame.display.update()
996
997
# calculate the gradient map for message transmission
998
gradients = np.copy(conn_table) # build gradient map on connection map
999
pool_gradient = 1 # gradients of the connections in the pool
1000
pool_conn = {}
1001
for i in range(swarm_size):
1002
pool_conn[i] = conn_lists[i][:] # start with gradient 1 connections
1003
while len(pool_conn.keys()) != 0:
1004
source_deactivate = []
1005
for source in pool_conn:
1006
targets_temp = [] # the new targets
1007
for target in pool_conn[source]:
1008
for target_new in conn_lists[target]:
1009
if target_new == source: continue # skip itself
1010
if gradients[source, target_new] == 0:
1011
gradients[source, target_new] = pool_gradient + 1
1012
targets_temp.append(target_new)
1013
if len(targets_temp) == 0:
1014
source_deactivate.append(source)
1015
else:
1016
pool_conn[source] = targets_temp[:] # update with new targets
1017
for source in source_deactivate:
1018
pool_conn.pop(source) # remove the finished sources
1019
pool_gradient = pool_gradient + 1
1020
# calculate the relative gradient values
1021
gradients_rel = []
1022
# gradients_rel[i][j,k] refers to gradient of k relative to j with message source i
1023
for i in range(swarm_size): # message source i
1024
gradient_temp = np.zeros((swarm_size, swarm_size))
1025
for j in range(swarm_size): # in the view point of j
1026
gradient_temp[j] = gradients[i] - gradients[i,j]
1027
gradients_rel.append(gradient_temp)
1028
# list the neighbors a robot can send message to regarding a message source
1029
neighbors_send = [[[] for j in range(swarm_size)] for i in range(swarm_size)]
1030
# neighbors_send[i][j][k] means, if message from source i is received in j,
1031
# it should be send to k
1032
for i in range(swarm_size): # message source i
1033
for j in range(swarm_size): # in the view point of j
1034
for neighbor in conn_lists[j]:
1035
if gradients_rel[i][j,neighbor] == 1:
1036
neighbors_send[i][j].append(neighbor)
1037
1038
# initialize the role assignment variables
1039
# preference distribution of all robots
1040
pref_dist = np.random.rand(swarm_size, swarm_size) # no need to normalize it
1041
initial_roles = np.argmax(pref_dist, axis=1) # the chosen role
1042
# the local assignment information
1043
local_role_assignment = [[[-1, 0, -1] for j in range(swarm_size)] for i in range(swarm_size)]
1044
# local_role_assignment[i][j] is local assignment information of robot i for robot j
1045
# first number is chosen role, second is probability, third is time stamp
1046
local_robot_assignment = [[[] for j in range(swarm_size)] for i in range(swarm_size)]
1047
# local_robot_assignment[i][j] is local assignment of robot i for role j
1048
# contains a list of robots that choose role j
1049
# populate the chosen role of itself to the local assignment information
1050
for i in range(swarm_size):
1051
local_role_assignment[i][i][0] = initial_roles[i]
1052
local_role_assignment[i][i][1] = pref_dist[i, initial_roles[i]]
1053
local_role_assignment[i][i][2] = 0
1054
local_robot_assignment[i][initial_roles[i]].append(i)
1055
1056
# received message container for all robots
1057
message_rx = [[] for i in range(swarm_size)]
1058
# for each message entry, it containts:
1059
# message[0]: ID of message source
1060
# message[1]: its preferred role
1061
# message[2]: probability of chosen role
1062
# message[3]: time stamp
1063
# all robots transmit once their chosen role before the loop
1064
transmission_total = 0 # count message transmissions for each iteration
1065
iter_count = 0 # also used as time stamp in message
1066
for source in range(swarm_size):
1067
chosen_role = local_role_assignment[source][source][0]
1068
message_temp = [source, chosen_role, pref_dist[source, chosen_role], iter_count]
1069
for target in conn_lists[source]: # send to all neighbors
1070
message_rx[target].append(message_temp)
1071
transmission_total = transmission_total + 1
1072
role_color = [0 for i in range(swarm_size)] # colors for a conflicting role
1073
# Dynamically manage color for conflicting robots is unnecessarily complicated, might just
1074
# assign the colors in advance.
1075
role_index_pool = range(swarm_size)
1076
random.shuffle(role_index_pool)
1077
color_index_pool = range(color_quantity)
1078
random.shuffle(color_index_pool)
1079
while len(role_index_pool) != 0:
1080
role_color[role_index_pool[0]] = color_index_pool[0]
1081
role_index_pool.pop(0)
1082
color_index_pool.pop(0)
1083
if len(color_index_pool) == 0:
1084
color_index_pool = range(color_quantity)
1085
random.shuffle(color_index_pool)
1086
1087
# flags
1088
transmit_flag = [[False for j in range(swarm_size)] for i in range(swarm_size)]
1089
# whether robot i should transmit received message of robot j
1090
change_flag = [False for i in range(swarm_size)]
1091
# whether robot i should change its chosen role
1092
scheme_converged = [False for i in range(swarm_size)]
1093
1094
# the loop for simulation 3
1095
sim_haulted = False
1096
time_last = pygame.time.get_ticks()
1097
time_now = time_last
1098
time_period = 2000 # not frame_period
1099
sim_freq_control = True
1100
flash_delay = 200
1101
sys.stdout.write("iteration {}".format(iter_count))
1102
while True:
1103
for event in pygame.event.get():
1104
if event.type == pygame.QUIT: # close window button is clicked
1105
print("program exit in simulation 3")
1106
sys.exit() # exit the entire program
1107
if event.type == pygame.KEYUP:
1108
if event.key == pygame.K_SPACE:
1109
sim_haulted = not sim_haulted # reverse the pause flag
1110
if sim_haulted: continue
1111
1112
# simulation frequency control
1113
if sim_freq_control:
1114
time_now = pygame.time.get_ticks()
1115
if (time_now - time_last) > time_period:
1116
time_last = time_now
1117
else:
1118
continue
1119
1120
# increase iteration count
1121
iter_count = iter_count + 1
1122
sys.stdout.write("\riteration {}".format(iter_count))
1123
sys.stdout.flush()
1124
1125
# process the received messages
1126
# transfer messages to the processing buffer, then empty the message receiver
1127
message_rx_buf = [[[k for k in j] for j in i] for i in message_rx]
1128
message_rx = [[] for i in range(swarm_size)]
1129
yield_robots = [] # the robots that are yielding on chosen roles
1130
yield_roles = [] # the old roles of yield_robots before yielding
1131
for i in range(swarm_size): # messages received by robot i
1132
for message in message_rx_buf[i]:
1133
source = message[0]
1134
role = message[1]
1135
probability = message[2]
1136
time_stamp = message[3]
1137
if source == i:
1138
print("error, robot {} receives message of itself".format(i))
1139
sys.exit()
1140
if time_stamp > local_role_assignment[i][source][2]:
1141
# received message will only take any effect if time stamp is new
1142
# update local_robot_assignment
1143
role_old = local_role_assignment[i][source][0]
1144
if role_old >= 0: # has been initialized before, not -1
1145
local_robot_assignment[i][role_old].remove(source)
1146
local_robot_assignment[i][role].append(source)
1147
# update local_role_assignment
1148
local_role_assignment[i][source][0] = role
1149
local_role_assignment[i][source][1] = probability
1150
local_role_assignment[i][source][2] = time_stamp
1151
transmit_flag[i][source] = True
1152
# check conflict with itself
1153
if role == local_role_assignment[i][i][0]:
1154
if probability >= pref_dist[i, local_role_assignment[i][i][0]]:
1155
# change its choice after all message received
1156
change_flag[i] = True
1157
yield_robots.append(i)
1158
yield_roles.append(local_role_assignment[i][i][0])
1159
# change the choice of role for those decide to
1160
for i in range(swarm_size):
1161
if change_flag[i]:
1162
change_flag[i] = False
1163
role_old = local_role_assignment[i][i][0]
1164
pref_dist_temp = np.copy(pref_dist[i])
1165
pref_dist_temp[local_role_assignment[i][i][0]] = -1
1166
# set to negative to avoid being chosen
1167
for j in range(swarm_size):
1168
if len(local_robot_assignment[i][j]) != 0:
1169
# eliminate those choices that have been taken
1170
pref_dist_temp[j] = -1
1171
role_new = np.argmax(pref_dist_temp)
1172
if pref_dist_temp[role_new] < 0:
1173
print("error, robot {} has no available role".format(i))
1174
sys.exit()
1175
# role_new is good to go
1176
# update local_robot_assignment
1177
local_robot_assignment[i][role_old].remove(i)
1178
local_robot_assignment[i][role_new].append(i)
1179
# update local_role_assignment
1180
local_role_assignment[i][i][0] = role_new
1181
local_role_assignment[i][i][1] = pref_dist[i][role_new]
1182
local_role_assignment[i][i][2] = iter_count
1183
transmit_flag[i][i] = True
1184
# transmit the received messages or initial new message transmission
1185
transmission_total = 0
1186
for transmitter in range(swarm_size): # transmitter robot
1187
for source in range(swarm_size): # message is for this source robot
1188
if transmit_flag[transmitter][source]:
1189
transmit_flag[transmitter][source] = False
1190
message_temp = [source, local_role_assignment[transmitter][source][0],
1191
local_role_assignment[transmitter][source][1],
1192
local_role_assignment[transmitter][source][2]]
1193
for target in neighbors_send[source][transmitter]:
1194
message_rx[target].append(message_temp)
1195
transmission_total = transmission_total + 1
1196
1197
# check if role assignment scheme is converged at every robot
1198
for i in range(swarm_size):
1199
if not scheme_converged[i]:
1200
converged = True
1201
for j in range(swarm_size):
1202
if len(local_robot_assignment[i][j]) != 1:
1203
converged = False
1204
break
1205
if converged:
1206
scheme_converged[i] = True
1207
1208
# for display, scan the robots that have detected conflict but not yielding
1209
persist_robots = []
1210
for i in range(swarm_size):
1211
if i in yield_robots: continue
1212
if len(local_robot_assignment[i][local_role_assignment[i][i][0]]) > 1:
1213
persist_robots.append(i)
1214
1215
# update the display
1216
for i in range(swarm_size):
1217
for j in range(i+1, swarm_size):
1218
if conn_table[i,j]:
1219
pygame.draw.line(screen, color_black, disp_poses[i], disp_poses[j],
1220
conn_width_thin_consensus)
1221
for i in range(swarm_size):
1222
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size_consensus, 0)
1223
# draw the persisting robots with color of conflicting role
1224
for i in persist_robots:
1225
pygame.draw.circle(screen, distinct_color_set[role_color[local_role_assignment[i][i][0]]],
1226
disp_poses[i], robot_size_consensus, 0)
1227
# draw extra ring on robot if local scheme has converged
1228
for i in range(swarm_size):
1229
if scheme_converged[i]:
1230
pygame.draw.circle(screen, color_black, disp_poses[i],
1231
robot_ring_size, 1)
1232
pygame.display.update()
1233
# flash the yielding robots with color of old role
1234
for _ in range(3):
1235
# change to color
1236
for i in range(len(yield_robots)):
1237
pygame.draw.circle(screen, distinct_color_set[role_color[yield_roles[i]]],
1238
disp_poses[yield_robots[i]], robot_size_consensus, 0)
1239
pygame.display.update()
1240
pygame.time.delay(flash_delay)
1241
# change to black
1242
for i in range(len(yield_robots)):
1243
pygame.draw.circle(screen, color_black,
1244
disp_poses[yield_robots[i]], robot_size_consensus, 0)
1245
pygame.display.update()
1246
pygame.time.delay(flash_delay)
1247
1248
# exit the simulation if all role assignment schemes have converged
1249
all_converged = scheme_converged[0]
1250
for i in range(1, swarm_size):
1251
all_converged = all_converged and scheme_converged[i]
1252
if not all_converged: break
1253
if all_converged:
1254
for i in range(swarm_size):
1255
assignment_scheme[i] = local_role_assignment[0][i][0]
1256
print("") # move cursor to the new line
1257
print("simulation 3 is finished")
1258
if manual_mode: raw_input("<Press Enter to continue>")
1259
print("") # empty line
1260
break
1261
1262
# # store the variable "assignment_scheme"
1263
# # tmp_filepath = os.path.join('tmp', 'demo1_30_assignment_scheme')
1264
# tmp_filepath = os.path.join('tmp', 'demo1_100_assignment_scheme')
1265
# with open(tmp_filepath, 'w') as f:
1266
# pickle.dump(assignment_scheme, f)
1267
# raw_input("<Press Enter to continue>")
1268
# break
1269
1270
########### simulation 4: loop formation with designated role assignment ###########
1271
1272
# # restore variable "assignment_scheme"
1273
# # tmp_filepath = os.path.join('tmp', 'demo1_30_assignment_scheme')
1274
# tmp_filepath = os.path.join('tmp', 'demo1_100_assignment_scheme')
1275
# with open(tmp_filepath) as f:
1276
# assignment_scheme = pickle.load(f)
1277
1278
print("##### simulation 4: loop formation #####")
1279
1280
# robot perperties
1281
# all robots start with state '-1'
1282
robot_states = np.array([-1 for i in range(swarm_size)])
1283
# '-1' for wandering around, ignoring all connections
1284
# '0' for wandering around, available to connection
1285
# '1' for in a group, order on loop not satisfied, climbing CCW around loop
1286
# '2' for in a group, order on loop satisfied
1287
n1_life_lower = 2 # inclusive
1288
n1_life_upper = 6 # exclusive
1289
robot_n1_lives = np.random.uniform(n1_life_lower, n1_life_upper, swarm_size)
1290
robot_oris = np.random.rand(swarm_size) * 2 * math.pi - math.pi # in range of [-pi, pi)
1291
robot_key_neighbors = [[] for i in range(swarm_size)] # key neighbors for robot on loop
1292
# for state '1' robot: the robot that it is climbing around
1293
# for state '2' robot: the left and right neighbors in serial connection on the loop
1294
# exception is the group has only two members, key neighbor will be only one robot
1295
1296
# group properties
1297
groups = {}
1298
# key is the group id, value is a list, in the list:
1299
# [0]: a list of robots in the group, both state '1' and '2'
1300
# [1]: remaining life time of the group
1301
# [2]: whether or not being the dominant group
1302
life_incre = 5 # number of seconds added to the life of a group when new robot joins
1303
group_id_upper = swarm_size # upper limit of group id
1304
robot_group_ids = np.array([-1 for i in range(swarm_size)]) # group id for the robots
1305
# '-1' for not in a group
1306
1307
# use step moving distance in each update, instead of calculating from robot velocity
1308
# so to make it independent of simulation frequency control
1309
step_moving_dist = 0.05 # should be smaller than destination distance error
1310
destination_error = 0.1
1311
mov_vec_ratio = 0.5 # ratio used when calculating mov vector
1312
# spring constants in SMA
1313
linear_const = 1.0
1314
bend_const = 0.8
1315
disp_coef = 0.5
1316
# for avoiding space too small on loop
1317
space_good_thres = desired_space * 0.85
1318
1319
# the loop for simulation 4
1320
sim_haulted = False
1321
time_last = pygame.time.get_ticks()
1322
time_now = time_last
1323
frame_period = 50
1324
sim_freq_control = True
1325
iter_count = 0
1326
# sys.stdout.write("iteration {}".format(iter_count)) # did nothing in iteration 0
1327
print("swarm robots are forming an ordered loop ...")
1328
loop_formed = False
1329
ending_period = 1.0 # grace period
1330
while True:
1331
for event in pygame.event.get():
1332
if event.type == pygame.QUIT: # close window button is clicked
1333
print("program exit in simulation 4")
1334
sys.exit() # exit the entire program
1335
if event.type == pygame.KEYUP:
1336
if event.key == pygame.K_SPACE:
1337
sim_haulted = not sim_haulted # reverse the pause flag
1338
if sim_haulted: continue
1339
1340
# simulation frequency control
1341
if sim_freq_control:
1342
time_now = pygame.time.get_ticks()
1343
if (time_now - time_last) > frame_period:
1344
time_last = time_now
1345
else:
1346
continue
1347
1348
# increase iteration count
1349
iter_count = iter_count + 1
1350
# sys.stdout.write("\riteration {}".format(iter_count))
1351
# sys.stdout.flush()
1352
1353
# state transition variables
1354
st_n1to0 = [] # robot '-1' gets back to '0' after life time ends
1355
# list of robots changing to '0' from '-1'
1356
st_gton1 = [] # group disassembles either life expires, or triggered by others
1357
# list of groups to be disassembled
1358
st_0to1 = {} # robot '0' detects robot '2', join its group
1359
# key is the robot '0', value is the group id
1360
st_0to2 = {} # robot '0' detects another robot '0', forming a new group
1361
# key is the robot '0', value is the other neighbor robot '0'
1362
st_1to2 = {} # robot '1' is climbing around the loop, and finds its right position
1363
# key is the left side of the slot, value is a list of robots intend to join
1364
1365
dist_conn_update() # update "relations" of the robots
1366
# check state transitions, and schedule the tasks
1367
for i in range(swarm_size):
1368
if robot_states[i] == -1: # for host robot with state '-1'
1369
if robot_n1_lives[i] < 0:
1370
st_n1to0.append(i)
1371
else:
1372
if len(conn_lists[i]) == 0: continue
1373
state2_list = []
1374
state1_list = []
1375
for j in conn_lists[i]:
1376
if robot_states[j] == 2:
1377
state2_list.append(j)
1378
elif robot_states[j] == 1:
1379
state1_list.append(j)
1380
if len(state2_list) + len(state1_list) != 0:
1381
groups_local, group_id_max = S14_robot_grouping(
1382
state2_list + state1_list, robot_group_ids, groups)
1383
if len(groups_local.keys()) > 1:
1384
# disassemble all groups except the largest one
1385
for group_id_temp in groups_local.keys():
1386
if ((group_id_temp != group_id_max) and
1387
(group_id_temp not in st_gton1)):
1388
# schedule to disassemble this group
1389
st_gton1.append(group_id_temp)
1390
else:
1391
# state '-1' robot can only be repelled away by state '2' robot
1392
if len(state2_list) != 0:
1393
# find the closest neighbor in groups_local[group_id_max]
1394
robot_closest = S14_closest_robot(i, state2_list)
1395
# change moving direction opposing the closest robot
1396
vect_temp = robot_poses[i] - robot_poses[robot_closest]
1397
robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
1398
elif robot_states[i] == 0: # for host robot with state '0'
1399
if len(conn_lists[i]) == 0: continue
1400
state2_list = []
1401
state1_list = []
1402
state0_list = []
1403
for j in conn_lists[i]:
1404
if robot_states[j] == 2:
1405
state2_list.append(j)
1406
elif robot_states[j] == 1:
1407
state1_list.append(j)
1408
elif robot_states[j] == 0:
1409
state0_list.append(j)
1410
state2_quantity = len(state2_list)
1411
state1_quantity = len(state1_list)
1412
state0_quantity = len(state0_list)
1413
# disassemble minority groups if there are multiple groups
1414
if state2_quantity + state1_quantity > 1:
1415
# there is in-group robot in the neighbors
1416
groups_local, group_id_max = S14_robot_grouping(state2_list+state1_list,
1417
robot_group_ids, groups)
1418
# disassmeble all groups except the largest one
1419
for group_id_temp in groups_local.keys():
1420
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
1421
st_gton1.append(group_id_temp) # schedule to disassemble this group
1422
# responses to the state '2', '1', and '0' robots
1423
if state2_quantity != 0:
1424
# join the group with state '2' robots
1425
if state2_quantity == 1: # only one state '2' robot
1426
# join the group of the state '2' robot
1427
st_0to1[i] = robot_group_ids[state2_list[0]]
1428
robot_key_neighbors[i] = [state2_list[0]] # add key neighbor
1429
else: # multiple state '2' robots
1430
# it's possible that the state '2' robots are in different groups
1431
# find the closest one in the largest group, and join the group
1432
groups_local, group_id_max = S14_robot_grouping(state2_list,
1433
robot_group_ids, groups)
1434
robot_closest = S14_closest_robot(i, groups_local[group_id_max])
1435
st_0to1[i] = group_id_max
1436
robot_key_neighbors[i] = [robot_closest] # add key neighbor
1437
# elif state1_quantity != 0:
1438
# # get repelled away from state '1' robot
1439
# if state1_quantity == 1: # only one state '1' robot
1440
# vect_temp = robot_poses[i] - robot_poses[state1_list[0]]
1441
# robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
1442
# else:
1443
# groups_local, group_id_max = S14_robot_grouping(state1_list,
1444
# robot_group_ids, groups)
1445
# robot_closest = S14_closest_robot(i, groups_local[group_id_max])
1446
# vect_temp = robot_poses[i] - robot_poses[robot_closest]
1447
# robot_oris[i] = math.atan2(vect_temp[1], vect_temp[0])
1448
elif state0_quantity != 0:
1449
# form new group with state '0' robots
1450
st_0to2[i] = S14_closest_robot(i, state0_list)
1451
elif (robot_states[i] == 1) or (robot_states[i] == 2):
1452
# disassemble the minority groups
1453
state12_list = [] # list of state '1' and '2' robots in the list
1454
has_other_group = False
1455
host_group_id = robot_group_ids[i]
1456
for j in conn_lists[i]:
1457
if (robot_states[j] == 1) or (robot_states[j] == 2):
1458
state12_list.append(j)
1459
if robot_group_ids[j] != host_group_id:
1460
has_other_group = True
1461
if has_other_group:
1462
groups_local, group_id_max = S14_robot_grouping(state12_list,
1463
robot_group_ids, groups)
1464
for group_id_temp in groups_local.keys():
1465
if (group_id_temp != group_id_max) and (group_id_temp not in st_gton1):
1466
st_gton1.append(group_id_temp) # schedule to disassemble this group
1467
# check if state '1' robot's order on loop is good
1468
if robot_states[i] == 1:
1469
current_key = robot_key_neighbors[i][0]
1470
next_key = robot_key_neighbors[current_key][-1]
1471
if next_key in conn_lists[i]: # next key neighbor is detected
1472
role_i = assignment_scheme[i]
1473
role_current_key = assignment_scheme[current_key]
1474
role_next_key = assignment_scheme[next_key]
1475
if len(robot_key_neighbors[current_key]) == 1:
1476
# the situation that only two members are in the group
1477
vect_temp = robot_poses[next_key] - robot_poses[current_key]
1478
# deciding which side of vect_temp robot i is at
1479
vect_i = robot_poses[i] - robot_poses[current_key]
1480
side_value = np.cross(vect_i, vect_temp)
1481
if side_value > 0: # robot i on the right side
1482
if role_next_key > role_current_key:
1483
if (role_i < role_current_key) or (role_i > role_next_key):
1484
# update with next key neighbor
1485
robot_key_neighbors[i] = [next_key]
1486
else:
1487
# its position on loop is achieved, becoming state '2'
1488
if current_key in st_1to2.keys():
1489
st_1to2[current_key].append(i)
1490
else:
1491
st_1to2[current_key] = [i]
1492
else:
1493
if (role_i < role_current_key) and (role_i > role_next_key):
1494
# update with next key neighbor
1495
robot_key_neighbors[i] = [next_key]
1496
else:
1497
# its position on loop is achieved, becoming state '2'
1498
if current_key in st_1to2.keys():
1499
st_1to2[current_key].append(i)
1500
else:
1501
st_1to2[current_key] = [i]
1502
else: # robot i on the left side
1503
pass
1504
else:
1505
# the situation that at least three members are in the group
1506
if role_next_key > role_current_key:
1507
# the roles of the two robots are on the same side
1508
if (role_i < role_current_key) or (role_i > role_next_key):
1509
# update with next key neighbor
1510
robot_key_neighbors[i] = [next_key]
1511
else:
1512
# its position on loop is achieved, becoming state '2'
1513
if current_key in st_1to2.keys():
1514
st_1to2[current_key].append(i)
1515
else:
1516
st_1to2[current_key] = [i]
1517
else:
1518
# the roles of the two robots are on different side
1519
if (role_i < role_current_key) and (role_i > role_next_key):
1520
# update with next key neighbor
1521
robot_key_neighbors[i] = [next_key]
1522
else:
1523
# its position on loop is achieved, becoming state '2'
1524
if current_key in st_1to2.keys():
1525
st_1to2[current_key].append(i)
1526
else:
1527
st_1to2[current_key] = [i]
1528
1529
# check the life time of the groups; schedule disassembling if expired
1530
for group_id_temp in groups.keys():
1531
if groups[group_id_temp][1] < 0: # life time of a group ends
1532
if group_id_temp not in st_gton1:
1533
st_gton1.append(group_id_temp)
1534
1535
# process the state transition tasks
1536
# 1.st_1to2, robot '1' locates its order on loop, becoming '2'
1537
for left_key in st_1to2.keys():
1538
right_key = robot_key_neighbors[left_key][-1]
1539
role_left_key = assignment_scheme[left_key]
1540
role_right_key = assignment_scheme[right_key]
1541
joiner = -1 # the accepted joiner in this position
1542
if len(st_1to2[left_key]) == 1:
1543
joiner = st_1to2[left_key][0]
1544
else:
1545
joiner_list = st_1to2[left_key]
1546
role_dist_min = swarm_size
1547
for joiner_temp in joiner_list:
1548
# find the joiner with closest role to left key neighbor
1549
role_joiner_temp = assignment_scheme[joiner_temp]
1550
role_dist_temp = role_joiner_temp - role_left_key
1551
if role_dist_temp < 0:
1552
role_dist_temp = role_dist_temp + swarm_size
1553
if role_dist_temp < role_dist_min:
1554
joiner = joiner_temp
1555
role_dist_min = role_dist_temp
1556
# put in the new joiner
1557
# update the robot properties
1558
group_id_temp = robot_group_ids[left_key]
1559
robot_states[joiner] = 2
1560
robot_group_ids[joiner] = group_id_temp
1561
robot_key_neighbors[joiner] = [left_key, right_key]
1562
if len(robot_key_neighbors[left_key]) == 1:
1563
robot_key_neighbors[left_key] = [right_key, joiner]
1564
robot_key_neighbors[right_key] = [joiner, left_key]
1565
else:
1566
robot_key_neighbors[left_key][1] = joiner
1567
robot_key_neighbors[right_key][0] = joiner
1568
# no need to update the group properties
1569
# 2.st_0to1, robot '0' joins a group, becoming '1'
1570
for joiner in st_0to1.keys():
1571
group_id_temp = st_0to1[joiner]
1572
# update the robot properties
1573
robot_states[joiner] = 1
1574
robot_group_ids[joiner] = group_id_temp
1575
# update the group properties
1576
groups[group_id_temp][0].append(joiner)
1577
groups[group_id_temp][1] = groups[group_id_temp][1] + life_incre
1578
# 3.st_0to2, robot '0' forms new group with '0', both becoming '2'
1579
while len(st_0to2.keys()) != 0:
1580
pair0 = st_0to2.keys()[0]
1581
pair1 = st_0to2[pair0]
1582
st_0to2.pop(pair0)
1583
if (pair1 in st_0to2.keys()) and (st_0to2[pair1] == pair0):
1584
st_0to2.pop(pair1)
1585
# only forming a group if there is at least one seed robot in the pair
1586
if robot_seeds[pair0] or robot_seeds[pair1]:
1587
# forming new group for robot pair0 and pair1
1588
group_id_temp = np.random.randint(0, group_id_upper)
1589
while group_id_temp in groups.keys():
1590
group_id_temp = np.random.randint(0, group_id_upper)
1591
# update properties of the robots
1592
robot_states[pair0] = 2
1593
robot_states[pair1] = 2
1594
robot_group_ids[pair0] = group_id_temp
1595
robot_group_ids[pair1] = group_id_temp
1596
robot_key_neighbors[pair0] = [pair1]
1597
robot_key_neighbors[pair1] = [pair0]
1598
# update properties of the group
1599
groups[group_id_temp] = [[pair0, pair1], life_incre*2, False]
1600
# 4.st_gton1, groups get disassembled, life time ends or triggered by others
1601
for group_id_temp in st_gton1:
1602
for robot_temp in groups[group_id_temp][0]:
1603
robot_states[robot_temp] = -1
1604
robot_n1_lives[robot_temp] = np.random.uniform(n1_life_lower, n1_life_upper)
1605
robot_group_ids[robot_temp] = -1
1606
robot_oris[robot_temp] = np.random.rand() * 2 * math.pi - math.pi
1607
robot_key_neighbors[robot_temp] = []
1608
groups.pop(group_id_temp)
1609
# 5.st_n1to0, life time of robot '-1' ends, get back to '0'
1610
for robot_temp in st_n1to0:
1611
robot_states[robot_temp] = 0
1612
1613
# check if a group becomes dominant
1614
for group_id_temp in groups.keys():
1615
if len(groups[group_id_temp][0]) > swarm_size/2.0:
1616
groups[group_id_temp][2] = True
1617
else:
1618
groups[group_id_temp][2] = False
1619
1620
# update the physics
1621
robot_poses_t = np.copy(robot_poses) # as old poses
1622
no_state1_robot = True
1623
for i in range(swarm_size):
1624
# adjusting moving direction for state '1' and '2' robots
1625
if robot_states[i] == 1:
1626
no_state1_robot = False
1627
# rotating around its only key neighbor
1628
center = robot_key_neighbors[i][0] # the center robot
1629
# use the triangle of (desired_space, dist_table[i,center], step_moving_dist)
1630
if dist_table[i,center] > (desired_space + step_moving_dist):
1631
# moving toward the center robot
1632
robot_oris[i] = math.atan2(robot_poses_t[center,1] - robot_poses_t[i,1],
1633
robot_poses_t[center,0] - robot_poses_t[i,0])
1634
elif (dist_table[i,center] + step_moving_dist) < desired_space:
1635
# moving away from the center robot
1636
robot_oris[i] = math.atan2(robot_poses_t[i,1] - robot_poses_t[center,1],
1637
robot_poses_t[i,0] - robot_poses_t[center,0])
1638
else:
1639
# moving tangent along the circle of radius of "desired_space"
1640
robot_oris[i] = math.atan2(robot_poses_t[i,1] - robot_poses_t[center,1],
1641
robot_poses_t[i,0] - robot_poses_t[center,0])
1642
# interior angle between
1643
int_angle_temp = math.acos(np.around((math.pow(dist_table[i,center],2) +
1644
math.pow(step_moving_dist,2) - math.pow(desired_space,2)) /
1645
(2.0*dist_table[i,center]*step_moving_dist)))
1646
robot_oris[i] = reset_radian(robot_oris[i] + (math.pi - int_angle_temp))
1647
elif robot_states[i] == 2:
1648
# adjusting position to maintain the loop
1649
if len(robot_key_neighbors[i]) == 1:
1650
# situation that only two robots are in the group
1651
j = robot_key_neighbors[i][0]
1652
if abs(dist_table[i,j] - desired_space) < destination_error:
1653
continue # stay in position if within destination error
1654
else:
1655
if dist_table[i,j] > desired_space:
1656
robot_oris[i] = math.atan2(robot_poses_t[j,1] - robot_poses_t[i,1],
1657
robot_poses_t[j,0] - robot_poses_t[i,0])
1658
else:
1659
robot_oris[i] = math.atan2(robot_poses_t[i,1] - robot_poses_t[j,1],
1660
robot_poses_t[i,0] - robot_poses_t[j,0])
1661
else:
1662
# normal situation with at least three members in the group
1663
group_id_temp = robot_group_ids[i]
1664
state2_quantity = 0 # number of state '2' robots
1665
for robot_temp in groups[group_id_temp][0]:
1666
if robot_states[robot_temp] == 2:
1667
state2_quantity = state2_quantity + 1
1668
desired_angle = math.pi - 2*math.pi / state2_quantity
1669
# use the SMA algorithm to achieve the desired interior angle
1670
left_key = robot_key_neighbors[i][0]
1671
right_key = robot_key_neighbors[i][1]
1672
vect_l = (robot_poses_t[left_key] - robot_poses_t[i]) / dist_table[i,left_key]
1673
vect_r = (robot_poses_t[right_key] - robot_poses_t[i]) / dist_table[i,right_key]
1674
vect_lr = robot_poses_t[right_key] - robot_poses_t[left_key]
1675
vect_lr_dist = np.linalg.norm(vect_lr)
1676
vect_in = np.array([-vect_lr[1], vect_lr[0]]) / vect_lr_dist
1677
inter_curr = math.acos(np.dot(vect_l, vect_r)) # interior angle
1678
if np.cross(vect_r, vect_l) < 0:
1679
inter_curr = 2*math.pi - inter_curr
1680
fb_vect = np.zeros(2) # feedback vector to accumulate spring effects
1681
fb_vect = fb_vect + ((dist_table[i,left_key] - desired_space) *
1682
linear_const * vect_l)
1683
fb_vect = fb_vect + ((dist_table[i,right_key] - desired_space) *
1684
linear_const * vect_r)
1685
fb_vect = fb_vect + ((desired_angle - inter_curr) *
1686
bend_const * vect_in)
1687
if (np.linalg.norm(fb_vect)*disp_coef < destination_error and
1688
dist_table[i,left_key] > space_good_thres and
1689
dist_table[i,right_key] > space_good_thres):
1690
# stay in position if within destination error, and space are good
1691
continue
1692
else:
1693
robot_oris[i] = math.atan2(fb_vect[1], fb_vect[0])
1694
# check if out of boundaries
1695
if (robot_states[i] == -1) or (robot_states[i] == 0):
1696
# only applies for state '-1' and '0'
1697
robot_oris[i] = robot_boundary_check(robot_poses_t[i], robot_oris[i])
1698
# update one step of move
1699
robot_poses[i] = robot_poses_t[i] + (step_moving_dist *
1700
np.array([math.cos(robot_oris[i]), math.sin(robot_oris[i])]))
1701
1702
# update the graphics
1703
disp_poses_update()
1704
screen.fill(color_white)
1705
# draw the robots of states '-1' and '0'
1706
for i in range(swarm_size):
1707
if robot_seeds[i]:
1708
color_temp = color_red
1709
else:
1710
color_temp = color_grey
1711
if robot_states[i] == -1: # empty circle for state '-1' robot
1712
pygame.draw.circle(screen, color_temp, disp_poses[i],
1713
robot_size_formation, robot_width_empty)
1714
elif robot_states[i] == 0: # full circle for state '0' robot
1715
pygame.draw.circle(screen, color_temp, disp_poses[i],
1716
robot_size_formation, 0)
1717
# draw text of the assigned roles for all robots
1718
text = font.render(str(int(assignment_scheme[i])), True, color_grey)
1719
# text = font.render(str(int(i)), True, color_grey)
1720
screen.blit(text, (disp_poses[i,0]+6, disp_poses[i,1]-6))
1721
# draw the in-group robots by group
1722
for group_id_temp in groups.keys():
1723
if groups[group_id_temp][2]:
1724
# highlight the dominant group with black color
1725
color_group = color_black
1726
else:
1727
color_group = color_grey
1728
conn_draw_sets = [] # avoid draw same connection two times
1729
# draw the robots and connections in the group
1730
for i in groups[group_id_temp][0]:
1731
for j in robot_key_neighbors[i]:
1732
if set([i,j]) not in conn_draw_sets:
1733
pygame.draw.line(screen, color_group, disp_poses[i],
1734
disp_poses[j], conn_width_formation)
1735
conn_draw_sets.append(set([i,j]))
1736
# draw robots in the group
1737
if robot_seeds[i]: # force color red for seed robot
1738
pygame.draw.circle(screen, color_red, disp_poses[i],
1739
robot_size_formation, 0)
1740
else:
1741
pygame.draw.circle(screen, color_group, disp_poses[i],
1742
robot_size_formation, 0)
1743
pygame.display.update()
1744
1745
# reduce life time of robot '-1' and groups
1746
for i in range(swarm_size):
1747
if robot_states[i] == -1:
1748
robot_n1_lives[i] = robot_n1_lives[i] - frame_period/1000.0
1749
for group_id_temp in groups.keys():
1750
if not groups[group_id_temp][2]: # skip dominant group
1751
groups[group_id_temp][1] = groups[group_id_temp][1] - frame_period/1000.0
1752
1753
# check exit condition of simulation 4
1754
if not loop_formed:
1755
if ((len(groups.keys()) == 1) and (len(groups.values()[0][0]) == swarm_size)
1756
and no_state1_robot):
1757
loop_formed = True
1758
if loop_formed:
1759
if ending_period <= 0:
1760
print("simulation 4 is finished")
1761
if manual_mode: raw_input("<Press Enter to continue>")
1762
print("") # empty line
1763
break
1764
else:
1765
ending_period = ending_period - frame_period/1000.0
1766
1767
# # store the variable "robot_poses", "robot_key_neighbors"
1768
# # tmp_filepath = os.path.join('tmp', 'demo1_30_robot_poses2')
1769
# tmp_filepath = os.path.join('tmp', 'demo1_100_robot_poses2')
1770
# with open(tmp_filepath, 'w') as f:
1771
# pickle.dump([robot_poses, robot_key_neighbors], f)
1772
# raw_input("<Press Enter to continue>")
1773
# break
1774
1775
########### simulation 5: loop reshaping to chosen shape ###########
1776
1777
# # restore variable "robot_poses"
1778
# # tmp_filepath = os.path.join('tmp', 'demo1_30_robot_poses2')
1779
# tmp_filepath = os.path.join('tmp', 'demo1_100_robot_poses2')
1780
# with open(tmp_filepath) as f:
1781
# robot_poses, robot_key_neighbors = pickle.load(f)
1782
1783
print("##### simulation 5: loop reshaping #####")
1784
1785
print("chosen shape {}: {}".format(shape_decision, shape_catalog[shape_decision]))
1786
1787
# # force the choice of shape, for video recording
1788
# if len(force_shape_set) == 0: force_shape_set = range(shape_quantity)
1789
# force_choice = np.random.choice(force_shape_set)
1790
# force_shape_set.remove(force_choice)
1791
# shape_decision = force_choice
1792
# print("force shape to {}: {} (for video recording)".format(shape_decision,
1793
# shape_catalog[shape_decision]))
1794
1795
# spring constants in SMA
1796
linear_const = 1.0
1797
bend_const = 0.8
1798
disp_coef = 0.05
1799
1800
# shift the robots to the middle of the window
1801
x_max, y_max = np.amax(robot_poses, axis=0)
1802
x_min, y_min = np.amin(robot_poses, axis=0)
1803
robot_middle = np.array([(x_max+x_min)/2.0, (y_max+y_min)/2.0])
1804
world_middle = np.array([world_side_length/2.0, world_side_length/2.0])
1805
for i in range(swarm_size):
1806
robot_poses[i] = robot_poses[i] - robot_middle + world_middle
1807
1808
# read the loop shape from file
1809
filename = str(swarm_size) + "-" + shape_catalog[shape_decision]
1810
filepath = os.path.join(os.getcwd(), loop_folder, filename)
1811
if os.path.isfile(filepath):
1812
with open(filepath, 'r') as f:
1813
target_poses = pickle.load(f)
1814
else:
1815
print("fail to locate shape file: {}".format(filepath))
1816
sys.exit()
1817
# calculate the interior angles for the robots(instead of target positions)
1818
inter_target = np.zeros(swarm_size)
1819
for i in range(swarm_size): # i on the target loop
1820
i_l = (i-1)%swarm_size
1821
i_r = (i+1)%swarm_size
1822
vect_l = target_poses[i_l] - target_poses[i]
1823
vect_r = target_poses[i_r] - target_poses[i]
1824
dist_l = np.linalg.norm(vect_l)
1825
dist_r = np.linalg.norm(vect_r)
1826
robot_temp = list(assignment_scheme).index(i) # find the robot that chooses role i
1827
inter_target[robot_temp] = math.acos(np.around(
1828
np.dot(vect_l, vect_r) / (dist_l * dist_r) ,6))
1829
if np.cross(vect_r, vect_l) < 0:
1830
inter_target[robot_temp] = 2*math.pi - inter_target[robot_temp]
1831
1832
# reusing robot_key_neighbors variable from S4
1833
for i in range(swarm_size):
1834
if len(robot_key_neighbors[i]) != 2:
1835
print("state 1 robot not merged in yet")
1836
sys.exit()
1837
else:
1838
i_left = robot_key_neighbors[i][0]
1839
i_right = robot_key_neighbors[i][1]
1840
role_i = assignment_scheme[i]
1841
role_i_left = assignment_scheme[i_left]
1842
role_i_right = assignment_scheme[i_right]
1843
if (((role_i-role_i_left)%swarm_size != 1) or
1844
((role_i_right-role_i)%swarm_size != 1)):
1845
print("loop formed with incorrect order")
1846
sys.exit()
1847
1848
# formation control variables
1849
inter_err_thres = 0.1
1850
1851
# the loop for simulation 5
1852
sim_haulted = False
1853
time_last = pygame.time.get_ticks()
1854
time_now = time_last
1855
frame_period = 100
1856
sim_freq_control = True
1857
iter_count = 0
1858
print("loop is reshaping to " + shape_catalog[shape_decision] + " ...")
1859
while True:
1860
for event in pygame.event.get():
1861
if event.type == pygame.QUIT: # close window button is clicked
1862
print("program exit in simulation 5")
1863
sys.exit() # exit the entire program
1864
if event.type == pygame.KEYUP:
1865
if event.key == pygame.K_SPACE:
1866
sim_haulted = not sim_haulted # reverse the pause flag
1867
if sim_haulted: continue
1868
1869
# simulation frequency control
1870
if sim_freq_control:
1871
time_now = pygame.time.get_ticks()
1872
if (time_now - time_last) > frame_period:
1873
time_last = time_now
1874
else:
1875
continue
1876
1877
# increase iteration count
1878
iter_count = iter_count + 1
1879
1880
# update the physics
1881
robot_poses_t = np.copy(robot_poses)
1882
inter_curr = np.zeros(swarm_size) # current interior angles
1883
for i in range(swarm_size):
1884
i_l = robot_key_neighbors[i][0]
1885
i_r = robot_key_neighbors[i][1]
1886
# vectors
1887
vect_l = robot_poses_t[i_l] - robot_poses_t[i]
1888
vect_r = robot_poses_t[i_r] - robot_poses_t[i]
1889
vect_lr = robot_poses_t[i_r] - robot_poses_t[i_l]
1890
# distances
1891
dist_l = np.linalg.norm(vect_l)
1892
dist_r = np.linalg.norm(vect_r)
1893
dist_lr = np.linalg.norm(vect_lr)
1894
# unit vectors
1895
u_vect_l = vect_l / dist_l
1896
u_vect_r = vect_r / dist_r
1897
u_vect_in = np.array([-vect_lr[1], vect_lr[0]]) / dist_lr
1898
# calculate current interior angle
1899
inter_curr[i] = math.acos(np.around(
1900
np.dot(vect_l, vect_r) / (dist_l * dist_r), 6))
1901
if np.cross(vect_r, vect_l) < 0:
1902
inter_curr[i] = 2*math.pi - inter_curr[i]
1903
# feedback vector for the SMA algorithm
1904
fb_vect = np.zeros(2)
1905
fb_vect = fb_vect + (dist_l - desired_space) * linear_const * u_vect_l
1906
fb_vect = fb_vect + (dist_r - desired_space) * linear_const * u_vect_r
1907
fb_vect = fb_vect + (inter_target[i] - inter_curr[i]) * bend_const * u_vect_in
1908
# update one step of position
1909
robot_poses[i] = robot_poses_t[i] + disp_coef * fb_vect
1910
1911
# update the graphics
1912
disp_poses_update()
1913
screen.fill(color_white)
1914
for i in range(swarm_size):
1915
pygame.draw.circle(screen, color_black, disp_poses[i], robot_size_formation, 0)
1916
pygame.draw.line(screen, color_black, disp_poses[i],
1917
disp_poses[robot_key_neighbors[i][1]], conn_width_formation)
1918
pygame.display.update()
1919
1920
# calculate the maximum error of interior angle
1921
inter_err_max = max(np.absolute(inter_curr - inter_target))
1922
# print("current maximum error of interior angle: {}".format(inter_err_max))
1923
1924
# check exit condition of simulation 5
1925
if inter_err_max < inter_err_thres:
1926
print("simulation 5 is finished")
1927
if manual_mode: raw_input("<Press Enter to continue>")
1928
print("") # empty line
1929
break
1930
1931
1932
1933