# random 2D network generator for robot swarm on 2D equilateral triangle grid1# the networks are connected in one piece, there is a path between any pair of nodes23# Constraint of triangle grid guaranteed the robots are uniformed spaced, and 6 connections4# at most for each robot. Robots will reside in the joint of edges, the edges represents the5# connections between robots.67# Equilateral triangle grid coordinates:8# Position of a node on a 2D plane has two degrees of freedom. Following this rule, even9# though three intersecting axes can be drew on the triangle grid, only two are chosen as10# the coordinates. The chosen two axes are angled at pi/3, with x pointing right, and y11# pointing pi/3 CCW relative to x, like a warped Cartesian coordinate system. The nodes can12# be located by drawing parallel lines to the two aces. The position of a node is similarly13# described as (x,y) in integers.1415# No topology duplication check:16# It's possible that two generated networks having different grid layouts may share the same17# topology. It's preferable to combine these two layouts, because the result of the algorithm18# test is only related to network topology. But since telling whether one grid layout has the19# same topology with another is with too much effort, I just use the grid layout as test20# subject of topology. Besides, it's not that often that two layouts are of same topology21# when network size is very large.222324import math, random, sys, os, time25import matplotlib.pyplot as plt26import getopt2728def main():29size = 0 # network size to be read from input3031savefile = True32# read command line options33try:34opts, args = getopt.getopt(sys.argv[1:], 'n:', ['nosave'])35except getopt.GetoptError as err:36print str(err)37sys.exit()38for opt,arg in opts:39if opt == '-n':40size = int(arg)41elif opt == '--nosave':42savefile = False4344node_size = 10 # default 104546# use list to store the node information47nodes_t = [] # target nodes pool, for decided nodes in target network48nodes_a = [] # available nodes pool, for nodes available to be added to network49# place the first node at the origin for the network50nodes_t.append((0,0))51for pos in get_neighbors(nodes_t[0]):52nodes_a.append(pos) # append all six neighbors to available pool5354# loop for randomly placing new nodes from available pool to generate the network55for i in range(size-1): # first node is decided and excluded56# randomly choose one from the available pool57pos_new = random.choice(nodes_a)58nodes_a.remove(pos_new) # remove selected node from available pool59nodes_t.append(pos_new) # add new node to the target pool60# check and update every neighbor of newly selected node61for pos in get_neighbors(pos_new):62if pos in nodes_t: continue63if pos in nodes_a: continue64# if none of the above, add to the available pool65nodes_a.append(pos)6667# generate the connection variable, 0 for not connected, 1 for connected68connections = [[0 for j in range(size)] for i in range(size)] # populated with zeros69for i in range(size):70for j in range(i+1, size):71# find if nodes[i] and nodes[j] are neighbors72diff_x = nodes_t[i][0] - nodes_t[j][0]73diff_y = nodes_t[i][1] - nodes_t[j][1]74if abs(diff_x) + abs(diff_y) == 1 or diff_x * diff_y == -1:75# condition 1: one of the axis value difference is 1, the other is 076# condition 2: one of the axis value difference is 1, the other is -177connections[i][j] = 178connections[j][i] = 17980# the network has been generated, save it to file81if savefile:82save_folder = 'trigrid-networks' # folder for triangle grid network files83save_path = os.path.join(os.getcwd(), save_folder)84filename_count = 1 # always start to try filename with suffix of 185new_filename = str(size) + '-' + str(filename_count)86all_files = os.listdir(save_path)87# check to avoid filename duplication88while new_filename in all_files:89filename_count = filename_count + 190new_filename = str(size) + '-' + str(filename_count)91new_filepath = os.path.join(save_path, new_filename)92f = open(new_filepath, 'w')93for pos in nodes_t:94f.write(str(pos[0]) + ' ' + str(pos[1]) + '\n')95f.close()9697# plot the network as dots and lines98fig_side_size = int(math.sqrt(size)*0.7) # calculate fig side size from network size99fig = plt.figure(figsize=(fig_side_size, fig_side_size)) # square fig100fig.canvas.set_window_title('2D Triangle Grid Network Generator')101splt = fig.add_subplot(1,1,1)102splt.axis('equal') # equal scale for x and y axis103splt.tick_params(axis='both',104which='both',105bottom='off',106top='off',107left='off',108right='off',109labelbottom='off',110labelleft='off') # turn off ticks and labels111# convert node position from triangle grid to Cartesian, for plotting112nodes_t_plt = [trigrid_to_cartesian(pos) for pos in nodes_t]113# set x and y axes limits114xmin = min([pos[0] for pos in nodes_t_plt])115xmax = max([pos[0] for pos in nodes_t_plt])116ymin = min([pos[1] for pos in nodes_t_plt])117ymax = max([pos[1] for pos in nodes_t_plt])118splt.set_xlim([xmin-0.5, xmax+0.5]) # leave space on both sides119splt.set_ylim([ymin-0.5, ymax+0.5])120# draw the connections as lines121for i in range(size):122for j in range(i+1, size):123if connections[i][j] == 1:124splt.plot([nodes_t_plt[i][0], nodes_t_plt[j][0]],125[nodes_t_plt[i][1], nodes_t_plt[j][1]], '-k')126for i in range(size):127splt.plot(nodes_t_plt[i][0], nodes_t_plt[i][1], 'o',128markersize=node_size, markerfacecolor='black')129130# show the figure, press to exit131fig.show()132# choose one of the following two lines133raw_input("Press <ENTER> to continue")134plt.close(fig)135136137# return the positions of the six neighbors of the input node on triangle grid138# The first four neighbors are just like the situation in the Cartesian coordinates, the last139# two neighbors are the two on the diagonal line along the y=-x axis, because the triangle140# grid is like askewing the y axis toward x, allowing more space in second and fourth quadrants.141def get_neighbors(pos):142x = pos[0]143y = pos[1]144return [(x+1, y), (x-1, y),145(x, y+1), (x, y-1),146(x+1, y-1), (x-1, y+1)]147148# return Cartesian coordinates of triangle grid nodes for plotting149def trigrid_to_cartesian(pos):150# the resulting coordinates should be in floating point numbers151x = float(pos[0])152y = float(pos[1])153# askewing y axis to the right for pi/6154return [x+y*math.sin(math.pi/6), y*math.cos(math.pi/6)]155156if __name__ == '__main__':157main()158159160161162