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