#!/usr/bin/python
# ===-- TreeGraph.py ------------------------------------------------------===##
#
# The KLEE Symbolic Virtual Machine
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
# ===----------------------------------------------------------------------===##
from __future__ import division
import sys
from types import GeneratorType
import DumpTreeStream
from Graphics.Canvas import PdfCanvas
from Graphics.Geometry import vec2
import os, time
import math, os, random
def generator_fold(it):
"""generator_fold(it) -> iterator
Given _it_, return a new iterator over the elements of _it_,
(recursively) folding in any elements whenever an iterator
result happens to also be a generator. This simplifies the
writing of iterators over recursive data structures.
"""
for res in it:
if isinstance(res,GeneratorType):
for res in generator_fold(res):
yield res
else:
yield res
def drawTree(a, b, maxDepth, sizes, depth=0):
yield (a, b)
if depth<maxDepth:
height = sizes[depth]
yield drawTree(b, vec2.add(b, (-height,height)),
maxDepth, sizes, depth+1)
yield drawTree(b, vec2.add(b, (+height,height)),
maxDepth, sizes, depth+1)
def makeTreeGraph(output, symPath, count, shuffle=False):
random.seed(10)
c = PdfCanvas(output, basePos=(0,0), baseScale=(72*5,72*5),
pageSize=(72*10,72*10))
c.startDrawing()
c.setColor(1,1,1)
c.drawFilledCircle((0,0),.9)
c.setColor(0,0,0)
c.setLineWidth(5)
c.drawOutlineCircle((0,0),.9)
c.setLineWidth(1)
# c.setColor(.2,.6,.3)
N = 8
sizes = [.5**(i+1) for i in range(N)]
res = drawTree((0.,-.9), (0.,-.5), N, [1.3*s/sum(sizes) for s in sizes])
# [c.drawLine(a,b) for a,b in generator_fold(res)]
def mapIt(center, radius, spanAngle, (x,y)):
a = vec2.sub(center,(0,radius))
b = vec2.add(center,vec2.fromangle(math.pi*.5 + 2*x*spanAngle, radius))
return vec2.lerp(a, b, y)
m = lambda pt: mapIt((0,0),.9,math.pi*.7, pt)
toBox = lambda pt: vec2.div(vec2.add(pt,(0.,.9)),
(2*1.3,1.7))
def lm((x,y)):
x,y = toBox((x,y))
N = 100
return m((x,1-math.log(1+(1-y**4),2)))
return m((x,(N**(1+y)-N)/(N**2-N)))
return m((x,y))
# c.drawOutlineBox((-.5,0),(.5,1))
#[c.drawLine(lm(a),lm(b)) for a,b in generator_fold(res)]
spanAngle = math.pi*.9
zeroRad = vec2.length(vec2.sub(vec2.fromangle(math.pi*3/2,.9),
vec2.fromangle(math.pi*.5 + spanAngle,.9)))
oneRad = .9
if 0:
for i in range(N):
t = i/(N-1)
c.drawOutlineCircle(vec2.add((0.,-.9), (0.,t*.9)),
zeroRad + (oneRad-zeroRad)*t)
def getTreePos(depth, maxDepth, index, ranges=None, depthOrder=None):
isoT = depth/(maxDepth-1)
isoCent = vec2.add((0.,-.9),(0.,isoT*.9))
isoRadius = zeroRad + (oneRad-zeroRad)*isoT
total = 2**(depth+1)
# isoSpanAngle = math.pi*.5 - vec2.toangle(vec2.sub(vec2.fromangle(math.pi*.5 + spanAngle,.9),
# isoCent))
isoSpanAngle = math.pi*.1 + (math.pi*.7 - math.pi*.1)*isoT
if ranges:
min,max = ranges
if max[depth]==min[depth]:
x = 0.
else:
x = (index - min[depth])/(max[depth]-min[depth])
elif depthOrder:
idx = depthOrder[depth].index(index)
x = idx/(len(depthOrder[depth])-1)
else:
x = index/(total-1)
return vec2.add(isoCent,vec2.fromangle(math.pi*.5 + (2*x-1)*isoSpanAngle, isoRadius))
treeData = DumpTreeStream.getTreeStream(symPath)
treeDataItems = treeData.items()
treeDataItems.sort()
N = max([len(p) for _,p in treeDataItems])
# N = min(20,N)
minDepthIndex = [2**(i+1) for i in range(N)]
maxDepthIndex = [0 for i in range(N)]
depthIndices = [set() for i in range(N+2)]
for id,path in treeDataItems:
depth = -1
index = 0
for p in path[:N]:
depth += 1
index = index*2 + (p=='1')
depthIndices[depth].add(index)
if 1:
low = index*2
hi = index*2+1
mid = index*2 + .5
M = 5
for x in range(depth+1,min(N,depth+M)):
t = (x-(depth+1))/(M-1)
depthIndices[x].add(low + (mid-low)*t)
depthIndices[x].add(hi + (mid-hi)*t)
low *= 2
hi *= 2 + 1
mid = mid*2 + .5
else:
depthIndices[depth+1].add(index*2)
depthIndices[depth+1].add(index*2+1)
depthIndices[depth+2].add(index*2*2)
depthIndices[depth+2].add(index*4+3)
minDepthIndex[depth] = min(index,minDepthIndex[depth])
maxDepthIndex[depth] = max(index,maxDepthIndex[depth])
if 0:
for d in range(1,N)[::-1]:
minDepthIndex[d] = min(minDepthIndex[d-1]*2,minDepthIndex[d])
maxDepthIndex[d] = max(maxDepthIndex[d-1]*2,maxDepthIndex[d])
for d in range(N):
depthIndices[d] = list(depthIndices[d])
depthIndices[d].sort()
def drawPoints(list):
for pt in list:
c.drawPoint(pt)
def catmullRom1((p0,p1,p2,p3), t):
return (0.5 * (( 2*p1 ) +
( -p0 + p2 ) * t +
( 2*p0 - 5*p1 + 4*p2 - p3) * t*t +
( -p0 + 3*p1 - 3*p2 + p3) * t*t*t))
def catmullRom2((p0,p1,p2,p3), t):
return (catmullRom1((p0[0],p1[0],p2[0],p3[0]),t),
catmullRom1((p0[1],p1[1],p2[1],p3[1]),t))
c.setLineWidth(2)
# c.setColor(.2,.6,.3)
lines = set()
segments = set()
segments2 = dict()
allPoints = set()
side = set()
paths = []
paths_to_draw = treeDataItems
if shuffle:
paths_to_draw = list(treeDataItems)
random.shuffle(paths_to_draw)
for id,path in paths_to_draw[:count]:
depth = -1
index = 0
pts = [(0.,-.9)]
for p in path[:N]:
depth += 1
index = index*2 + (p=='1')
pts.append(getTreePos(depth,N,index,None,depthIndices))
# pts.append(getTreePos(depth,N,index,(minDepthIndex,maxDepthIndex)))
allPoints |= set(pts)
if len(pts)>3:
paths.append(pts)
for i in range(0,len(pts)-1):
p0 = pts[max(0,i-1)]
p1 = pts[i]
p2 = pts[min(len(pts)-1,i+1)]
p3 = pts[min(len(pts)-1,i+2)]
#segments[(p1,p2)] = segments.get((p1,p2),[]) + [(p0,p1)]
if (p1,p2) not in side:
segments.add((p0,p1,p2,p3))
side.add((p1,p2))
# drawPoints((b,vec2.add(b,vec2.mulN(vec2.normalizeOrZero(vec2.sub(b,a)),l*.4)),
# cx,cx))
# c.drawLineStrip(pts)
# for a,b in zip(pts,pts[1:]):
# lines.add((a,b))
for a,b in lines:
c.drawLine(a,b)
for (p1,p2),extra in segments2.items():
a,b = zip(*extra)
a = vec2.divN(reduce(vec2.add, a),len(a))
b = vec2.divN(reduce(vec2.add, b),len(b))
c.drawLineStrip([catmullRom2((a,p1,p2,b),x/10.) for x in range(10+1)])
if 1:
side = set()
nPaths = []
for path in paths:
path = list(path)
path = [(0.,-.9)] + path
depth = 0
while len(path)>3 and (path[1],path[2]) in side:
depth += 1
path = path[1:]
nPaths.append((depth,path))
side |= set(zip(path,path[1:]))
# for path in nPaths:
# c.drawLineStrip(path)
for depth,path in nPaths:
ppts = []
for i in range(1,len(path)-1):
p0 = path[max(0,i-1)]
p1 = path[i]
p2 = path[min(len(path)-1,i+1)]
p3 = path[min(len(path)-1,i+2)]
for x in range(10):
ppts.append(catmullRom2((p0,p1,p2,p3),x/10.))
#ppts.append(p1)
ppts.append(path[-1])
if 1:
up = []
down = []
ref = path[0]
for i,pt in enumerate(ppts):
ref = ppts[min(len(ppts)-1,i+1)]
vec = vec2.sub(pt,ref)
l = vec2.length(vec)
if l<.000001:
vec = (1.,0.)
else:
vec = vec2.rotate90(vec2.divN(vec,l))
width = .001 * (1. - (depth + i/20.)/N)
up.append(vec2.add(pt, vec2.mulN(vec, width)))
down.append(vec2.add(pt, vec2.mulN(vec, -width)))
ref = pt
# c.drawLineStrip(up)
# c.drawLineStrip(down)
c.drawFilledPolygon(down + up[::-1])
else:
c.drawLineStrip(ppts)
# print len(paths),sum(map(len,paths))
# print len(nPaths),sum(map(len,nPaths))
else:
for P in segments:
c.drawLineStrip([catmullRom2(P,x/10.) for x in range(10+1)])
if 0:
c.setPointSize(10)
c.drawPoints(allPoints)
if 0:
N = 20
for i in range(N+1):
t = 2*i/N - 1
c.drawLine(m((t,0)), m((t,1)))
c.endDrawing()
def main():
from optparse import OptionParser
op = OptionParser("usage: %prog [options] <tree-stream-path> <output-path>")
op.add_option('','--count', dest='count', type=int, default=-1,
help="number of distinct paths to draw")
op.add_option('','--shuffle', dest='shuffle', action='store_true',
default=False)
opts,args = op.parse_args()
if len(args) != 2:
parser.error('invalid number of arguments')
symPath,output = args
makeTreeGraph(output, symPath, opts.count, opts.shuffle)
if __name__=='__main__':
try:
main()
except:
import sys,traceback
traceback.print_exc(file= sys.__stdout__)
sys.__stdout__.flush()