266 lines
8.6 KiB
Python
Executable file
266 lines
8.6 KiB
Python
Executable file
#!/usr/bin/env python
|
|
#encoding: utf-8
|
|
|
|
import sys
|
|
import turtle
|
|
import argparse
|
|
import matplotlib.pyplot as plot
|
|
from itertools import ifilterfalse as filter_if_not
|
|
|
|
import ants
|
|
import utils
|
|
from utils import LOG,LOGN
|
|
from geometry import x,y
|
|
import hull
|
|
import uberplot
|
|
import shortpath
|
|
import lindenmayer
|
|
import geometry
|
|
import triangulation
|
|
import voronoi
|
|
import graph
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument('-p', "--penrose", help="Do not compute the Penrose tiling but load it from a file",
|
|
default=None, action='store', type=str, metavar="SEGMENTS")
|
|
parser.add_argument( '-d', '--depth', help="Recursive depth of the Lindenmayer computations = size of the Penrose tiling",
|
|
default=1, type=int, metavar="DEPTH")
|
|
|
|
parser.add_argument('-t', "--notsp", help="Do not compute the TSP",
|
|
default=False, action='store_true')
|
|
parser.add_argument('-r', "--tour", help="Load several TSP tour from a file",
|
|
default=[None], action='store', type=str, nargs="*", metavar="POINTS")
|
|
parser.add_argument('-m', "--pheromones", help="Load a pheromones matrix from a file",
|
|
default=None, action='store', type=str, metavar="MATRIX")
|
|
|
|
parser.add_argument('-g', "--triangulation", help="Do not compute the Delaunay triangulation but load it from a file",
|
|
default=None, action='store', type=str, metavar="SEGMENTS")
|
|
parser.add_argument('-v', "--voronoi", help="Do not compute the Voronoï diagram but load it from a file",
|
|
default=None, action='store', type=str, metavar="POINTS")
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
error_codes = {"NOTSP":100}
|
|
|
|
depth = args.depth
|
|
LOGN( "depth",depth )
|
|
|
|
########################################################################
|
|
# PENROSE TILING
|
|
########################################################################
|
|
|
|
penrose_segments = set()
|
|
|
|
if args.penrose:
|
|
LOGN( "Load the penrose tiling" )
|
|
with open(args.penrose) as fd:
|
|
penrose_segments = utils.load_segments(fd)
|
|
|
|
else:
|
|
LOGN( "Draw the penrose tiling" )
|
|
|
|
|
|
segment_size = 10
|
|
float_rounding = 10
|
|
|
|
ttl = turtle.Turtle()
|
|
ttl.speed('fastest')
|
|
penrose = lindenmayer.DumpTurtleLSystem(ttl,
|
|
axiom="[X]++[X]++[X]++[X]++[X]",
|
|
rules={
|
|
'F': "",
|
|
'W': "YF++ZF----XF[-YF----WF]++",
|
|
'X': "+YF--ZF[---WF--XF]+",
|
|
'Y': "-WF++XF[+++YF++ZF]-",
|
|
'Z': "--YF++++WF[+ZF++++XF]--XF"
|
|
},
|
|
angle=36, heading=0, size=segment_size, rounding=float_rounding )
|
|
|
|
# actually do something
|
|
penrose.draw( depth )
|
|
|
|
# save this intermediate step
|
|
penrose_segments = penrose.segments
|
|
LOGN( "\tsegments",len(penrose_segments) )
|
|
with open("d%i_penrose.segments" % depth, "w") as fd:
|
|
utils.write_segments( penrose_segments, fd )
|
|
|
|
|
|
|
|
########################################################################
|
|
# TSP
|
|
########################################################################
|
|
|
|
trajs = []
|
|
|
|
if args.tour != [None]:
|
|
for tour in args.tour:
|
|
with open(tour) as fd:
|
|
trajs.append( utils.load_points(fd) )
|
|
|
|
if args.notsp:
|
|
if args.tour == [None] or not args.pheromones:
|
|
LOGN( "If you do not want to solve the TSP, you must provide a solution tour (--tour) and a pheromones matrix (--pheromones)" )
|
|
sys.exit(error_codes["NO-TSP"])
|
|
|
|
if args.pheromones:
|
|
with open(args.pheromones) as fd:
|
|
phero = utils.load_matrix(fd)
|
|
|
|
else:
|
|
LOGN( "Solve the TSP with an Ant Colony Algorithm" )
|
|
|
|
LOGN( "\tConvert the segment list into an adjacency list graph" )
|
|
G = graph.graph_of( penrose_segments )
|
|
|
|
LOGN( "\tCompute a tour" )
|
|
max_it = 10
|
|
num_ants = 10 #* depth
|
|
decay = 0.1
|
|
w_heur = 2.5
|
|
w_local_phero = 0.1
|
|
c_greed = 0.9
|
|
w_history = 1.0
|
|
|
|
best,phero = ants.search( G, max_it, num_ants, decay, w_heur, w_local_phero, w_history, c_greed, cost_func = ants.graph_distance )
|
|
|
|
LOGN( "\tTransform the resulting nodes permutation into a path on the graph" )
|
|
# by finding the shortest path between two cities.
|
|
traj = []
|
|
for start,end in utils.tour(best["permutation"]):
|
|
p,c = shortpath.astar( G, start, end )
|
|
traj += p
|
|
trajs.append(traj)
|
|
|
|
with open("d%i_tour.points" % depth, "w") as fd:
|
|
utils.write_points( traj, fd )
|
|
|
|
with open("d%i_pheromones.mat" % depth, "w") as fd:
|
|
utils.write_matrix( phero, fd )
|
|
|
|
|
|
########################################################################
|
|
# TRIANGULATION
|
|
########################################################################
|
|
|
|
triangulated = []
|
|
|
|
if args.triangulation:
|
|
with open(args.triangulation) as fd:
|
|
triangulated = triangulation.load(fd)
|
|
|
|
else:
|
|
LOGN( "Compute the triangulation of the penrose vertices" )
|
|
points = utils.vertices_of(penrose_segments)
|
|
triangles = triangulation.delaunay_bowyer_watson( points, do_plot = False )
|
|
|
|
LOGN( "\tRemove triangles that are not sub-parts of the Penrose tiling" )
|
|
|
|
|
|
def strictly_acute(triangle):
|
|
return triangulation.is_acute( triangle, exclude_edges = True )
|
|
# Filter (i.e. keep) triangles that are strictly acute,
|
|
# By excluding edges, we also ensure that no triangle can be collinear nor rectangle,
|
|
triangulated = list(filter( strictly_acute, triangles ))
|
|
# A more consise but less readable one-liner would be:
|
|
# triangulated = list(filter( lambda t: triangulation.is_acute( t, exclude_edges = True ), triangles ))
|
|
|
|
LOGN( "\t\tRemoved", len(triangles)-len(triangulated), "triangles from", len(triangles))
|
|
|
|
with open("d%i_triangulation.triangles" % depth, "w") as fd:
|
|
triangulation.write( triangulated, fd )
|
|
|
|
triangulation_edges = triangulation.edges_of( triangulated )
|
|
|
|
|
|
########################################################################
|
|
# VORONOÏ
|
|
########################################################################
|
|
|
|
voronoi_graph = {}
|
|
|
|
if args.voronoi:
|
|
with open(args.voronoi) as fd:
|
|
voronoi_graph = graph.load( fd )
|
|
|
|
else:
|
|
LOGN( "Compute the Voronoï diagram of the triangulation" )
|
|
voronoi_tri_graph = voronoi.dual(triangulated)
|
|
# voronoi_tri_edges = graph.edges_of(voronoi_tri_graph)
|
|
# voronoi_tri_centers = graph.nodes_of(voronoi_tri_graph)
|
|
|
|
LOGN("\tMerge nodes that are both located within a single diamond" )
|
|
LOG("\t\tMerge",len(voronoi_tri_graph),"nodes")
|
|
voronoi_graph = voronoi.merge_enclosed( voronoi_tri_graph, penrose_segments )
|
|
LOGN("as",len(voronoi_graph),"enclosed nodes")
|
|
|
|
with open("d%i_voronoi.graph" % depth, "w") as fd:
|
|
graph.write( voronoi_graph, fd )
|
|
|
|
|
|
voronoi_edges = graph.edges_of( voronoi_graph )
|
|
voronoi_centers = graph.nodes_of( voronoi_graph )
|
|
|
|
|
|
########################################################################
|
|
# PLOT
|
|
########################################################################
|
|
|
|
LOGN( "Plot the resulting tour" )
|
|
fig = plot.figure()
|
|
ax = fig.add_subplot(111)
|
|
|
|
LOGN( "\tpheromones",len(phero),"nodes" )#,"x",len(phero[traj[0]]) )
|
|
maxph=0
|
|
for i in phero:
|
|
maxph = max( maxph, max(phero[i].values()))
|
|
|
|
# ant colony
|
|
# pheromones
|
|
for i in phero:
|
|
for j in phero[i]:
|
|
if i == j:
|
|
continue
|
|
nph = phero[i][j]/maxph
|
|
seg = [(i,j)]
|
|
# LOGN( nph,seg )
|
|
uberplot.plot_segments( ax, seg, edgecolor="blue", alpha=0.01*nph, linewidth=1*nph )
|
|
# uberplot.scatter_segments( ax, seg, color="red", alpha=0.5, linewidth=nph )
|
|
|
|
for traj in trajs:
|
|
LOGN( "\ttraj",len(traj),"points" )
|
|
# best tour
|
|
uberplot.plot_segments( ax, utils.tour(traj), edgecolor="red", alpha=0.9, linewidth=3 )
|
|
|
|
LOGN( "\ttiling",len(penrose_segments),"segments" )
|
|
tcol = "black"
|
|
uberplot.plot_segments( ax, penrose_segments, edgecolor=tcol, alpha=0.9, linewidth=2 )
|
|
# uberplot.scatter_segments( ax, penrose_segments, edgecolor=tcol, alpha=0.9, linewidth=1 )
|
|
|
|
# triangulation
|
|
LOGN( "\ttriangulation",len(triangulation_edges),"edges" )
|
|
uberplot.plot_segments( ax, triangulation_edges, edgecolor="green", alpha=0.2, linewidth=1 )
|
|
|
|
# Voronoï
|
|
LOGN( "\tVoronoï",len(voronoi_edges),"edges")
|
|
# uberplot.plot_segments( ax, voronoi_tri_edges, edgecolor="red", alpha=1, linewidth=1 )
|
|
# uberplot.scatter_points( ax, voronoi_tri_centers, edgecolor="red", facecolor="white", s=200, alpha=1, zorder=10 )
|
|
uberplot.plot_segments( ax, voronoi_edges, edgecolor="magenta", alpha=1, linewidth=1 )
|
|
uberplot.scatter_points( ax, voronoi_centers, edgecolor="magenta", facecolor="white", s=200, alpha=1, zorder=11 )
|
|
|
|
ax.set_aspect('equal')
|
|
|
|
|
|
# transparent background in SVG
|
|
fig.patch.set_visible(False)
|
|
ax.axis('off')
|
|
plot.savefig("ubergeekism.svg", dpi=600)
|
|
|
|
ax.axis('off')
|
|
fig.patch.set_visible(True)
|
|
fig.patch.set_facecolor('white')
|
|
plot.savefig("ubergeekism.png", dpi=600)
|
|
plot.show()
|
|
|