Repository: Theano/Theano

Issue:
In debugprint, have an option to print the inner graph of scan nodes
The same way pydotprint does.



Discussion:
@caglar the two files we should get to understand are [printing.py](https://github.com/Theano/Theano/blob/master/theano/printing.py#L37-L114) and [debugmode.py](https://github.com/Theano/Theano/blob/master/theano/compile/debugmode.py#L495-L615).

The problem that needs to be fixed is that right now debugprint only prints the `scan` op, not its internal computation. The suggested fix would be, for every `scan` op in the graph, to have a mini-debugprint that shows what's happening inside `scan`. These secondary debugprints would be either printed after the principal debugprint on the terminal (much like profiling in Theano breaks down computation time inside a `scan` op after it has broken down computation times in the main graph).

`debugprint` also has the option to return a string or write to a file. In the 'file' case, it would make sense to do as with pydotprint, i.e. write to a file for the main debugprint, and then write to other files for the mini-debugprints of `scan` ops. In the 'string' case, we have the option to return a tuple of strings or simply to concatenate the main debugprint and the mini-debugprints associated with `scan` ops.

Why not just print the inner of debugprint in the same output as the
debugprint, at the end? This don't mess with the way we output stuff, so it
keep the difficulty to: after the debugprint, check the nodes that was
printed and for each scan, print the inner. Try to keep a link in the for
of the [@d] ref to the corresponding scan.

On Wed, Oct 1, 2014 at 3:53 PM, vdumoulin notifications@github.com wrote:

> @caglar https://github.com/caglar the two files we should get to
> understand are printing.py
> https://github.com/Theano/Theano/blob/master/theano/printing.py#L37-L114
> and debugmode.py
> https://github.com/Theano/Theano/blob/master/theano/compile/debugmode.py#L495-L615
> .
> 
> The problem that needs to be fixed is that right now debugprint only
> prints the scan op, not its internal computation. The suggested fix would
> be, for every scan op in the graph, to have a mini-debugprint that shows
> what's happening inside scan. These secondary debugprints would be either
> printed after the principal debugprint on the terminal (much like profiling
> in Theano breaks down computation time inside a scan op after it has
> broken down computation times in the main graph).
> 
> debugprint also has the option to return a string or write to a file. In
> the 'file' case, it would make sense to do as with pydotprint, i.e. write
> to a file for the main debugprint, and then write to other files for the
> mini-debugprints of scan ops. In the 'string' case, we have the option to
> return a tuple of strings or simply to concatenate the main debugprint and
> the mini-debugprints associated with scan ops.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/Theano/Theano/issues/2079#issuecomment-57527221.

That was what I was trying to convey, sorry if I wasn't clear.

## 

Vincent Dumoulin, B.Sc. Physique et Informatique
Étudiant au Ph.D.
LISA, Université de Montréal
Bureau: 3336, pavillon André-Aisenstadt
vincent.dumoulin@umontreal.ca

On Wed, Oct 1, 2014 at 4:31 PM, Frédéric Bastien notifications@github.com
wrote:

> Why not just print the inner of debugprint in the same output as the
> debugprint, at the end? This don't mess with the way we output stuff, so
> it
> keep the difficulty to: after the debugprint, check the nodes that was
> printed and for each scan, print the inner. Try to keep a link in the for
> of the [@d] ref to the corresponding scan.
> 
> On Wed, Oct 1, 2014 at 3:53 PM, vdumoulin notifications@github.com
> wrote:
> 
> > @caglar https://github.com/caglar the two files we should get to
> > understand are printing.py
> > <
> > https://github.com/Theano/Theano/blob/master/theano/printing.py#L37-L114>
> > and debugmode.py
> > <
> > https://github.com/Theano/Theano/blob/master/theano/compile/debugmode.py#L495-L615>
> > 
> > .
> > 
> > The problem that needs to be fixed is that right now debugprint only
> > prints the scan op, not its internal computation. The suggested fix
> > would
> > be, for every scan op in the graph, to have a mini-debugprint that shows
> > what's happening inside scan. These secondary debugprints would be
> > either
> > printed after the principal debugprint on the terminal (much like
> > profiling
> > in Theano breaks down computation time inside a scan op after it has
> > broken down computation times in the main graph).
> > 
> > debugprint also has the option to return a string or write to a file. In
> > the 'file' case, it would make sense to do as with pydotprint, i.e.
> > write
> > to a file for the main debugprint, and then write to other files for the
> > mini-debugprints of scan ops. In the 'string' case, we have the option
> > to
> > return a tuple of strings or simply to concatenate the main debugprint
> > and
> > the mini-debugprints associated with scan ops.
> > 
> > —
> > Reply to this email directly or view it on GitHub
> > https://github.com/Theano/Theano/issues/2079#issuecomment-57527221.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/Theano/Theano/issues/2079#issuecomment-57533514.


Resolving patch:
diff --git a/theano/compile/debugmode.py b/theano/compile/debugmode.py
--- a/theano/compile/debugmode.py
+++ b/theano/compile/debugmode.py
@@ -494,7 +494,8 @@ def char_from_number(number):
 def debugprint(r, prefix='', depth=-1, done=None, print_type=False,
                file=sys.stdout, print_destroy_map=False,
                print_view_map=False, order=None, ids='CHAR',
-               stop_on_name=False, prefix_child=None):
+               stop_on_name=False, prefix_child=None,
+               scan_ops=None):
     """Print the graph leading to `r` to given depth.
 
     :param r: Variable instance
@@ -502,10 +503,10 @@ def debugprint(r, prefix='', depth=-1, done=None, print_type=False,
     :param depth: maximum recursion depth (Default -1 for unlimited).
     :param done: dict of Apply instances that have already been printed
                  and their associated printed ids
-    :param print_type: wether to print the Variable type after the other infos
+    :param print_type: whether to print the Variable type after the other infos
     :param file: file-like object to which to print
-    :param print_destroy_map: wether to print the op destroy_map after ofther info
-    :param print_view_map: wether to print the op view_map after ofther info
+    :param print_destroy_map: whether to print the op destroy_map after other info
+    :param print_view_map: whether to print the op view_map after other info
     :param order: If not empty will print the index in the toposort.
     :param ids: How do we print the identifier of the variable
                 id - print the python id value
@@ -514,6 +515,8 @@ def debugprint(r, prefix='', depth=-1, done=None, print_type=False,
                 "" - don't print an identifier
     :param stop_on_name: When True, if a node in the graph has a name,
                          we don't print anything below it.
+    :param scan_ops: Scan ops in the graph will be added inside this list
+                     for later printing purposes.
 
     """
     if depth == 0:
@@ -525,6 +528,9 @@ def debugprint(r, prefix='', depth=-1, done=None, print_type=False,
     if done is None:
         done = dict()
 
+    if scan_ops is None:
+        scan_ops = []
+
     if print_type:
         type_str = ' <%s>' % r.type
     else:
@@ -575,37 +581,45 @@ def get_id_str(obj):
         o = ''
         if order:
             o = str(order.index(r.owner))
+
         already_printed = a in done  # get_id_str put it in the dict
         id_str = get_id_str(a)
 
         if len(a.outputs) == 1:
             print >> file, '%s%s %s%s \'%s\' %s %s %s' % (prefix, a.op,
-                                                             id_str,
-                                                             type_str, r_name,
+                                                          id_str,
+                                                          type_str,
+                                                          r_name,
+                                                          destroy_map_str,
+                                                          view_map_str,
+                                                          o)
+        else:
+            print >> file, '%s%s.%i %s%s \'%s\' %s %s %s' % (prefix, a.op,
+                                                             a.outputs.index(r),
+                                                             id_str, type_str,
+                                                             r_name,
                                                              destroy_map_str,
                                                              view_map_str,
                                                              o)
-        else:
-            print >> file, '%s%s.%i %s%s \'%s\' %s %s %s' % (prefix, a.op,
-                                                            a.outputs.index(r),
-                                                            id_str, type_str,
-                                                            r_name,
-                                                            destroy_map_str,
-                                                            view_map_str,
-                                                            o)
         if not already_printed:
             if (not stop_on_name or
                 not (hasattr(r, 'name') and r.name is not None)):
                 new_prefix = prefix_child + ' |'
                 new_prefix_child = prefix_child + ' |'
+
                 for idx, i in enumerate(a.inputs):
                     if idx == len(a.inputs) - 1:
                         new_prefix_child = prefix_child + '  '
 
+                    if hasattr(i, 'owner') and hasattr(i.owner, 'op'):
+                        if isinstance(i.owner.op, theano.scan_module.scan_op.Scan):
+                            scan_ops.append(i)
+
                     debugprint(i, new_prefix, depth=depth - 1, done=done,
                                print_type=print_type, file=file, order=order,
                                ids=ids, stop_on_name=stop_on_name,
-                               prefix_child=new_prefix_child)
+                               prefix_child=new_prefix_child, scan_ops=scan_ops)
+
     else:
         #this is an input variable
         id_str = get_id_str(r)
@@ -624,7 +638,6 @@ def _optcheck_fgraph(input_specs, output_specs, accept_inplace=False):
     :type accept_inplace: Bool
     :rtype: `FunctionGraph`
     :returns: a new FunctionGraph with a cloned graph, with debugging `Feature` instances already installed.
-
     """
     orig_inputs = [spec.variable for spec in input_specs]
     updates = [spec.update for spec in input_specs if spec.update]
@@ -2152,7 +2165,7 @@ def __init__(self, inputs, outputs, optimizer, mode,
         # Check if some input variables are unused
         self._check_unused_inputs(inputs, outputs, on_unused_input)
 
-        # Make a list of (SymbolicInput|SymblicInputKits, indices, [SymbolicInput,...]), one 
+        # Make a list of (SymbolicInput|SymblicInputKits, indices, [SymbolicInput,...]), one
         # tuple for each input. (See Function.indices for more details)
         indices = [[input] + self.expand_in(input, _inputs) for input in inputs]
 
diff --git a/theano/printing.py b/theano/printing.py
--- a/theano/printing.py
+++ b/theano/printing.py
@@ -102,10 +102,38 @@ def debugprint(obj, depth=-1, print_type=False,
         else:
             raise TypeError("debugprint cannot print an object of this type",
                             obj)
+
+    scan_ops = []
     for r in results_to_print:
+        #Add the parent scan op to the list as well
+        if hasattr(r.owner, 'op') and isinstance(r.owner.op, theano.scan_module.scan_op.Scan):
+            scan_ops.append(r)
+
         debugmode.debugprint(r, depth=depth, done=done, print_type=print_type,
                              file=_file, order=order, ids=ids,
-                             stop_on_name=stop_on_name)
+                             scan_ops=scan_ops, stop_on_name=stop_on_name)
+    if len(scan_ops) > 0:
+        print >> file, ""
+        new_prefix = ' >'
+        new_prefix_child = ' >'
+        print >> file, "Inner graphs of the scan ops:"
+
+        for s in scan_ops:
+            print >> file, ""
+            debugmode.debugprint(s, depth=depth, done=done, print_type=print_type,
+                                 file=_file, ids=ids,
+                                 scan_ops=scan_ops, stop_on_name=stop_on_name)
+
+            for idx, i in enumerate(s.owner.op.outputs):
+                if hasattr(i, 'owner') and hasattr(i.owner, 'op'):
+                    if isinstance(i.owner.op, theano.scan_module.scan_op.Scan):
+                        scan_ops.append(i)
+
+                debugmode.debugprint(r=i, prefix=new_prefix, depth=depth, done=done,
+                                     print_type=print_type, file=file,
+                                     ids=ids, stop_on_name=stop_on_name,
+                                     prefix_child=new_prefix_child, scan_ops=scan_ops)
+
     if file is _file:
         return file
     elif file == 'str':


Test patch: