code: def pydotprint(
      fct,
      outfile=None,
      compact=True,
      format="png",
      with_ids=False,
      high_contrast=True,
      cond_highlight=None,
      colorCodes=None,
      max_label_size=70,
      scan_graphs=False,
      var_with_name_simple=False,
      print_output_file=True,
      return_image=False,
  ):
      """Print to a file the graph of a compiled theano function's ops. Supports
      all pydot output formats, including png and svg.
  
      :param fct: a compiled Theano function, a Variable, an Apply or
                  a list of Variable.
      :param outfile: the output file where to put the graph.
      :param compact: if True, will remove intermediate var that don't have name.
      :param format: the file format of the output.
      :param with_ids: Print the toposort index of the node in the node name.
                       and an index number in the variable ellipse.
      :param high_contrast: if true, the color that describes the respective
              node is filled with its corresponding color, instead of coloring
              the border
      :param colorCodes: dictionary with names of ops as keys and colors as
              values
      :param cond_highlight: Highlights a lazy if by surrounding each of the 3
                  possible categories of ops with a border. The categories
                  are: ops that are on the left branch, ops that are on the
                  right branch, ops that are on both branches
                  As an alternative you can provide the node that represents
                  the lazy if
      :param scan_graphs: if true it will plot the inner graph of each scan op
                  in files with the same name as the name given for the main
                  file to which the name of the scan op is concatenated and
                  the index in the toposort of the scan.
                  This index can be printed with the option with_ids.
      :param var_with_name_simple: If true and a variable have a name,
                  we will print only the variable name.
                  Otherwise, we concatenate the type to the var name.
      :param return_image: If True, it will create the image and return it.
          Useful to display the image in ipython notebook.
  
          .. code-block:: python
  
              import theano
              v = theano.tensor.vector()
              from IPython.display import SVG
              SVG(theano.printing.pydotprint(v*2, return_image=True,
                                             format='svg'))
  
      In the graph, ellipses are Apply Nodes (the execution of an op)
      and boxes are variables.  If variables have names they are used as
      text (if multiple vars have the same name, they will be merged in
      the graph).  Otherwise, if the variable is constant, we print its
      value and finally we print the type + a unique number to prevent
      multiple vars from being merged.  We print the op of the apply in
      the Apply box with a number that represents the toposort order of
      application of those Apply.  If an Apply has more than 1 input, we
      label each edge between an input and the Apply node with the
      input's index.
  
      Variable color code::
          - Cyan boxes are SharedVariable, inputs and/or outputs) of the graph,
          - Green boxes are inputs variables to the graph,
          - Blue boxes are outputs variables of the graph,
          - Grey boxes are variables that are not outputs and are not used,
  
      Default apply node code::
          - Red ellipses are transfers from/to the gpu
          - Yellow are scan node
          - Brown are shape node
          - Magenta are IfElse node
          - Dark pink are elemwise node
          - Purple are subtensor
          - Orange are alloc node
  
      For edges, they are black by default. If a node returns a view
      of an input, we put the corresponding input edge in blue. If it
      returns a destroyed input, we put the corresponding edge in red.
  
      .. note::
  
          Since October 20th, 2014, this print the inner function of all
          scan separately after the top level debugprint output.
  
      """
      if colorCodes is None:
          colorCodes = default_colorCodes
  
      if outfile is None:
          outfile = os.path.join(
              config.compiledir,
              "theano.pydotprint." + config.device + "." + format,
          )
  
      if isinstance(fct, Function):
          profile = getattr(fct, "profile", None)
          outputs = fct.maker.fgraph.outputs
          topo = fct.maker.fgraph.toposort()
      elif isinstance(fct, gof.FunctionGraph):
          profile = None
          outputs = fct.outputs
          topo = fct.toposort()
      else:
          if isinstance(fct, gof.Variable):
              fct = [fct]
          elif isinstance(fct, gof.Apply):
              fct = fct.outputs
          assert isinstance(fct, (list, tuple))
          assert all(isinstance(v, gof.Variable) for v in fct)
          fct = gof.FunctionGraph(
              inputs=gof.graph.inputs(fct), outputs=fct
          )
          profile = None
          outputs = fct.outputs
          topo = fct.toposort()
      if not pydot_imported:
          raise RuntimeError(
              "Failed to import pydot. You must install graphviz"
              " and either pydot or pydot-ng for "
              "`pydotprint` to work.",
              pydot_imported_msg,
          )
  
      g = pd.Dot()
  
      if cond_highlight is not None:
          c1 = pd.Cluster("Left")
          c2 = pd.Cluster("Right")
          c3 = pd.Cluster("Middle")
          cond = None
          for node in topo:
              if (
                  node.op.__class__.__name__ == "IfElse"
                  and node.op.name == cond_highlight
              ):
                  cond = node
          if cond is None:
              _logger.warn(
                  "pydotprint: cond_highlight is set but there is no"
                  " IfElse node in the graph"
              )
              cond_highlight = None
  
      if cond_highlight is not None:
  
          def recursive_pass(x, ls):
              if not x.owner:
                  return ls
              else:
                  ls += [x.owner]
                  for inp in x.inputs:
                      ls += recursive_pass(inp, ls)
                  return ls
  
          left = set(recursive_pass(cond.inputs[1], []))
          right = set(recursive_pass(cond.inputs[2], []))
          middle = left.intersection(right)
          left = left.difference(middle)
          right = right.difference(middle)
          middle = list(middle)
          left = list(left)
          right = list(right)
  
      var_str = {}
      var_id = {}
      all_strings = set()
  
      def var_name(var):
          if var in var_str:
              return var_str[var], var_id[var]
  
          if var.name is not None:
              if var_with_name_simple:
                  varstr = var.name
              else:
                  varstr = "name=" + var.name + " " + str(var.type)
          elif isinstance(var, gof.Constant):
              dstr = "val=" + str(np.asarray(var.data))
              if "\n" in dstr:
                  dstr = dstr[: dstr.index("\n")]
              varstr = "%s %s" % (dstr, str(var.type))
          elif (
              var in input_update and input_update[var].name is not None
          ):
              varstr = input_update[var].name
              if not var_with_name_simple:
                  varstr += str(var.type)
          else:
              # a var id is needed as otherwise var with the same type will be
              # merged in the graph.
              varstr = str(var.type)
          if len(varstr) > max_label_size:
              varstr = varstr[: max_label_size - 3] + "..."
          var_str[var] = varstr
          var_id[var] = str(id(var))
  
          all_strings.add(varstr)
  
          return varstr, var_id[var]
  
      apply_name_cache = {}
      apply_name_id = {}
  
      def apply_name(node):
          if node in apply_name_cache:
              return apply_name_cache[node], apply_name_id[node]
          prof_str = ""
          if profile:
              time = profile.apply_time.get(node, 0)
              # second, %fct time in profiler
              if (
                  profile.fct_callcount == 0
                  or profile.fct_call_time == 0
              ):
                  pf = 0
              else:
                  pf = time * 100 / profile.fct_call_time
              prof_str = "   (%.3fs,%.3f%%)" % (time, pf)
          applystr = str(node.op).replace(":", "_")
          applystr += prof_str
          if (applystr in all_strings) or with_ids:
              idx = " id=" + str(topo.index(node))
              if len(applystr) + len(idx) > max_label_size:
                  applystr = (
                      applystr[: max_label_size - 3 - len(idx)]
                      + idx
                      + "..."
                  )
              else:
                  applystr = applystr + idx
          elif len(applystr) > max_label_size:
              applystr = applystr[: max_label_size - 3] + "..."
              idx = 1
              while applystr in all_strings:
                  idx += 1
                  suffix = " id=" + str(idx)
                  applystr = (
                      applystr[: max_label_size - 3 - len(suffix)]
                      + "..."
                      + suffix
                  )
  
          all_strings.add(applystr)
          apply_name_cache[node] = applystr
          apply_name_id[node] = str(id(node))
  
          return applystr, apply_name_id[node]
  
      # Update the inputs that have an update function
      input_update = {}
      reverse_input_update = {}
      # Here outputs can be the original list, as we should not change
      # it, we must copy it.
      outputs = list(outputs)
      if isinstance(fct, Function):
          function_inputs = zip(
              fct.maker.expanded_inputs, fct.maker.fgraph.inputs
          )
          for i, fg_ii in reversed(list(function_inputs)):
              if i.update is not None:
                  k = outputs.pop()
                  # Use the fgaph.inputs as it isn't the same as maker.inputs
                  input_update[k] = fg_ii
                  reverse_input_update[fg_ii] = k
  
      apply_shape = "ellipse"
      var_shape = "box"
      for node_idx, node in enumerate(topo):
          astr, aid = apply_name(node)
  
          use_color = None
          for opName, color in iteritems(colorCodes):
              if opName in node.op.__class__.__name__:
                  use_color = color
  
          if use_color is None:
              nw_node = pd.Node(aid, label=astr, shape=apply_shape)
          elif high_contrast:
              nw_node = pd.Node(
                  aid,
                  label=astr,
                  style="filled",
                  fillcolor=use_color,
                  shape=apply_shape,
              )
          else:
              nw_node = pd.Node(
                  aid, label=astr, color=use_color, shape=apply_shape
              )
          g.add_node(nw_node)
          if cond_highlight:
              if node in middle:
                  c3.add_node(nw_node)
              elif node in left:
                  c1.add_node(nw_node)
              elif node in right:
                  c2.add_node(nw_node)
  
          for idx, var in enumerate(node.inputs):
              varstr, varid = var_name(var)
              label = ""
              if len(node.inputs) > 1:
                  label = str(idx)
              param = {}
              if label:
                  param["label"] = label
              if hasattr(node.op, "view_map") and idx in reduce(
                  list.__add__, node.op.view_map.values(), []
              ):
                  param["color"] = colorCodes["Output"]
              elif hasattr(node.op, "destroy_map") and idx in reduce(
                  list.__add__, node.op.destroy_map.values(), []
              ):
                  param["color"] = "red"
              if var.owner is None:
                  color = "green"
                  if isinstance(var, SharedVariable):
                      # Input are green, output blue
                      # Mixing blue and green give cyan! (input and output var)
                      color = "cyan"
                  if high_contrast:
                      g.add_node(
                          pd.Node(
                              varid,
                              style="filled",
                              fillcolor=color,
                              label=varstr,
                              shape=var_shape,
                          )
                      )
                  else:
                      g.add_node(
                          pd.Node(
                              varid,
                              color=color,
                              label=varstr,
                              shape=var_shape,
                          )
                      )
                  g.add_edge(pd.Edge(varid, aid, **param))
              elif var.name or not compact or var in outputs:
                  g.add_edge(pd.Edge(varid, aid, **param))
              else:
                  # no name, so we don't make a var ellipse
                  if label:
                      label += " "
                  label += str(var.type)
                  if len(label) > max_label_size:
                      label = label[: max_label_size - 3] + "..."
                  param["label"] = label
                  g.add_edge(
                      pd.Edge(apply_name(var.owner)[1], aid, **param)
                  )
  
          for idx, var in enumerate(node.outputs):
              varstr, varid = var_name(var)
              out = var in outputs
              label = ""
              if len(node.outputs) > 1:
                  label = str(idx)
              if len(label) > max_label_size:
                  label = label[: max_label_size - 3] + "..."
              param = {}
              if label:
                  param["label"] = label
              if out or var in input_update:
                  g.add_edge(pd.Edge(aid, varid, **param))
                  if high_contrast:
                      g.add_node(
                          pd.Node(
                              varid,
                              style="filled",
                              label=varstr,
                              fillcolor=colorCodes["Output"],
                              shape=var_shape,
                          )
                      )
                  else:
                      g.add_node(
                          pd.Node(
                              varid,
                              color=colorCodes["Output"],
                              label=varstr,
                              shape=var_shape,
                          )
                      )
              elif len(var.clients) == 0:
                  g.add_edge(pd.Edge(aid, varid, **param))
                  # grey mean that output var isn't used
                  if high_contrast:
                      g.add_node(
                          pd.Node(
                              varid,
                              style="filled",
                              label=varstr,
                              fillcolor="grey",
                              shape=var_shape,
                          )
                      )
                  else:
                      g.add_node(
                          pd.Node(
                              varid,
                              label=varstr,
                              color="grey",
                              shape=var_shape,
                          )
                      )
              elif var.name or not compact:
                  if not (not compact):
                      if label:
                          label += " "
                      label += str(var.type)
                      if len(label) > max_label_size:
                          label = label[: max_label_size - 3] + "..."
                      param["label"] = label
                  g.add_edge(pd.Edge(aid, varid, **param))
                  g.add_node(
                      pd.Node(varid, shape=var_shape, label=varstr)
                  )
      #            else:
      # don't add egde here as it is already added from the inputs.
  
      # The var that represent updates, must be linked to the input var.
      for sha, up in input_update.items():
          _, shaid = var_name(sha)
          _, upid = var_name(up)
          g.add_edge(
              pd.Edge(
                  shaid,
                  upid,
                  label="UPDATE",
                  color=colorCodes["Output"],
              )
          )
  
      if cond_highlight:
          g.add_subgraph(c1)
          g.add_subgraph(c2)
          g.add_subgraph(c3)
  
      if not outfile.endswith("." + format):
          outfile += "." + format
  
      if scan_graphs:
          scan_ops = [
              (idx, x)
              for idx, x in enumerate(topo)
              if isinstance(x.op, theano.scan_module.scan_op.Scan)
          ]
          path, fn = os.path.split(outfile)
          basename = ".".join(fn.split(".")[:-1])
          # Safe way of doing things .. a file name may contain multiple .
          ext = fn[len(basename) :]
  
          for idx, scan_op in scan_ops:
              # is there a chance that name is not defined?
              if hasattr(scan_op.op, "name"):
                  new_name = (
                      basename + "_" + scan_op.op.name + "_" + str(idx)
                  )
              else:
                  new_name = basename + "_" + str(idx)
              new_name = os.path.join(path, new_name + ext)
              if hasattr(scan_op.op, "fn"):
                  to_print = scan_op.op.fn
              else:
                  to_print = scan_op.op.outputs
              pydotprint(
                  to_print,
                  new_name,
                  compact,
                  format,
                  with_ids,
                  high_contrast,
                  cond_highlight,
                  colorCodes,
                  max_label_size,
                  scan_graphs,
              )
  
      if return_image:
          return g.create(prog="dot", format=format)
      else:
          try:
              g.write(outfile, prog="dot", format=format)
          except pd.InvocationException:
              # based on https://github.com/Theano/Theano/issues/2988
              version = getattr(pd, "__version__", "")
              if version and [int(n) for n in version.split(".")] < [
                  1,
                  0,
                  28,
              ]:
                  raise Exception(
                      "Old version of pydot detected, which can "
                      "cause issues with pydot printing. Try "
                      "upgrading pydot version to a newer one"
                  )
              raise
  
          if print_output_file:
              print("The output file is available at", outfile)
  
imports: from __future__ import absolute_import, print_function, division
  from copy import copy
  import logging
  import os
  import sys
  import hashlib
  import numpy
  from six import string_types, integer_types, iteritems
  from six.moves import StringIO, reduce
  import theano
  from theano import gof
  from theano import config
  from theano.gof import Op, Apply
  from theano.compile import Function, debugmode, SharedVariable
label_desc: Use image node_id 3 for calling a global function with example usage: pydotprint(fct, outfile, compact, format, with_ids, high_contrast, cond_highlight, colorCodes, max_label_size, scan_graphs, var_with_name_simple, print_output_file, return_image) and returns: varstr,  var_id, applystr,  apply_name_id, g, var_str,  var_id, apply_name_cache,  apply_name_id, ls, ls