]> hydra-www.ietfng.org Git - dyna2/commitdiff
Error handling, hypergraph recovery.
authortimv <tim.f.vieira@gmail.com>
Tue, 4 Jun 2013 23:38:21 +0000 (19:38 -0400)
committertimv <tim.f.vieira@gmail.com>
Tue, 4 Jun 2013 23:38:21 +0000 (19:38 -0400)
  - code instrumentation `emit` now passed passengers.
  - see draw() method and --draw flag

src/Dyna/Backend/Python/Backend.hs
src/Dyna/Backend/Python/debug.py
src/Dyna/Backend/Python/interpreter.py

index 8fe82e29cfe22e2c0a630224243f11f6e9c6ca34..a6714c9b12375c7728dfa293e8db5f29657033fc 100644 (file)
@@ -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
index 983123fd7e3ffeaf05c80517bb4e73fe199ae5ea..6ebb1fb63be88edc392cdd2c08c3cc4074173e41 100644 (file)
@@ -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, '</div>'
 
     if browser:
-        #os.system('gnome-open %s 2>/dev/null >/dev/null' % html.name)
         import webbrowser
         webbrowser.open(html.name)
 
index 16fce95be02b7da1913c07911497e9d1783345f7..87ccf80e9f3144d7212085bdd2ab7127a1caaf0e 100644 (file)
@@ -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, """
+        <html>
+        <head>
+        <style>
+        body {
+          background-color: black;
+          color: white;
+        }
+        </style>
+        </head>
+        <body>
+        """
+
+        x = StringIO()
+        dump_charts(x)
+
+        print >> f, '<div style="position:absolute;">%s</div>' \
+            % '<h1>Charts</h1>%s' \
+            % '<pre style="width: 500px;">%s</pre>' \
+            % x.getvalue()
+
+        print >> f, """
+<div style="width: 800px; position:absolute; left: 550px">
+<h1>Hypergraph</h1>
+%s
+</div>
+""" % g.render('/tmp/hypergraph')
+
+        print >> f, '</body></html>'
+
+    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()