From f949b6963ba53c29780c27b68922b2c81196b9e5 Mon Sep 17 00:00:00 2001 From: timv Date: Tue, 4 Jun 2013 19:38:21 -0400 Subject: [PATCH] Error handling, hypergraph recovery. - code instrumentation `emit` now passed passengers. - see draw() method and --draw flag --- src/Dyna/Backend/Python/Backend.hs | 59 +++++----- src/Dyna/Backend/Python/debug.py | 14 +-- src/Dyna/Backend/Python/interpreter.py | 149 +++++++++++++++++++++---- 3 files changed, 165 insertions(+), 57 deletions(-) diff --git a/src/Dyna/Backend/Python/Backend.hs b/src/Dyna/Backend/Python/Backend.hs index 8fe82e2..a6714c9 100644 --- a/src/Dyna/Backend/Python/Backend.hs +++ b/src/Dyna/Backend/Python/Backend.hs @@ -16,6 +16,7 @@ module Dyna.Backend.Python.Backend (pythonBackend) where -- import Control.Exception import Control.Lens ((^.)) import Control.Monad +import Control.Monad.State -- import qualified Data.ByteString as B -- import qualified Data.ByteString.UTF8 as BU -- import Data.Char @@ -36,6 +37,7 @@ import Dyna.Main.Exception import Dyna.Term.TTerm -- import qualified Dyna.ParserHS.Parser as P import Dyna.XXX.PPrint +import Dyna.XXX.MonadUtils import Dyna.XXX.Trifecta (prettySpanLoc) import System.IO import Text.PrettyPrint.Free @@ -179,14 +181,14 @@ piterate vs = parens $ filterGround = map (^.mv_var) . filter (not.nGround.(^.mv_mi)) -- | Render a single dopamine opcode or its surrogate -pdope_ :: DOpAMine PyDopeBS -> Doc e +pdope_ :: DOpAMine PyDopeBS -> State Int (Doc e) pdope_ (OPIndr _ _) = dynacSorry "indirect evaluation not implemented" -pdope_ (OPAsgn v val) = pretty v <+> equals <+> pretty val -pdope_ (OPCheq v val) = "if" <+> pretty v <+> "!=" - <+> pretty val <> ": continue" -pdope_ (OPCkne v val) = "if" <+> pretty v <+> "==" - <+> pretty val <> ": continue" -pdope_ (OPPeel vs i f) = +pdope_ (OPAsgn v val) = return $ pretty v <+> equals <+> pretty val +pdope_ (OPCheq v val) = return $ "if" <+> pretty v <+> "!=" + <+> pretty val <> ": continue" +pdope_ (OPCkne v val) = return $ "if" <+> pretty v <+> "==" + <+> pretty val <> ": continue" +pdope_ (OPPeel vs i f) = return $ "try:" `above` (indent 4 $ tupledOrUnderscore vs <+> equals @@ -194,45 +196,50 @@ pdope_ (OPPeel vs i f) = ) -- you'll get a "TypeError: 'NoneType' is not iterable." `above` "except (TypeError, AssertionError): continue" -pdope_ (OPWrap v vs f) = pretty v +pdope_ (OPWrap v vs f) = return $ pretty v <+> equals <+> "build" <> (parens $ pfas f vs <> comma <> (sepBy "," $ map pretty vs)) -pdope_ (OPIter v vs f _ (Just (PDBS c))) = pretty (v^.mv_var) +pdope_ (OPIter v vs f Det (Just (PDBS c))) = return $ pretty (v^.mv_var) <+> equals <+> c v vs -pdope_ (OPIter o m f _ Nothing) = - let mo = m ++ [o] in - "for" <+> piterate mo --(tupledOrUnderscore $ filterGround mo) +pdope_ (OPIter o m f _ Nothing) = do + i <- incState + return $ let mo = m ++ [o] in + "for" <+> "d" <> pretty i <> "," <> piterate mo <+> "in" <+> functorIndirect "chart" f m <> pslice mo <> colon -- XXX Ought to make i and vs conditional on... doing debugging or the -- aggregator for this head caring. The latter is a good bit more -- advanced than we are right now. -pdope_ (OPEmit h r i vs) = - "emit" <> tupled [ pretty h - , pretty r - , pretty i - , varmap - ] - where +pdope_ (OPEmit h r i vs) = do + ds <- get + -- A python map of variable name to value - varmap = encloseSep lbrace rbrace comma - $ map (\v -> let v' = pretty v in dquotes v' <> colon <+> v') vs + let varmap = encloseSep lbrace rbrace comma $ + ("'nodes'" <> colon <> (encloseSep lbracket rbracket comma $ map (("d"<>).pretty) [0..ds-1])) + : (map (\v -> let v' = pretty v in dquotes v' <> colon <+> v') vs) + + return $ "emit" <> tupled [ pretty h + , pretty r + , pretty i + , varmap + ] -- | Render a dopamine sequence's checks and loops above a (indended) core. pdope :: Actions PyDopeBS -> Doc e pdope _d = (indent 4 $ "for _ in [None]:") - `above` (indent 8 $ go _d) + `above` (indent 8 $ evalState (go _d) 0) where - go [] = empty + go [] = return empty go (x:xs) = let indents = case x of OPIter _ _ _ d _ -> d /= Det ; _ -> False - in above (pdope_ x) - . (if indents then indent 4 else id) - $ go xs + in do + x' <- pdope_ x + xs' <- go xs + return $ x' `above` ((if indents then indent 4 else id) xs') printPlanHeader :: Rule -> Cost -> Maybe Int -> Doc e diff --git a/src/Dyna/Backend/Python/debug.py b/src/Dyna/Backend/Python/debug.py index 983123f..6ebb1fb 100644 --- a/src/Dyna/Backend/Python/debug.py +++ b/src/Dyna/Backend/Python/debug.py @@ -36,7 +36,6 @@ class Hypergraph(object): head = re.sub('"', r'\\"', head) body = map(lambda b: re.sub('"', r'\\"', b), body) - e = Edge(head, label, tuple(body)) self.edges.append(e) @@ -61,6 +60,8 @@ class Hypergraph(object): def render(self, name, sty=None): sty = sty or defaultdict(dict) + # TODO: misses orphaned nodes. + dot = '%s.dot' % name svg = '%s.svg' % name @@ -68,11 +69,8 @@ class Hypergraph(object): with file(dot, 'wb') as f: print >> f, 'digraph rule {' print >> f, 'rankdir=LR;' # left-to-right layout - print >> f, 'node [style=filled,fillcolor=white];' - print >> f, 'bgcolor="transparent";' - print >> f, 'edge [color=white];' for e in self.edges: @@ -91,7 +89,8 @@ class Hypergraph(object): # node styles for x in self.nodes: - sty[x].update({'shape': 'circle'}) +# sty[x].update({'shape': 'circle'}) + sty[e].update({'shape': 'rectangle'}) print >> f, '"%s" [%s]' % (x, ','.join('%s="%s"' % (k,v) for k,v in sty[x].items())) # edge styles @@ -145,8 +144,8 @@ class Hypergraph(object): return t(root) -def show_slice(e, M): - return [(b if bind else ':') for b, bind in zip(e.body, M)] +#def show_slice(e, M): +# return [(b if bind else ':') for b, bind in zip(e.body, M)] def isvar(x): @@ -399,7 +398,6 @@ Initializer: print >> html, '' if browser: - #os.system('gnome-open %s 2>/dev/null >/dev/null' % html.name) import webbrowser webbrowser.open(html.name) diff --git a/src/Dyna/Backend/Python/interpreter.py b/src/Dyna/Backend/Python/interpreter.py index 16fce95..87ccf80 100644 --- a/src/Dyna/Backend/Python/interpreter.py +++ b/src/Dyna/Backend/Python/interpreter.py @@ -37,8 +37,6 @@ MISC thinking because we don't yet know what things in the chart have been touched. - - TODO: transactions for errors. - - TODO: Numeric precision is an issue with BAggregators. a[0.1] += 1 @@ -147,6 +145,9 @@ from collections import defaultdict, namedtuple from functools import partial from argparse import ArgumentParser +from StringIO import StringIO +import webbrowser + from utils import ip, red, green, blue, magenta, yellow, dynahome, \ notimplemented, prioritydict from defn import aggregator @@ -180,11 +181,12 @@ class aggregator_declaration(object): return None -error_suppression = False +error_suppression = True trace = None agenda = prioritydict() agg_decl = aggregator_declaration() chart = chart_indirect() +errors = {} def dump_charts(out=sys.stdout): @@ -199,14 +201,36 @@ def dump_charts(out=sys.stdout): print >> out, chart[x] print >> out + if errors: + print >> out + print >> out, 'Errors' + print >> out, '============' + for item, (val, es) in errors.items(): + print >> out, 'because %r is %r:' % (item, val) + for e in es: + print >> out, ' ', e + print >> out + # TODO: codegen should output a derived Term instance for each functor -class Term(namedtuple('Term', 'fn args'), object): +class Term(object): + + __slots__ = 'fn args value aggregator'.split() def __init__(self, fn, args): - self._value = None + self.fn = fn + self.args = args + self.value = None self.aggregator = None - super(Term, self).__init__(fn, args) + + def __cmp__(self, other): + if other is None: + return 1 + return cmp((self.fn, self.args), (other.fn, other.args)) + + # default hash and eq suffice because we intern + #def __hash__(self): + #def __eq__(self): def __repr__(self): "Pretty print a term. Will retrieve the complete (ground) term." @@ -215,19 +239,18 @@ class Term(namedtuple('Term', 'fn args'), object): return fn return '%s(%s)' % (fn, ','.join(map(repr, self.args))) - __add__ \ - = __sub__ \ - = __mul__ \ - = notimplemented + __add__ = __sub__ = __mul__ = notimplemented -# @property -# def value(self): -# return self._value -# @value.setter -# def value(self, val): -# assert not isinstance(val, tuple) or isinstance(val, Term) -# self._value = val +_edges = defaultdict(set) +def edges(): + def _emit(item, val, ruleix, variables): + b = variables['nodes'] + b.sort() + b = tuple(b) + _edges[item].add((ruleix, b)) + for init in initializer.handlers: + init(emit=_emit) class Chart(object): @@ -273,11 +296,11 @@ class Chart(object): if isinstance(val, slice): for term in candidates: if term.value is not None: - yield term.args + (term.value,) + yield term, term.args + (term.value,) else: for term in candidates: if term.value == val: - yield term.args + (term.value,) + yield term, term.args + (term.value,) def lookup(self, args): "find index for these args" @@ -306,7 +329,8 @@ class Chart(object): def build(fn, *args): - if fn == "true/0": # TODO: I'd rather have the codegen ensure true/0 is True and false/0 is False + # TODO: codegen should handle true/0 is True and false/0 is False + if fn == "true/0": return True if fn == "false/0": return False @@ -368,13 +392,15 @@ def update_dispatcher(item, val, delete): """ Passes update to relevant handlers. """ + if val is None: return + for handler in register.handlers[item.fn]: emittiers = [] _emit = lambda item, val, ruleix, variables: \ - emittiers.append(lambda: emit(item, val, ruleix, variables, delete=delete)) + emittiers.append((item, val, ruleix, variables, delete)) try: handler(item, val, emit=_emit) @@ -382,12 +408,21 @@ def update_dispatcher(item, val, delete): if error_suppression: #print >> trace, print '%s on update %s = %s' % (e, item, val) + + if item not in errors: + errors[item] = (val, []) + errors[item][1].append(e) + + # TODO: store which rule. + else: raise e else: # no exception, accept emissions. for e in emittiers: - e() + # an error could happen here, but we assume (by contract) that + # this is not possible. + emit(*e) def emit(item, val, ruleix, variables, delete): @@ -437,7 +472,16 @@ def _go(): was = item.value print >> trace, '(was: %s,' % (was,), - now = item.aggregator.fold() + if item in errors: # clear the error + del errors[item] + + try: + now = item.aggregator.fold() + except (ZeroDivisionError, TypeError) as e: + errors[item] = ('failed to aggregate %r' % item.aggregator, [e]) + # TODO: Are we sure there is never a reason to requeue this item. + continue + print >> trace, 'now: %s)' % (now,) if was == now: @@ -650,6 +694,9 @@ class REPL(cmd.Cmd, object): else: self.do_changed('') + def do_draw(self, _): + draw() + def cmdloop(self, _=None): try: super(REPL, self).cmdloop() @@ -664,6 +711,56 @@ class REPL(cmd.Cmd, object): def repl(hist): REPL(hist).cmdloop() + +def hypergraph(): + from debug import Hypergraph + # collect edges + edges() + # create hypergraph object + g = Hypergraph() + for c in chart.values(): + for x in c.intern.values(): + for e in _edges[x]: + label, body = e + g.edge(str(x), str(label), map(str, body)) + return g + + +def draw(): + g = hypergraph() + with file('/tmp/state.html', 'wb') as f: + print >> f, """ + + + + + + """ + + x = StringIO() + dump_charts(x) + + print >> f, '
%s
' \ + % '

Charts

%s' \ + % '
%s
' \ + % x.getvalue() + + print >> f, """ +
+

Hypergraph

+%s +
+""" % g.render('/tmp/hypergraph') + + print >> f, '' + + webbrowser.open(f.name) + def main(): # from repl import repl @@ -674,6 +771,8 @@ def main(): parser.add_argument('--trace', default='/tmp/dyna.log') parser.add_argument('-i', dest='interactive', action='store_true', help='Fire-up an IPython shell.') parser.add_argument('-o', dest='output', help='Output chart.') + parser.add_argument('--draw', action='store_true', + help='Output html page with hypergraph and chart.') argv = parser.parse_args() @@ -714,6 +813,10 @@ def main(): else: repl(hist = '/tmp/dyna.hist') + if argv.draw: + draw() + + if __name__ == '__main__': main() -- 2.50.1