Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
yangliu28
GitHub Repository: yangliu28/swarm_formation_sim
Path: blob/master/loop_reshape_test_motion.py
104 views
1
# program for only testing the physical motion control algorithm of the loop reshape process
2
# a new SMA algorithm inspired by the behavior of shape memory alloy
3
# the starting and ending formations will be read from file
4
# the goal is to reshape the initial loop to the shape of target loop
5
6
# Two arguments specifying the loop formation files needs to be passed, first one is for
7
# initial loop formation, second for target formation. The loop formation files should be
8
# located under 'loop-data' folder. A third argument is optional, specifying the shift of
9
# which node it prefers in the target formation, 0 is default if not specified.
10
# ex1: 'python loop_reshape_test_motion.py 30-3 30-4'
11
# ex2: 'python loop_reshape_test_motion.py 30-9 30-12 15'
12
13
# comments on first algorithm test:
14
# First algorithm focusing on the idea of influencing neighbors. The motion of a node is
15
# controller by external effects. One part of the effects is the pulling or pushing when
16
# the neighbor distance is larger or smaller than desired loop space, this is modelled as
17
# as a linear spring with relative large spring constant. Second part of the effects is the
18
# bending force driven by the desired interior angle of neighbor nodes.If a neighbor wants
19
# smaller interior angle, it exerts a bending force on host node inward the polygon, with
20
# direction perpendicular to the connecting line. And larger interior angle will produce
21
# bending force outward the polygon.
22
# The simulation results that the polygon is always bending part of nodes inward and curling
23
# up the whole loop.
24
# Presumption: the perpendicular bending force may produce unwanted motion that curls up
25
# the loop, make the bending force just along the central axis.
26
27
# comments on second algorithm test:
28
# A revision based on first algorithm test has been made, that the bending force is only
29
# along the central axis. The second algorithm presumes all motion effects are initialed
30
# by the host node itself. It judges the two neighbor distances, and calculate the force
31
# from the linear springs. It finds the difference to the desired interior angle, and
32
# produce the force along the central axis. All motion effects are proportional to the
33
# driven factors.
34
# The simulation result is just what I have been expecting for a while. It could really
35
# reshape the loop in a very fast way.
36
37
import pygame
38
from formation_functions import *
39
import numpy as np
40
import sys, os
41
import math
42
43
if len(sys.argv) < 3:
44
# three arguments at least, first one is this program's filename
45
# the other two are for the loop formation files
46
print("incorrect number of input arguments")
47
sys.exit()
48
form_files = []
49
form_files.append(sys.argv[1])
50
form_files.append(sys.argv[2])
51
# make sure the files exist
52
loop_folder = "loop-data"
53
for i in range(2):
54
new_filepath = os.path.join(os.getcwd(), loop_folder, form_files[0])
55
if not os.path.isfile(new_filepath):
56
if i == 0: print("incorrect filename for initial formation")
57
else: print("incorrect filename for target formation")
58
sys.exit()
59
# try to get another argument for shift in desired node
60
targ_shift = 0 # default
61
try:
62
targ_shift = int(sys.argv[3])
63
except: pass
64
65
pygame.init() # initialize the pygame
66
# parameters for display, window origin is at left up corner
67
screen_size = (600, 800) # width and height in pixels
68
# top half for initial formation, bottom half for target formation
69
background_color = (0,0,0) # black background
70
robot_color = (255,0,0) # red for robot and the line segments
71
robot_size = 5 # robot modeled as dot, number of pixels for radius
72
world_size = (100.0, 100.0 * screen_size[1]/screen_size[0])
73
74
# set up the simulation window and surface object
75
icon = pygame.image.load("icon_geometry_art.jpg")
76
pygame.display.set_icon(icon)
77
screen = pygame.display.set_mode(screen_size)
78
pygame.display.set_caption("Loop Reshape Motion Test")
79
80
# variables to configure the simulation
81
poly_n = 0 # will be decided when reading formation files
82
loop_space = 4.0 # side length of the equilateral polygon
83
# linear spring constant modeling the pulling and pushing effect of neighbor nodes
84
linear_const = 1.0
85
# # angular spring constant modeling the bending effect of neighbor nodes
86
# angular_const = 0.2
87
# bending spring constant modeling the bending initial by the host node itself
88
bend_const = 1.0
89
disp_coef = 0.5 # coefficient from feedback vector to displacement
90
91
# construct the polygons from formation data from file
92
nodes = [[], []] # node positions for the two formation, index is the robot's ID
93
nodes[0].append([0, 0]) # first node starts at origin
94
nodes[0].append([loop_space, 0]) # second node is loop space away on the right
95
nodes[1].append([0, 0])
96
nodes[1].append([loop_space, 0])
97
for i in range(2):
98
new_filepath = os.path.join(os.getcwd(), loop_folder, form_files[i])
99
f = open(new_filepath, 'r') # read only
100
# check consistence of polygon size
101
if i == 0: # this is the first polygon
102
poly_n = int(f.readline()) # initialize with first polygon size
103
else: # check consistence when reading second polygon
104
if int(f.readline()) != poly_n:
105
print("inconsistent polygon size from the formation files")
106
sys.exit()
107
# read all interior angles form the file
108
inter_ang = []
109
new_line = f.readline()
110
while len(new_line) != 0: # not the end of the file yet
111
inter_ang.append(float(new_line))
112
new_line = f.readline()
113
# check if this file has the nubmer of interior angles as it promised
114
if len(inter_ang) != poly_n-3:
115
print 'file "{}" has incorrect number of interior angles'.format(form_files[i])
116
sys.exit()
117
# construct positions of all nodes from these interior angles
118
ori_current = 0 # orientation of current line segment
119
for j in range(2, poly_n-1):
120
inter_ang_t = inter_ang[j-2] # interior angle of previous node
121
ori_current = reset_radian(ori_current + (math.pi - inter_ang_t))
122
nodes[i].append([nodes[i][j-1][0] + loop_space*math.cos(ori_current),
123
nodes[i][j-1][1] + loop_space*math.sin(ori_current)])
124
# calculate position of last node
125
vect_temp = [nodes[i][poly_n-2][0] - nodes[i][0][0],
126
nodes[i][poly_n-2][1] - nodes[i][0][1]] # from node 0 to n-2
127
dist_temp = math.sqrt(vect_temp[0]*vect_temp[0]+
128
vect_temp[1]*vect_temp[1])
129
midpoint = [(nodes[i][poly_n-2][0]+nodes[i][0][0])/2,
130
(nodes[i][poly_n-2][1]+nodes[i][0][1])/2]
131
perp_dist = math.sqrt(loop_space*loop_space - dist_temp*dist_temp/4)
132
perp_ori = math.atan2(vect_temp[1], vect_temp[0]) + math.pi/2
133
nodes[i].append([midpoint[0] + perp_dist*math.cos(perp_ori),
134
midpoint[1] + perp_dist*math.sin(perp_ori)])
135
136
# shift the two polygons to the top and bottom halves
137
nodes = np.array(nodes) # convert to numpy array
138
for i in range(2):
139
# calculate the geometry center of current polygon
140
geometry_center = np.mean(nodes[i], axis=0)
141
nodes[i,:,0] = nodes[i,:,0] - geometry_center[0] + world_size[0]/2
142
if i == 0: # initial formation shift to top half
143
nodes[i,:,1] = nodes[i,:,1] - geometry_center[1] + 3*world_size[1]/4
144
else: # target formation shift to bottom half
145
nodes[i,:,1] = nodes[i,:,1] - geometry_center[1] + world_size[1]/4
146
147
# calculate the interior angles of the target formation
148
inter_targ = [0 for i in range(poly_n)]
149
for i in range(poly_n):
150
vect_l = nodes[1][(i-1)%poly_n] - nodes[1][i] # from host to left
151
vect_r = nodes[1][(i+1)%poly_n] - nodes[1][i] # from host to right
152
# get the small angle between vect_l and vect_r
153
inter_targ[i] = math.acos((vect_l[0]*vect_r[0] + vect_l[1]*vect_r[1])/
154
(loop_space*loop_space))
155
if (vect_r[0]*vect_l[1] - vect_r[1]*vect_l[0]) < 0:
156
# cross product of vect_r to vect_l is smaller than 0
157
# the resulting interior angle should be in range of [0, 2*pi)
158
inter_targ[i] = 2*math.pi - inter_targ[i]
159
160
# loop for the physical motion update
161
sim_exit = False
162
sim_pause = False
163
frame_period = 100 # updating period of the simulation and graphics, in millisecond
164
timer_last = pygame.time.get_ticks()
165
timer_now = timer_last
166
# the interior angle of initial formation will be updated dynamically
167
inter_curr = [0 for i in range(poly_n)]
168
# the variable for the neighboring robots on loop
169
dist_neigh = [0 for i in range(poly_n)]
170
while not sim_exit:
171
# exit the program by close window button, or Esc or Q on keyboard
172
for event in pygame.event.get():
173
if event.type == pygame.QUIT:
174
sim_exit = True # exit with the close window button
175
if event.type == pygame.KEYUP:
176
if event.key == pygame.K_SPACE:
177
sim_pause = not sim_pause # reverse the pause flag
178
if (event.key == pygame.K_ESCAPE) or (event.key == pygame.K_q):
179
sim_exit = True # exit with ESC key or Q key
180
# skip the rest if paused
181
if sim_pause: continue
182
183
timer_now = pygame.time.get_ticks()
184
if (timer_now - timer_last) > frame_period:
185
timer_last = timer_now # reset timer
186
187
# update the dynamic neighbor distances of the loop
188
for i in range(poly_n):
189
vect_r = nodes[0][(i+1)%poly_n] - nodes[0][i] # from host to right
190
dist_neigh[i] = math.sqrt(vect_r[0]*vect_r[0] + vect_r[1]*vect_r[1])
191
192
# update the dynamic interior angles of the loop
193
for i in range(poly_n):
194
i_l = (i-1)%poly_n # index of left neighbor
195
i_r = (i+1)%poly_n # index of right neighbor
196
vect_l = nodes[0][i_l] - nodes[0][i] # from host to left
197
vect_r = nodes[0][i_r] - nodes[0][i] # from host to right
198
# get the small angle between vect_l and vect_r
199
inter_curr[i] = math.acos((vect_l[0]*vect_r[0] + vect_l[1]*vect_r[1])/
200
(dist_neigh[i_l] * dist_neigh[i]))
201
if (vect_r[0]*vect_l[1] - vect_r[1]*vect_l[0]) < 0:
202
# cross product of vect_r to vect_l is smaller than 0
203
# the resulting interior angle should be in range of [0, 2*pi)
204
inter_curr[i] = 2*math.pi - inter_curr[i]
205
206
# # first algorithm test, failed
207
# # variable for feedback from all spring effects
208
# fb_vect = [np.zeros([1,2]) for i in range(poly_n)]
209
# for i in range(poly_n):
210
# # get feedback from pulling and pushing of the linear spring
211
# i_l = (i-1)%poly_n # index of left neighbor
212
# i_r = (i+1)%poly_n # index of right neighbor
213
# # unit vector from host to left
214
# vect_l = (nodes[0][i_l]-nodes[0][i])/dist_neigh[i_l]
215
# # unit vector bending host node toward inside the polygon from left neighbor
216
# vect_lb = np.array([vect_l[1], -vect_l[0]]) # rotate vect_l cw for pi/2
217
# # unit vector from host to right
218
# vect_r = (nodes[0][i_r]-nodes[0][i])/dist_neigh[i]
219
# # unit vector bending host node toward inside the polygon from right neighbor
220
# vect_rb = np.array([-vect_r[1], vect_r[0]]) # rotate vect_r ccw for pi/2
221
222
# # add the pulling or pushing effect from left neighbor
223
# fb_vect[i] = fb_vect[i] + (dist_neigh[i_l]-loop_space) * linear_const * vect_l
224
# # add the pulling or pushing effect from right neighbor
225
# fb_vect[i] = fb_vect[i] + (dist_neigh[i]-loop_space) * linear_const * vect_r
226
# # add the bending effect from left neighbor
227
# fb_vect[i] = fb_vect[i] + ((inter_curr[i_l] - inter_targ[(i_l+targ_shift)%poly_n])*
228
# angular_const * vect_lb)
229
# # add the bending effect from right neighbor
230
# fb_vect[i] = fb_vect[i] + ((inter_curr[i_r] - inter_targ[(i_r+targ_shift)%poly_n])*
231
# angular_const * vect_rb)
232
233
# # update one step of position
234
# nodes[0][i] = nodes[0][i] + disp_coef * fb_vect[i]
235
236
# second algorithm test
237
# variable for feedback from all spring effects
238
fb_vect = [np.zeros([1,2]) for i in range(poly_n)]
239
for i in range(poly_n):
240
# get feedback from pulling and pushing of the linear spring
241
i_l = (i-1)%poly_n # index of left neighbor
242
i_r = (i+1)%poly_n # index of right neighbor
243
# unit vector from host to left
244
vect_l = (nodes[0][i_l]-nodes[0][i])/dist_neigh[i_l]
245
# unit vector from host to right
246
vect_r = (nodes[0][i_r]-nodes[0][i])/dist_neigh[i]
247
# unit vector along central axis pointing inside the polygon
248
vect_lr = nodes[0][i_r]-nodes[0][i_l] # vector from left neighbor to right
249
dist_temp = math.sqrt(vect_lr[0]*vect_lr[0]+vect_lr[1]*vect_lr[1])
250
vect_in = np.array([-vect_lr[1]/dist_temp, vect_lr[0]/dist_temp]) # rotate ccw pi/2
251
252
# add the pulling or pushing effect from left neighbor
253
fb_vect[i] = fb_vect[i] + (dist_neigh[i_l]-loop_space) * linear_const * vect_l
254
# add the pulling or pushing effect from right neighbor
255
fb_vect[i] = fb_vect[i] + (dist_neigh[i]-loop_space) * linear_const * vect_r
256
# add the bending effect initialize by the host node itself
257
fb_vect[i] = fb_vect[i] + ((inter_targ[(i+targ_shift)%poly_n]-inter_curr[i])*
258
bend_const * vect_in)
259
260
# update one step of position
261
nodes[0][i] = nodes[0][i] + disp_coef * fb_vect[i]
262
263
# graphics update
264
screen.fill(background_color)
265
for i in range(2):
266
# draw the nodes and line segments
267
disp_pos = [[0,0] for j in range(poly_n)]
268
# calculate the display pos for all nodes, as red dots
269
for j in range(0, poly_n):
270
disp_pos[j] = world_to_display(nodes[i][j], world_size, screen_size)
271
pygame.draw.circle(screen, robot_color, disp_pos[j], robot_size, 0)
272
# draw an outer circle to mark the starting node
273
pygame.draw.circle(screen, robot_color, disp_pos[0], int(robot_size*1.5), 1)
274
for j in range(poly_n-1):
275
pygame.draw.line(screen, robot_color, disp_pos[j], disp_pos[j+1])
276
pygame.draw.line(screen, robot_color, disp_pos[poly_n-1], disp_pos[0])
277
pygame.display.update()
278
279
280
281
282
283