From: timv Date: Fri, 7 Jun 2013 22:17:44 +0000 (-0400) Subject: added force-directed layout (with a small hack for random numbers). X-Git-Url: https://hydra-www.ietfng.org/gitweb/?a=commitdiff_plain;h=cf1a51fcfee32ffac5065294c5bbf9542cb1a762;p=dyna2 added force-directed layout (with a small hack for random numbers). use FIFO priorities added argument for runing post-processing scripts. --- diff --git a/examples/force.dyna b/examples/force.dyna new file mode 100644 index 0000000..5f83159 --- /dev/null +++ b/examples/force.dyna @@ -0,0 +1,63 @@ +% Try to use the force to lay out a few nodes + +% Kamada-Kawai force directed layout +% http://graphael.cs.arizona.edu/papers/graphael_final.pdf + +% distance between U and V at time T. +dist(U,V,T) := (x(U,T) - x(V,T))**2 + (y(U,T) - y(V,T))**2 + for true is (T < niter). % restrict to niter iterations. + +% all pairs shortest path. +shortestpath(U,U) min= 0 for node(U). +shortestpath(U,V) min= shortestpath(U,W) + edge(W,V). + +% "the unit edge length" +edgelen := 5.0. + +f(U,V,T) := true is (U != V), dist(U,V,T) / (shortestpath(U,V) * edgelen) - 1. + +forceX(V,T) += f(U,V,T) * (x(U,T) - x(V,T)). +forceY(V,T) += f(U,V,T) * (y(U,T) - y(V,T)). + +a := 0.15. +niter := 100. + +% should `a` be negative? +x(U,T) += a * forceX(U,T-1). +y(U,T) += a * forceY(U,T-1). + +edge("a", "b") := 1. +edge("a", "c") := 1. +edge("a", "d") := 1. +edge("a", "e") := 1. +edge("b", "h") := 1. +edge("b", "i") := 1. +edge("b", "j") := 1. +edge("e", "f") := 1. +edge("e", "g") := 1. +edge("a", "i") := 1. + +edge(A,B) := 1 for edge(B,A). % make graph symmetric. + +% collect nodes. +node(U) := true for edge(U,_). +node(U) := true for edge(_,U). + +% randomly initialize node positions. +x("a",0) += uniform(0,1). y("a",0) += uniform(0,1). +x("b",0) += uniform(0,1). y("b",0) += uniform(0,1). +x("c",0) += uniform(0,1). y("c",0) += uniform(0,1). +x("d",0) += uniform(0,1). y("d",0) += uniform(0,1). +x("e",0) += uniform(0,1). y("e",0) += uniform(0,1). +x("f",0) += uniform(0,1). y("f",0) += uniform(0,1). +x("g",0) += uniform(0,1). y("g",0) += uniform(0,1). +x("h",0) += uniform(0,1). y("h",0) += uniform(0,1). +x("i",0) += uniform(0,1). y("i",0) += uniform(0,1). +x("j",0) += uniform(0,1). y("j",0) += uniform(0,1). + +pos(U,T) := tuple(x(U, T), y(U, T)). + +% import pylab aspl +% for U,V,_ in edge: +% pl.plot([x[U], x[V]], +% [y[U], y[V]]) diff --git a/src/Dyna/Backend/Python/Backend.hs b/src/Dyna/Backend/Python/Backend.hs index bc4b7c3..85103c8 100644 --- a/src/Dyna/Backend/Python/Backend.hs +++ b/src/Dyna/Backend/Python/Backend.hs @@ -134,11 +134,13 @@ constants = go go ("%",2) = Just $ PDBS $ infixOp "%" go ("+",2) = Just $ PDBS $ infixOp "+" - go ("mod",2) = Just $ PDBS $ call "mod" + go ("mod",2) = Just $ PDBS $ infixOp "%" go ("abs",1) = Just $ PDBS $ call "abs" go ("log",1) = Just $ PDBS $ call "log" go ("exp",1) = Just $ PDBS $ call "exp" + go ("uniform", _) = Just $ PDBS $ call "uniform" + go ("<=",2) = Just $ PDBS $ infixOp "<=" go ("<",2) = Just $ PDBS $ infixOp "<" go ("=",2) = Just $ PDBS $ infixOp "=" diff --git a/src/Dyna/Backend/Python/chart.py b/src/Dyna/Backend/Python/chart.py index 70b8868..632a9bb 100644 --- a/src/Dyna/Backend/Python/chart.py +++ b/src/Dyna/Backend/Python/chart.py @@ -29,6 +29,12 @@ class Term(object): return fn return '%s(%s)' % (fn, ','.join(map(_repr, self.args))) + def __getstate__(self): + return (self.fn, self.args, self.value, self.aggregator) + + def __setstate__(self, state): + (self.fn, self.args, self.value, self.aggregator) = state + __add__ = __sub__ = __mul__ = notimplemented @@ -62,7 +68,7 @@ class Chart(object): def __getitem__(self, s): assert len(s) == self.arity + 1, \ - 'item width mismatch: arity %s, item %s' % (self.arity, len(s)) + 'Chart %r: item width mismatch: arity %s, item %s' % (self.name, self.arity, len(s)) args, val = s[:-1], s[-1] diff --git a/src/Dyna/Backend/Python/interpreter.py b/src/Dyna/Backend/Python/interpreter.py index 7346956..fbdf4a0 100644 --- a/src/Dyna/Backend/Python/interpreter.py +++ b/src/Dyna/Backend/Python/interpreter.py @@ -4,11 +4,20 @@ MISC ==== + - TODO: dyna rules for chart display or visualization via viz_chart. Rules will + use string formatting or python eval magic. + + - TODO: catch compiler errors (for example, ^C while compiling results in a + "Compiler panic! This is almost assuredly not your fault!..."). + + - TODO: dynac should provide routines for building terms. We can hack something + together with anf output. + - TODO: faster charts (dynamic argument types? jason's trie data structure) - TODO: write all files to ~/.dyna - - TODO: `None` does not propagate, eventually it will becase of the `?` prefix + - TODO: `None` does not propagate, eventually it will because of the `?` prefix operator. - TODO: (@nwf) String quoting (see example/stringquote.py) @@ -52,8 +61,8 @@ MISC - approximate deletion ("buckets"), find the nearest neighbor and delete it. - - hybrid: maintain streaming sum and the bag check periodically for quality - and null. + - hybrid: maintain streaming sum parallel to the BAggregator and check + periodically for quality and null. - numeric approximations, stream folding (fails to get null) @@ -61,6 +70,16 @@ MISC named with numeric values of variables. +JUST FOR FUN +============ + + - overload everything so that values maintain provenance and we can inspect the + entire fine-grained circuit. + + - play around with python modules uncertainties (error propagation and + gradients), look into tools for dimensional analysis. + + What is null? ============= @@ -97,6 +116,7 @@ from utils import ip, red, green, blue, magenta, yellow, \ from prioritydict import prioritydict from config import dotdynadir, dynahome +from time import time class AggregatorConflict(Exception): def __init__(self, key, expected, got): @@ -231,7 +251,8 @@ class Interpreter(object): """ # and now, for something truely horrendous -- look up an item by it's - # string value! + # string value! This could fail because of whitespace or trivial + # formatting differences. items = {} for c in self.chart.values(): for i in c.intern.values(): @@ -239,48 +260,44 @@ class Interpreter(object): try: item = items[item] except KeyError: - print 'item not found.' + print 'item not found. This could be because of a trivial formatting differences...' return print item - while item.value: - print item.value - self.emit(item, item.value, None, sys.maxint, delete=True) - self.go() + self.emit(item, item.value, None, sys.maxint, delete=True) + self.go() def retract_rule(self, idx): "Retract rule and all of it's edges." assert isinstance(idx, str) - try: rule = self.rules.pop(idx) except KeyError: print 'Rule %s not found.' % idx return - # Step 1: remove update handlers - print 'removing rule', rule - print ' removing updaters' for u in rule.updaters: - print ' ', u.__doc__ - deleted = False for hs in self.updaters.values(): for i, h in enumerate(hs): if u is h: del hs[i] - assert not u in hs - deleted = True - assert deleted, 'should always find handler.' - deleted = False - + break + else: + assert False, "failed to find updater." # Step 2: run initializer in delete mode rule.init(emit=self.delete_emit) - # Step 3; go! self.go() def go(self): + try: + self._go() + except KeyboardInterrupt: # TODO: need to be safer in some parts of the code. + print '^C' + self.dump_charts() + + def _go(self): "the main loop" changed = {} @@ -390,7 +407,8 @@ class Interpreter(object): item.aggregator.dec(val, ruleix, variables) else: item.aggregator.inc(val, ruleix, variables) - self.agenda[item] = 0 # everything is high priority +# self.agenda[item] = 0 # everything is high priority + self.agenda[item] = time() def repl(self, hist): import repl @@ -413,9 +431,11 @@ class Interpreter(object): print >> self.trace, magenta % 'Loading new code' print >> self.trace, yellow % h.read() + from numpy.random import uniform + env = {'_initializers': [], '_updaters': [], '_agg_decl': {}, 'chart': self.chart, 'build': self.build, 'peel': peel, - 'parser_state': None} + 'parser_state': None, 'uniform': uniform} # load generated code. execfile(filename, env) @@ -501,6 +521,8 @@ def main(): parser.add_argument('-o', dest='output', help='Output chart.') parser.add_argument('--draw', action='store_true', help='Output html page with hypergraph and chart.') + parser.add_argument('--postprocess', type=file, + help='run post-processing script.') argv = parser.parse_args() @@ -546,6 +568,9 @@ def main(): if argv.draw: interp.draw() + if argv.postprocess is not None: + execfile(argv.postprocess.name, {'interp': interp}) + if __name__ == '__main__': main() diff --git a/src/Dyna/Backend/Python/repl.py b/src/Dyna/Backend/Python/repl.py index 34e60a1..e3e2bf8 100644 --- a/src/Dyna/Backend/Python/repl.py +++ b/src/Dyna/Backend/Python/repl.py @@ -6,6 +6,7 @@ from interpreter import AggregatorConflict from config import dotdynadir import debug + class REPL(cmd.Cmd, object): def __init__(self, interp, hist):