Path: blob/master/loop_reshape_2_dynamic.py
104 views
# this is an algorithm revision besed on 'loop_reshape_1_static.py'1# First section is copied verbatim, algorithm tests are carried on in second section for the2# preferability distribution evolution.34# arguments format:5# '-i': filename of the initial loop formation; default is generate the initial formation6# '-t': filename of the target loop formation; default is generate the target formation7# '--gensave': option to all or none of the generated formations; default is discard8# '--nobargraph': option to skip the bar graph visualization; default is with bargraph910# Revised algorithm in the second section:11# New algorithm combines weighted averaging, linear multiplier and power function methods.12# Since equal weighted averaging method can guarantee convergence(although unipolarity of13# resulting distribution can be very poor), a revised weighted averaging method was14# implemented here to ensure as much convergence as possible. Each node is given a new15# piece of information, that is how many nodes in the adjacent block agree on which target16# node they should be. (This adjacent block is referred as a group.) This information17# will help to resolve any conflict on the boundary of two groups. And ideally, the18# group with higher number of nodes should win. With this conflict resolving mechanism,19# the linear multiplier method is used to improve the unipolarity of the distributions. This20# method has similar effect of previous power function method with bigger than 1 exponent,21# but in a slower and constant growing rate. The linear multiplier is only used when two22# neighbors converges with the host on the formation. How much the unipolarity should grow23# depends on the largest difference of distributions when using averaging method. The larger24# the difference, the less the unipolarity should grow. A power function method with exponent25# smaller than 1 is used to slow down the increasing rate of the unipolarity.2627# SMA motion control for the loop reshape process:28# Inspired by the shape memory alloy, each node maintain desired loop space by modeling the29# feedback from neighbors as stiff linear springs. Each node also trys to achived a certain30# interior angle, the difference from current interior angle to target interior angle acts31# as a potential energy, just like the memorized shape of the alloy. The node trys to32# accomodate this effect by moving itself along the central axis, the effect can be exerted33# from two neighbors, but they may have conflicting ideas, so it is simplier to just move34# host node along central axis. This feedback is also modeled as a rotating spring. This two35# combined spring force can reshape the loop to desired loop formation.3637# 01/29/201838# Adding colors to simulation after testing it with consensus algorithm simulation.39# An exhibited decision for a group is necessary to assign the colors. There was no such40# concept before, so the exhibited decision is defined as first node's choice.4142# 02/15/201843# Add the square and circle as the targets for loop reshape.444546import pygame47from formation_functions import *48import matplotlib.pyplot as plt49from matplotlib import gridspec5051import sys, os, math, random, time, getopt52import numpy as np5354# read simulation options from arguments55form_opts = [0,0] # indicating where the initial and target formation comes from56# '0' for randomly generated57# '1' for read from file58form_files = [0,0] # filenames for the formations if read from file59gen_save = False # whether saving all or none generated formations60show_bargraph = True # whether showing bargraphs of the preference distributions61try:62opts, args = getopt.getopt(sys.argv[1:], 'i:t:', ['gensave','nobargraph'])63except getopt.GetoptError as err:64print str(err)65sys.exit()66for opt,arg in opts:67if opt == '-i':68# read initial formation from file69form_opts[0] = 170form_files[0] = arg # get the filename of the initial formation71elif opt == '-t':72# read target formation from file73form_opts[1] = 174form_files[1] = arg # get the filename of the target formation75elif opt == '--gensave':76# save any generated formations77gen_save = True78elif opt == '--nobargraph':79show_bargraph = False8081########################### start of section 1 ###########################8283# initialize the pygame84pygame.init()8586# name of the folder under save directory that stores loop formation files87loop_folder = 'loop-data'8889# parameters for display, window origin is at left up corner90# screen_size: width and height in pixels91screen_size = (600, 800) # for network size of 3092# screen_size = (900, 1200) # for network size of 10093# top half for initial formation, bottom half for target formation94color_black = (0,0,0)95color_white = (255,255,255)96# a set of 20 distinct colors (black and white excluded)97distinct_color_set = ((230,25,75), (60,180,75), (255,225,25), (0,130,200), (245,130,48),98(145,30,180), (70,240,240), (240,50,230), (210,245,60), (250,190,190),99(0,128,128), (230,190,255), (170,110,40), (255,250,200), (128,0,0),100(170,255,195), (128,128,0), (255,215,180), (0,0,128), (128,128,128))101robot_size = 7 # robot modeled as dot, number of pixels for radius102group_line_wid = 4 # width of the connecting lines in the group103104# set up the simulation window and surface object105icon = pygame.image.load("icon_geometry_art.jpg")106pygame.display.set_icon(icon)107screen = pygame.display.set_mode(screen_size)108pygame.display.set_caption("Loop Reshape 2 (dynamic version)")109110# for physics, continuous world, origin is at left bottom corner, starting (0, 0),111# with x axis pointing right, y axis pointing up.112# It's more natural to compute the physics in right hand coordiante system.113world_size = (100.0, 100.0 * screen_size[1]/screen_size[0])114115# variables to configure the simulation116# poly_n: number of nodes for the polygon, also the robot quantity, at least 3117# loop_space: side length of the equilateral polygon118poly_n = 30 # for network size of 30119loop_space = 4.0 # for network size of 30120# poly_n = 100 # for network size of 100121# loop_space = 2.0 # for network size of 100122# desired loop space is a little over half of communication range123comm_range = loop_space/0.6124# upper and lower limits have equal difference to the desired loop space125space_upper = comm_range*0.9 # close but not equal to whole communication range126space_lower = comm_range*0.3127# ratio of speed to the distance difference128vel_dist_ratio = 1.0129# period for calculating new positions130# updating is not in real time, because bar graph animation takes too much time131physics_period = 500/1000.0 # second132# for the guessing of the free interior angles133int_angle_reg = math.pi - 2*math.pi/poly_n # interior angle of regular polygon134# standard deviation of the normal distribution of the guesses135int_angle_dev = (int_angle_reg - math.pi/3)/5136137# instantiate the node positions variable138nodes = [[],[]] # node positions for two formation, index is the robot's identification139nodes[0].append([0, 0]) # first node starts at origin140nodes[0].append([loop_space, 0]) # second node is loop space away on the right141nodes[1].append([0, 0])142nodes[1].append([loop_space, 0])143for i in range(2, poly_n):144nodes[0].append([0, 0]) # filled with [0,0]145nodes[1].append([0, 0])146147# construct the formation data for the two formation, either generating or from file148for i in range(2):149if form_opts[i] == 0: # option to generate a new formation150# process for generating the random equilateral polygon, two stages151poly_success = False # flag for succeed in generating the polygon152trial_count = 0 # record number of trials until a successful polygon is achieved153int_final = [] # interior angles to be saved later in file154while not poly_success:155trial_count = trial_count + 1156# print "trial {}: ".format(trial_count),157# continue trying until all the guesses can forming the desired polygon158# stage 1: guessing all the free interior angles159dof = poly_n-3 # number of free interior angles to be randomly generated160if dof > 0: # only continue guessing if at least one free interior angle161# generate all the guesses from a normal distribution162int_guesses = np.random.normal(int_angle_reg, int_angle_dev, dof).tolist()163ori_current = 0 # orientation of the line segment164no_irregular = True # flag indicating if the polygon is irregular or not165# example for irregular cases are intersecting of line segments166# or non neighbor nodes are closer than the loop space167# construct the polygon based on these guessed angles168for j in range(2, 2+dof): # for the position of j-th node169int_angle_t = int_guesses[j-2] # interior angle of previous node170ori_current = reset_radian(ori_current + (math.pi - int_angle_t))171nodes[i][j][0] = nodes[i][j-1][0] + loop_space*math.cos(ori_current)172nodes[i][j][1] = nodes[i][j-1][1] + loop_space*math.sin(ori_current)173# check the distance of node j to all previous nodes174# should not be closer than the loop space175for k in range(j-1): # exclude the previous neighbor176vect_temp = [nodes[i][k][0]-nodes[i][j][0],177nodes[i][k][1]-nodes[i][j][1]] # from j to k178dist_temp = math.sqrt(vect_temp[0]*vect_temp[0]+179vect_temp[1]*vect_temp[1])180if dist_temp < loop_space:181no_irregular = False182# print "node {} is too close to node {}".format(j, k)183break184if not no_irregular:185break186if not no_irregular:187continue # continue the while loop, keep trying new polygon188else: # if here, current interior angle guesses are good189int_final = int_guesses[:]190# although later check on the final node may still disqualify191# these guesses, the while loop will exit with a good int_final192# stage 2: use convex triangle for the rest, and deciding if polygon is possible193# solve the one last node194vect_temp = [nodes[i][0][0]-nodes[i][poly_n-2][0],195nodes[i][0][1]-nodes[i][poly_n-2][1]] # from n-2 to 0196dist_temp = math.sqrt(vect_temp[0]*vect_temp[0]+197vect_temp[1]*vect_temp[1])198# check distance between node n-2 and 0 to see if a convex triangle is possible199# the situation that whether node n-2 and 0 are too close has been excluded200if dist_temp > 2*loop_space:201# print("second last node is too far away from the starting node")202continue203else:204# calculate the position of the last node205midpoint = [(nodes[i][poly_n-2][0]+nodes[i][0][0])/2,206(nodes[i][poly_n-2][1]+nodes[i][0][1])/2]207perp_dist = math.sqrt(loop_space*loop_space - dist_temp*dist_temp/4)208perp_ori = math.atan2(vect_temp[1], vect_temp[0]) - math.pi/2209nodes[i][poly_n-1][0] = midpoint[0] + perp_dist*math.cos(perp_ori)210nodes[i][poly_n-1][1] = midpoint[1] + perp_dist*math.sin(perp_ori)211# also check any irregularity for the last node212no_irregular = True213for j in range(1, poly_n-2): # exclude starting node and second last node214vect_temp = [nodes[i][j][0]-nodes[i][poly_n-1][0],215nodes[i][j][1]-nodes[i][poly_n-1][1]] # from n-1 to j216dist_temp = math.sqrt(vect_temp[0]*vect_temp[0]+217vect_temp[1]*vect_temp[1])218if dist_temp < loop_space:219no_irregular = False220# print "last node is too close to node {}".format(j)221break222if no_irregular:223poly_success = True # reverse the flag224if i == 0: # for print message225print "initial formation generated at trial {}".format(trial_count)226else:227print "target formation generated at trial {}".format(trial_count)228# print("successful!")229# if here, a polygon has been successfully generated, save any new formation230if not gen_save: continue # skip following if option is not to save it231new_filename = get_date_time()232new_filepath = os.path.join(os.getcwd(), loop_folder, new_filename)233if os.path.isfile(new_filepath):234new_filename = new_filename + '-(1)' # add a suffix to avoid overwrite235new_filepath = new_filepath + '-(1)'236f = open(new_filepath, 'w')237f.write(str(poly_n) + '\n') # first line is the number of sides of the polygon238for j in int_final: # only recorded guessed interior angles239f.write(str(j) + '\n') # convert float to string240f.close()241# message for a file has been saved242if i == 0:243print('initial formation saved as "' + new_filename + '"')244else:245print('target formation saved as "' + new_filename + '"')246else: # option to read formation from file247new_filepath = os.path.join(os.getcwd(), loop_folder, form_files[i])248f = open(new_filepath, 'r') # read only249# check if the loop has the same number of side250if int(f.readline()) == poly_n:251# continue getting the interior angles252int_angles = []253new_line = f.readline()254while len(new_line) != 0: # not the end of the file yet255int_angles.append(float(new_line)) # add the new angle256new_line = f.readline()257# check if this file has the number of interior angles as it promised258if len(int_angles) != poly_n-3: # these many angles will determine the polygon259# the number of sides is not consistent inside the file260print 'file "{}" has incorrect number of interior angles'.format(form_files[i])261sys.exit()262# if here the data file is all fine, print message for this263if i == 0:264print 'initial formation read from file "{}"'.format(form_files[i])265else:266print 'target formation read from file "{}"'.format(form_files[i])267# construct the polygon from these interior angles268ori_current = 0 # orientation of current line segment269for j in range(2, poly_n-1):270int_angle_t = int_angles[j-2] # interior angle of previous node271ori_current = reset_radian(ori_current + (math.pi - int_angle_t))272nodes[i][j][0] = nodes[i][j-1][0] + loop_space*math.cos(ori_current)273nodes[i][j][1] = nodes[i][j-1][1] + loop_space*math.sin(ori_current)274# no need to check any irregularities275vect_temp = [nodes[i][0][0]-nodes[i][poly_n-2][0],276nodes[i][0][1]-nodes[i][poly_n-2][1]] # from node n-2 to 0277dist_temp = math.sqrt(vect_temp[0]*vect_temp[0]+278vect_temp[1]*vect_temp[1])279midpoint = [(nodes[i][poly_n-2][0]+nodes[i][0][0])/2,280(nodes[i][poly_n-2][1]+nodes[i][0][1])/2]281perp_dist = math.sqrt(loop_space*loop_space - dist_temp*dist_temp/4)282perp_ori = math.atan2(vect_temp[1], vect_temp[0]) - math.pi/2283nodes[i][poly_n-1][0] = midpoint[0] + perp_dist*math.cos(perp_ori)284nodes[i][poly_n-1][1] = midpoint[1] + perp_dist*math.sin(perp_ori)285else:286# the number of sides is not the same with poly_n specified here287print 'file "{}" has incorrect number of sides of polygon'.format(form_files[i])288sys.exit()289290# shift the two polygon to the top and bottom halves291nodes = np.array(nodes) # convert to numpy array292for i in range(2):293# calculate the geometry center of current polygon294geometry_center = np.mean(nodes[i], axis=0)295# shift the polygon to the top or bottom half of the screen296nodes[i,:,0] = nodes[i,:,0] - geometry_center[0] + world_size[0]/2297if i == 0: # initial formation shift to top half298nodes[i,:,1] = nodes[i,:,1] - geometry_center[1] + 3*world_size[1]/4299else: # target formation shift to bottom half300nodes[i,:,1] = nodes[i,:,1] - geometry_center[1] + world_size[1]/4301302# draw the two polygons303screen.fill(color_white)304for i in range(2):305# draw the nodes and line segments306disp_pos = [[0,0] for j in range(poly_n)]307# calculate the display pos for all nodes, draw them as red dots308for j in range(0, poly_n):309disp_pos[j] = world_to_display(nodes[i][j], world_size, screen_size)310pygame.draw.circle(screen, color_black, disp_pos[j], robot_size, 0)311# draw an outer circle to mark the starting node312pygame.draw.circle(screen, color_black, disp_pos[0], int(robot_size*1.5), 1)313# line segments for connecitons on the loop314for j in range(poly_n-1):315pygame.draw.line(screen, color_black, disp_pos[j], disp_pos[j+1])316pygame.draw.line(screen, color_black, disp_pos[poly_n-1], disp_pos[0])317pygame.display.update()318319# raw_input("<Press Enter to continue>") # halt the program to check the networks320321########################### start of section 2 ###########################322323# calculate the interior angles of the two formations324# It's not necessary to do the calculation again, but may have this part ready325# for the dynamic version of the program for the loop reshape simulation.326inter_ang = [[0 for j in range(poly_n)] for i in range(2)]327for i in range(2):328for j in range(poly_n):329# for the interior angles of initial setup formation330node_h = nodes[i][j] # host node331node_l = nodes[i][(j-1)%poly_n] # node on the left332node_r = nodes[i][(j+1)%poly_n] # node on the right333vect_l = [node_l[0]-node_h[0], node_l[1]-node_h[1]] # from host to left334vect_r = [node_r[0]-node_h[0], node_r[1]-node_h[1]] # from host to right335# get the angle rotating from vect_r to vect_l336# # temporary fix for square formation337# try:338# inter_ang[i][j] = math.acos((vect_l[0]*vect_r[0] + vect_l[1]*vect_r[1])/339# (loop_space*loop_space))340# except:341# inter_ang[i][j] = math.acos(-1.0)342inter_ang[i][j] = math.acos((vect_l[0]*vect_r[0] + vect_l[1]*vect_r[1])/343(loop_space*loop_space))344if (vect_r[0]*vect_l[1] - vect_r[1]*vect_l[0]) < 0:345# cross product of vect_r to vect_l is smaller than 0346inter_ang[i][j] = 2*math.pi - inter_ang[i][j]347# the result interior angles should be in range of [0, 2*pi)348349# rename the interior angle variables to be used later350# use interior angle instead of deviation angle because they should be equivalent351inter_curr = inter_ang[0][:] # interior angles of initial(dynamic) setup formation352inter_targ = inter_ang[1][:] # interior angles of target formation353# variable for the preferability distribution354pref_dist = np.zeros((poly_n, poly_n))355# variable indicating which target node has largest probability in the distributions356# this also represents which node it mostly prefers357domi_node = [0 for i in range(poly_n)] # dominant node for the distributions358# divide nodes on loop to groups based on dominant node359# only adjacent block of nodes are in same group if they agree on dominant node360groups = [] # lists of adjacent nodes inside361# variable indicating how many nodes are there in the same group with host node362group_sizes = [0 for i in range(poly_n)] # size of the group the node is in363color_initialized = False # whether the color assignment has run the first time364deci_colors = [-1 for i in range(poly_n)] # color index for each exhibited decision365# -1 for not assigned366color_assigns = [0 for i in range(20)] # number of assignments for each color367group_colors = [] # color for the groups368node_colors = [0 for i in range(poly_n)] # color for the nodes369# overflow threshold for the distribution difference370dist_diff_thres = 0.3371# variable for ratio of distribution difference to threshold, for tuning growing rate372# in range of [0,1], higher the ratio, slower it grows373dist_diff_ratio = [0 for i in range(poly_n)]374# exponent of the power function to map the ratio to a slower growing value375dist_diff_power = 0.3376# linear spring constant modeling the pulling and pushing effects of the neighbor nodes377linear_const = 1.0378# bending spring constant modeling the bending effect from host node itself379bend_const = 0.8380# disp_coef: coefficient from feedback vector to displacement381# disp_coef = 0.5 # for network size of 30382disp_coef = 0.2 # for network size of 100383384# calculate the initial preferability distribution385for i in range(poly_n):386# the angle difference of inter_curr[i] to all angles in inter_targ387ang_diff = [0 for j in range(poly_n)]388ang_diff_sum = 0389for j in range(poly_n):390# angle difference represents the effort of change between two angles391# the more effort, the less the preferability, so take reciprocal392ang_diff[j] = 1/abs(inter_curr[i]-inter_targ[j])393# summation of ang_diff394ang_diff_sum = ang_diff_sum + ang_diff[j]395# convert to preferability distribution396for j in range(poly_n):397# linearize all probabilities such that sum(pref_dist[i])=1398pref_dist[i][j] = ang_diff[j]/ang_diff_sum399400if show_bargraph:401# matplotlib method of bar graph animation402# adjust figure and grid size here403fig = plt.figure(figsize=(16,12), tight_layout=True)404fig.canvas.set_window_title('Evolution of Preferability Distribution')405gs = gridspec.GridSpec(5, 6)406ax = [fig.add_subplot(gs[i]) for i in range(poly_n)]407rects = [] # bar chart subplot rectangle handler408x_pos = range(poly_n)409for i in range(poly_n):410rects.append(ax[i].bar(x_pos, pref_dist[i], align='center'))411ax[i].set_xlim(-1, poly_n) # y limit depends on data set412413# the loop414sim_exit = False # simulation exit flag415sim_pause = False # simulation pause flag416iter_count = 0417graph_iters = 1 # draw the distribution graphs every these many iterations418while not sim_exit:419# exit the program by close window button, or Esc or Q on keyboard420for event in pygame.event.get():421if event.type == pygame.QUIT:422sim_exit = True # exit with the close window button423if event.type == pygame.KEYUP:424if event.key == pygame.K_SPACE:425sim_pause = not sim_pause # reverse the pause flag426if (event.key == pygame.K_ESCAPE) or (event.key == pygame.K_q):427sim_exit = True # exit with ESC key or Q key428429# skip the rest if paused430if sim_pause: continue431432# prepare information for the preferability distribution evolution433# find the dominant node in each of the distributions434for i in range(poly_n):435domi_node_t = 0 # initialize the dominant node with the first one436domi_prob_t = pref_dist[i][0]437for j in range(1, poly_n):438if pref_dist[i][j] > domi_prob_t:439domi_node_t = j440domi_prob_t = pref_dist[i][j]441domi_node[i] = domi_node_t442# update the groups443groups = [[0]] # initialize with a node 0 robot444group_deci = [] # the exhibited decision of the groups445for i in range(1, poly_n):446if (domi_node[i-1]+1)%poly_n == domi_node[i]: # i-1 and i agree on dominant node447# simply add i to same group with i-1448groups[-1].append(i)449else:450# add a new group for node i in groups451groups.append([i])452# check if starting and ending robots should be in same groups453if (domi_node[poly_n-1]+1)%poly_n == domi_node[0] and len(groups)>1:454# add the first group to the last group455for i in groups[0]:456groups[-1].append(i)457groups.pop(0) # pop out the first group458# update group size459group_sizes = [0 for i in range(poly_n)] # initialize with all 0460for group in groups:461group_size_t = len(group)462for i in group:463group_sizes[i] = group_size_t464# update the exhibited decision for the groups465for i in range(len(groups)):466group_deci.append((domi_node[groups[i][0]] - groups[i][0]) % poly_n)467468# update colors for the exhibited decisions469if not color_initialized:470color_initialized = True471select_set = range(20) # the initial selecting set472all_deci_set = set(group_deci) # put all exhibited decisions in a set473for deci in all_deci_set: # avoid checking duplicate decisions474if len(select_set) == 0:475select_set = range(20) # start a new set to select from476chosen_color = np.random.choice(select_set)477select_set.remove(chosen_color)478deci_colors[deci] = chosen_color # assign the chosen color to decision479# increase the assignments of chosen color by 1480color_assigns[chosen_color] = color_assigns[chosen_color] + 1481else:482# remove the color for a decision, if it's no longer the decision of any group483all_deci_set = set(group_deci)484for i in range(poly_n):485if deci_colors[i] != -1: # there was a color assigned before486if i not in all_deci_set:487# decrease the assignments of chosen color by 1488color_assigns[deci_colors[i]] = color_assigns[deci_colors[i]] - 1489deci_colors[i] = -1 # remove the assigned color490# assign color for an exhibited decision if not assigned491select_set = [] # set of colors to select from, start from empty492for i in range(len(groups)):493if deci_colors[group_deci[i]] == -1:494if len(select_set) == 0:495# construct a new select_set496color_assigns_min = min(color_assigns)497color_assigns_temp = [j - color_assigns_min for j in color_assigns]498select_set = range(20)499for j in range(20):500if color_assigns_temp[j] != 0:501select_set.remove(j)502# if here, the select_set is good to go503chosen_color = np.random.choice(select_set)504select_set.remove(chosen_color)505deci_colors[group_deci[i]] = chosen_color # assign the chosen color506# increase the assignments of chosen color by 1507color_assigns[chosen_color] = color_assigns[chosen_color] + 1508# update the color for the groups and nodes509group_colors = []510for i in range(len(groups)):511color_temp = deci_colors[group_deci[i]]512group_colors.append(color_temp) # color for the group513for node in groups[i]:514node_colors[node] = color_temp # color for the node515516# preferability distribution evolution517pref_dist_t = np.copy(pref_dist) # deep copy the 'pref_dist', intermediate variable518for i in range(poly_n):519i_l = (i-1)%poly_n # index of neighbor on the left520i_r = (i+1)%poly_n # index of neighbor on the right521# shifted distribution from left neighbor522dist_l = [pref_dist_t[i_l][-1]] # first one copied from one at end523for j in range(poly_n-1):524dist_l.append(pref_dist_t[i_l][j])525# shifted distribution from right neighbor526dist_r = []527for j in range(1, poly_n):528dist_r.append(pref_dist_t[i_r][j])529dist_r.append(pref_dist_t[i_r][0]) # last one copied from one at starting530# calculating if two neighbors have converged ideas with host robot531converge_l = False532if (domi_node[i_l]+1)%poly_n == domi_node[i]: converge_l = True533converge_r = False534if (domi_node[i_r]-1)%poly_n == domi_node[i]: converge_r = True535# weighted averaging depending on group property536if converge_l and converge_r: # all three neighbors are in the same group537# step 1: take equal weighted average on all three distributions538dist_sum = 0539for j in range(poly_n):540pref_dist[i][j] = dist_l[j] + pref_dist_t[i][j] + dist_r[j]541dist_sum = dist_sum + pref_dist[i][j]542# linearize the distribution543pref_dist[i] = pref_dist[i]/dist_sum544# step 2: increase the unipolarity by applying the linear multiplier545# (step 2 is only for when both neighbors have converged opinions)546# first find the largest difference in two of the three distributions547dist_diff = [0, 0, 0] # variable for difference of three distribution548# distribution difference of left neighbor and host549for j in range(poly_n):550# difference of two distributions is sum of absolute individual differences551# use current step's distribution for distribution difference552dist_diff[0] = dist_diff[0] + abs(dist_l[j]-pref_dist_t[i][j])553# distribution difference of host and right neighbor554for j in range(poly_n):555dist_diff[1] = dist_diff[1] + abs(pref_dist_t[i][j]-dist_r[j])556# distribution difference of left and right neighbors557for j in range(poly_n):558dist_diff[2] = dist_diff[2] + abs(dist_l[j]-dist_r[j])559# maximum distribution differences560dist_diff_max = max(dist_diff)561if dist_diff_max < dist_diff_thres:562dist_diff_ratio[i] = dist_diff_max/dist_diff_thres # for debugging563# will skip step 2 if maximum difference is larger than the threshold564# linear multiplier is generated from the smallest and largest probabilities565# the smaller end is linearly mapped from largest distribution difference566# '1.0/poly_n' is the average of the linear multiplier567small_end = 1.0/poly_n * np.power(dist_diff_max/dist_diff_thres, dist_diff_power)568large_end = 2.0/poly_n - small_end569# sort the magnitude of processed distribution570dist_t = np.copy(pref_dist[i]) # temporary distribution571sort_index = range(poly_n)572for j in range(poly_n-1): # bubble sort, ascending order573for k in range(poly_n-1-j):574if dist_t[k] > dist_t[k+1]:575# exchange values in 'dist_t'576temp = dist_t[k]577dist_t[k] = dist_t[k+1]578dist_t[k+1] = temp579# exchange values in 'sort_index'580temp = sort_index[k]581sort_index[k] = sort_index[k+1]582sort_index[k+1] = temp583# applying the linear multiplier584dist_sum = 0585for j in range(poly_n):586multiplier = small_end + float(j)/(poly_n-1) * (large_end-small_end)587pref_dist[i][sort_index[j]] = pref_dist[i][sort_index[j]] * multiplier588dist_sum = dist_sum + pref_dist[i][sort_index[j]]589# linearize the distribution590pref_dist[i] = pref_dist[i]/dist_sum591else:592dist_diff_ratio[i] = 1.0 # for debugging, ratio overflowed593else: # at least one side has not converged yet594dist_diff_ratio[i] = -1.0 # indicating linear multiplier was not used595# take unequal weight in the averaging process based on group property596group_size_l = group_sizes[i_l]597group_size_r = group_sizes[i_r]598# taking the weighted average599dist_sum = 0600for j in range(poly_n):601# weight on left is group_size_l, on host is 1, on right is group_size_r602pref_dist[i][j] = (dist_l[j]*group_size_l + pref_dist[i][j] +603dist_r[j]*group_size_r)604dist_sum = dist_sum + pref_dist[i][j]605pref_dist[i] = pref_dist[i]/dist_sum606607##### previous motion strategy #####608# # physics update, and carry out one iteration of position update609# for i in range(poly_n):610# node_h = nodes[0][i] # position of host node611# node_l = nodes[0][(i-1)%poly_n] # position of left neighbor612# node_r = nodes[0][(i+1)%poly_n] # position of right neighbor613# # find the central axis between the two neighbors614# pos_m = [(node_l[0]+node_r[0])/2, (node_l[1]+node_r[1])/2] # the origin on the axis615# vect_rl = [node_l[0]-node_r[0], node_l[1]-node_r[1]] # from node_r to node_l616# # distance of the two neighbors617# dist_rl = math.sqrt(vect_rl[0]*vect_rl[0]+vect_rl[1]*vect_rl[1])618# vect_rl = [vect_rl[0]/dist_rl, vect_rl[1]/dist_rl] # become unit vector619# vect_ax = [-vect_rl[1], vect_rl[0]] # central axis pointing outwords of the polygon620# vect_ax_ang = math.atan2(vect_ax[1], vect_ax[0])621# # all destinations will be defined as how much distance to pos_m along the axis622623# # find the target destination that satisfies desired interior angle624# ang_targ = inter_targ[domi_node[i]] # dynamic target interior angle625# # distance of target position along the axis626# targ_dist = loop_space*math.cos(ang_targ/2)627# # reverse distance if interior angle is over pi628# if ang_targ > math.pi: targ_dist = -targ_dist629630# # find the stable destination that satisfies the desired loop space631# # then decide the final destination by comparing with target destination632# final_dist = 0 # variable for the final destination633# # Following discussion is based on the range of dist_rl being divided into four634# # regions by three points, they are 2*space_upper, 2*loop_space, and 2*space_lower.635# if dist_rl >= 2*space_upper:636# # two neighbors are too far away, over the upper space limit the host can reach637# # no need to compare with target destination, ensure connection first638# final_dist = 0 # final destination is at origin639# elif dist_rl >= 2*loop_space and dist_rl < 2*space_upper:640# # the final destination has a tight single range, stable destination is at origin641# stab_dist = 0642# # calculate the half range for the final destination643# # the rangeis symmetric about the origin, lower range is negative of upper range644# range_upper = math.sqrt(space_upper*space_upper-dist_rl*dist_rl/4)645# # provisional final destination, balance between interior angle and loop space646# # final_dist = (targ_dist+stab_dist)/2647# final_dist = targ_dist*0.618 + stab_dist*0.382648# # set final destination to limiting positions if exceeding them649# final_dist = max(min(final_dist, range_upper), -range_upper)650# elif dist_rl >= 2*space_lower and dist_rl < 2*loop_space:651# # the final destination has only one range652# # but two stable destinations, will choose one closer to target destination653# stab_dist = math.sqrt(loop_space*loop_space-dist_rl*dist_rl/4)654# range_upper = math.sqrt(space_upper*space_upper-dist_rl*dist_rl/4)655# # check which stable destination the target destination is closer to656# if abs(targ_dist-stab_dist) < abs(targ_dist+stab_dist):657# # closer to stable destination at positive side658# # final_dist = (targ_dist+stab_dist)/2659# final_dist = targ_dist*0.618 + stab_dist*0.382660# else:661# # closer to stable destination at negative side662# # final_dist = (targ_dist-stab_dist)/2663# final_dist = targ_dist*0.618 + (-stab_dist)*0.382664# # set final destination to limiting positions if exceeding them665# final_dist = max(min(final_dist, range_upper), -range_upper)666# elif dist_rl < 2*space_lower:667# # final destination has two possible ranges to choose from668# # will take one on the side where the node is currently located669# stab_dist = math.sqrt(loop_space*loop_space-dist_rl*dist_rl/4)670# range_upper = math.sqrt(space_upper*space_upper-dist_rl*dist_rl/4)671# range_lower = math.sqrt(space_lower*space_lower-dist_rl*dist_rl/4)672# # find out the current side of the node673# vect_lr = [-vect_rl[0], -vect_rl[1]] # vector from left neighbor to right674# vect_lh = [node_h[0]-node_l[0], node_h[1]-node_l[1]] # from left to host675# # cross product of vect_lh and vect_lr676# if vect_lh[0]*vect_lr[1]-vect_lh[1]*vect_lr[0] >= 0:677# # host node is at positive side678# # final_dist = (targ_dist+stab_dist)/2679# final_dist = targ_dist*0.618 + stab_dist*0.382680# # avoid overflow in range [range_lower, range_upper]681# final_dist = max(min(final_dist, range_upper), range_lower)682# else:683# # host node is at negative side684# # final_dist = (targ_dist-stab_dist)/2685# final_dist = targ_dist*0.618 + (-stab_dist)*0.382686# # avoid overflow in range [-range_upper, -range_lower]687# final_dist = max(min(final_dist, -range_lower), -range_upper)688# # calculate the position of the final destination, where the node desires to move to689# final_des = [pos_m[0] + final_dist*math.cos(vect_ax_ang),690# pos_m[1] + final_dist*math.sin(vect_ax_ang)]691692# # calculate the velocity and orientation based on the final destination693# vect_des = [final_des[0]-node_h[0], final_des[1]-node_h[1]] # from host to des694# vect_des_dist = math.sqrt(vect_des[0]*vect_des[0] + vect_des[1]*vect_des[1])695# # velocity is proportional to the distance to the final destination696# vel = vel_dist_ratio*vect_des_dist697# ori = math.atan2(vect_des[1], vect_des[0])698699# # carry out one step of update on the position700# nodes[0][i] = [node_h[0] + vel*physics_period*math.cos(ori),701# node_h[1] + vel*physics_period*math.sin(ori)]702703##### new SMA motion algorithm based on 'loop_reshape_test_moiton.py' #####704# variable for the neighboring nodes on the loop705dist_neigh = [0 for i in range(poly_n)]706# update the dynamic neighbor distances of the loop707for i in range(poly_n):708vect_r = nodes[0][(i+1)%poly_n] - nodes[0][i] # from host to right node709dist_neigh[i] = math.sqrt(vect_r[0]*vect_r[0] + vect_r[1]*vect_r[1])710# update the dynamic interior angles of the loop711for i in range(poly_n):712i_l = (i-1)%poly_n713i_r = (i+1)%poly_n714vect_l = nodes[0][i_l] - nodes[0][i]715vect_r = nodes[0][i_r] - nodes[0][i]716inter_curr[i] = math.acos((vect_l[0]*vect_r[0] + vect_l[1]*vect_r[1])/717(dist_neigh[i_l] * dist_neigh[i]))718if (vect_r[0]*vect_l[1] - vect_r[1]*vect_l[0]) < 0:719inter_curr[i] = 2*math.pi - inter_curr[i]720# the feedback from all spring effects721fb_vect = [np.zeros([1,2]) for i in range(poly_n)]722for i in range(poly_n):723i_l = (i-1)%poly_n724i_r = (i+1)%poly_n725# unit vector from host to left726vect_l = (nodes[0][i_l]-nodes[0][i])/dist_neigh[i_l]727# unit vector from host to right728vect_r = (nodes[0][i_r]-nodes[0][i])/dist_neigh[i]729# unit vector along central axis pointing inward the polygon730vect_lr = nodes[0][i_r]-nodes[0][i_l] # from left neighbor to right731dist_temp = math.sqrt(vect_lr[0]*vect_lr[0]+vect_lr[1]*vect_lr[1])732vect_in = np.array([-vect_lr[1]/dist_temp, vect_lr[0]/dist_temp]) # rotate ccw pi/2733734# add the pulling or pushing effect from left neighbor735fb_vect[i] = fb_vect[i] + (dist_neigh[i_l]-loop_space) * linear_const * vect_l736# add the pulling or pushing effect from right neighbor737fb_vect[i] = fb_vect[i] + (dist_neigh[i]-loop_space) * linear_const * vect_r738# add the bending effect initialized by the host node itself739fb_vect[i] = fb_vect[i] + ((inter_targ[domi_node[i]]-inter_curr[i])*740bend_const * vect_in)741# update positions once, comment below if wishing to see the decision process only742nodes[0][i] = nodes[0][i] + disp_coef * fb_vect[i]743744# use delay to slow down the physics update when bar graph animation is skpped745# not clean buy quick way to adjust simulation speed746time.sleep(0.2) # in second747748# iteration count update749print("iteration count {}".format(iter_count))750iter_count = iter_count + 1 # update iteration count751752# graphics update753if iter_count%graph_iters == 0:754755if show_bargraph:756# graphics update for the bar graph757# find the largest y data in all distributions as up limit in graphs758y_lim = 0.0759for i in range(poly_n):760for j in range(poly_n):761if pref_dist[i][j] > y_lim:762y_lim = pref_dist[i][j]763y_lim = min(1.0, y_lim*1.1) # leave some gap764# matplotlib method765for i in range(poly_n):766for j in range(poly_n):767rects[i][j].set_height(pref_dist[i][j])768ax[i].set_title('{} -> {} -> {:.2f}'.format(i, group_sizes[i], dist_diff_ratio[i]))769ax[i].set_ylim(0.0, y_lim)770fig.canvas.draw()771fig.show()772773# graphics update for the pygame window774screen.fill(color_white)775for i in range(2):776# calculate the display pos for all nodes777disp_pos = [[0,0] for j in range(poly_n)]778for j in range(0, poly_n):779disp_pos[j] = world_to_display(nodes[i][j], world_size, screen_size)780# draw the connecting lines781for j in range(poly_n-1):782pygame.draw.line(screen, color_black, disp_pos[j], disp_pos[j+1])783pygame.draw.line(screen, color_black, disp_pos[poly_n-1], disp_pos[0])784# draw the group connections and nodes for top and bottom formations785if i == 0: # for top formation786# highlight the group connections787for j in range(len(groups)):788for k in range(len(groups[j])-1):789pair_l = groups[j][k]790pair_r = groups[j][k+1]791pygame.draw.line(screen, distinct_color_set[group_colors[j]],792disp_pos[pair_l], disp_pos[pair_r], group_line_wid)793if len(groups) == 1:794# draw extra segment for starting and end node795pair_l = groups[0][-1]796pair_r = groups[0][0]797pygame.draw.line(screen, distinct_color_set[group_colors[0]],798disp_pos[pair_l], disp_pos[pair_r], group_line_wid)799# draw all nodes as their group colors800for j in range(poly_n):801pygame.draw.circle(screen, distinct_color_set[node_colors[j]],802disp_pos[j], robot_size, 0)803else: # for bottom formation804# draw all nodes as black dots805for j in range(poly_n):806pygame.draw.circle(screen, color_black, disp_pos[j], robot_size, 0)807# draw an outer circle to mark the starting node808pygame.draw.circle(screen, color_black, disp_pos[0], int(robot_size*1.5), 1)809pygame.display.update()810811# hold the program to check the networks812# raw_input("<Press Enter to continue>")813814815