class_docstr: Object to store runtime and memory profiling information for all of
  Theano's operations: compilation, optimization, execution.
  
  Parameters
  ----------
  atexit_print : bool
      True means that this object will be printed to stderr (using .summary())
      at the end of the program.
  **kwargs : misc initializers
      These should (but need not) match the names of the class vars declared
      in this class.
class_docstr_tok: ["Object","to","store","runtime","and","memory","profiling","information","for","all","of","Theano","'s","operations",":","compilation",",","optimization",",","execution",".","Parameters","--","--","--","--","--","atexit_print",":","bool","True","means","that","this","object","will","be","printed","to","stderr","(","using",".summary","(",")",")","at","the","end","of","the","program",".","*","*","kwargs",":","misc","initializers","These","should","(","but","need","not",")","match","the","names","of","the","class","vars","declared","in","this","class","."]
code: def summary_memory(self, file, N=None):
      fct_memory = {}  # fgraph->dict(node->[outputs size])
      fct_shapes = {}  # fgraph->dict(node->[outputs shapes]))
      var_mem = (
          {}
      )  # variable->size in bytes; don't include input variables
      node_mem = {}  # node->total outputs size (only dense outputs)
  
      for node in self.apply_callcount:
          fct_memory.setdefault(node.fgraph, {})
          fct_memory[node.fgraph].setdefault(node, [])
          fct_shapes.setdefault(node.fgraph, {})
          fct_shapes[node.fgraph].setdefault(node, [])
          sum_dense = 0
          for out in node.outputs:
              if out in self.variable_shape:
                  sh = self.variable_shape[out]
                  if hasattr(out.type, "get_size"):
                      v = out.type.get_size(sh)
                      sum_dense += v
                  else:
                      v = 0  # 'Unknown'
              else:
                  v = 0  # 'Variable isn't created'
  
              var_mem[out] = v
              fct_memory[node.fgraph][node].append(v)
              fct_shapes[node.fgraph][node].append(sh)
          node_mem[node] = sum_dense
      del v
  
      # Find the function that used the most of that statistic
      max_sum_size = 0
  
      # statistics with the old and new order
      stats = [
          [[0, 0, 0], [0, 0, 0], 0, 0],  # old, with dmap
          [[0, 0, 0], [0, 0, 0], 0, 0],  # old, without dmap
          [[0, 0, 0], [0, 0, 0], 0, 0],  # new, with dmap
          [[0, 0, 0], [0, 0, 0], 0, 0],
      ]  # new, without dmap
  
      # track min peak memory usage
      min_max_peak = 0
      min_peak_time = 0
  
      def count_running_memory(
          order, fgraph, nodes_mem, ignore_dmap=False
      ):
          """
          Calculate memory with specific node order.
  
          Return a list including the following values
          1.  node_memory_size
              Sum of the size of all variables that actually allocate
              memory (excluding views, and inplace).
          2.  running_memory_size
              The memory allocated after the current apply node.
          3.  running_max_memory_size
              The maximum of running_memory_size during the function.
          4.  node_memory_saved_by_view
              The sum of memory saved by returning view instead of new
              allocation.
          5.  node_memory_saved_by_inplace
              The sum of memory saved by reusing the input instead of
              new allocation.
  
          """
          from theano.gpuarray import GpuArrayType
  
          # Initial Mem info values [CPU, GPU]
          node_memory_size = [0, 0]
          running_memory_size = [0, 0]
          running_max_memory_size = [0, 0]
          node_memory_saved_by_view = 0
          node_memory_saved_by_inplace = 0
          # This take only the inputs/outputs dependencies.
          dependencies = fgraph.profile.dependencies
  
          # Initial compute_map which is used to check if a node is valid
          compute_map = defaultdict(lambda: [0])
          for var in fgraph.inputs:
              compute_map[var][0] = 1
  
          # two data structure used to mimic Python gc
          viewed_by = {}  # {var1: [vars that view var1]}
          # The len of the list is the value of python ref
          # count. But we use a list, not just the ref count value.
          # This is more safe to help detect potential bug  in the algo
          for var in fgraph.variables:
              viewed_by[var] = []
          view_of = {}  # {var1: original var viewed by var1}
          # The orignal mean that we don't keep trac of all the intermediate
          # relationship in the view.
  
          for node in order:
              for var in node.outputs:
                  compute_map[var][0] = 1
              idx = 0
              if ignore_dmap:
                  dmap = None
              else:
                  dmap = getattr(node.op, "destroy_map", None)
              vmap = getattr(node.op, "view_map", None)
              val = nodes_mem[node]
  
              for v in val:
                  # TODO check the op returned a view
                  if dmap and idx in dmap:
                      node_memory_saved_by_inplace += v
                  # TODO check the op returned a view
                  elif vmap and idx in vmap:
                      node_memory_saved_by_view += v
                  idx += 1
  
              # Update the Python emulating dicts and add the memory
              # allocated by the node
              idx2 = 0
              for out in node.outputs:
                  if isinstance(out.type, GpuArrayType):
                      cg = 1
                  else:
                      cg = 0
                  ins = None
                  if dmap and idx2 in dmap:
                      vidx = dmap[idx2]
                      assert len(vidx) == 1, (
                          "Here we only support the "
                          "possibility to destroy one "
                          "input"
                      )
                      ins = node.inputs[vidx[0]]
                  if vmap and idx2 in vmap:
                      assert ins is None
                      vidx = vmap[idx2]
                      assert len(vidx) == 1, (
                          "Here we only support the "
                          "possibility to view one "
                          "input"
                      )
                      ins = node.inputs[vidx[0]]
                  if ins is not None:
                      # This is needed for destroy_map in case it
                      # return a partial view that is destroyed.  So
                      # the output could be different then the
                      # input.
                      assert isinstance(ins, theano.Variable)
                      # we keep trac of view only again the origin
                      origin = view_of.get(ins, ins)
                      view_of[out] = origin
                      viewed_by[origin].append(out)
                  else:
                      running_memory_size[cg] += var_mem[out]
                      node_memory_size[cg] += var_mem[out]
                  idx2 += 1
  
              running_max_memory_size[0] = max(
                  running_max_memory_size[0], running_memory_size[0]
              )
              running_max_memory_size[1] = max(
                  running_max_memory_size[1], running_memory_size[1]
              )
  
              # Mimic the combination of Theano and Python gc
              for ins in set(node.inputs):
                  assert not (ins in view_of and viewed_by[ins])
                  # we trac the original var, so this shouldn't happen
                  if isinstance(ins.type, GpuArrayType):
                      cg = 1
                  else:
                      cg = 0
                  if (
                      dependencies[ins]
                      and ins not in fgraph.outputs
                      and ins.owner
                      and all(
                          [compute_map[v][0] for v in dependencies[ins]]
                      )
                  ):
                      if ins not in view_of and not viewed_by.get(
                          ins, []
                      ):
                          running_memory_size[cg] -= var_mem[ins]
                      elif ins in view_of:
                          origin = view_of[ins]
                          viewed_by[origin].remove(ins)
                          if (
                              not viewed_by[origin]
                              and origin not in fgraph.inputs
                              and not isinstance(
                                  origin, theano.Constant
                              )
                          ):
                              running_memory_size[cg] -= var_mem[origin]
                  else:
                      # ins is viewed_by something else, so its
                      # memory isn't freed
                      pass
  
          return [
              node_memory_size,
              running_memory_size,
              running_max_memory_size,
              node_memory_saved_by_inplace,
              node_memory_saved_by_view,
          ]
  
      def count_minimum_peak(node_list, fgraph, nodes_mem):
          global mem_count, mem_bound, max_mem_count
          node_list = list(node_list)
          mem_count = 0
          max_mem_count = 0
          mem_bound = np.inf
          # This take only the inputs/outputs dependencies.
          dependencies = fgraph.profile.dependencies
          done_set = set([])
          done_dict = {}
  
          # Initial compute_map which is used to check if a node is valid
          compute_map = defaultdict(lambda: [0])
          for var in fgraph.inputs:
              compute_map[var][0] = 1
          for var in node_list:
              for val in var.inputs:
                  if isinstance(val, graph.Constant):
                      compute_map[val][0] = 1
  
          # Initial executable_nodes
          executable_nodes = set()
          for var in fgraph.inputs:
              for c, _ in var.clients:
                  if c != "output":
                      deps = c.inputs + c.destroy_dependencies
                      if all(compute_map[v][0] for v in deps):
                          executable_nodes.add(c)
  
          def min_memory_generator(
              executable_nodes, viewed_by, view_of
          ):
              """
              Generate all valid node order from node_list and compute its
              memory peak.
  
              Parameters
              ----------
              executable_nodes
                  Set of executable nodes.
  
              """
              global mem_count, mem_bound, max_mem_count
  
              for node in executable_nodes:
                  new_exec_nodes = executable_nodes.copy()
                  new_exec_nodes.remove(node)
  
                  # Check if cut path now
                  if max_mem_count > mem_bound:
                      continue
  
                  viewof_change = []
                  # Use to track view_of changes
  
                  viewedby_add = defaultdict(lambda: [])
                  viewedby_remove = defaultdict(lambda: [])
                  # Use to track viewed_by changes
  
                  for var in node.outputs:
                      compute_map[var][0] = 1
  
                  mem_created = 0
                  mem_freed = 0
                  max_storage = max_mem_count
  
                  dmap = getattr(node.op, "destroy_map", None)
                  vmap = getattr(node.op, "view_map", None)
  
                  idx = 0
                  # Update the Python emulating dicts and add the
                  # memory allocated by the node
                  for out in node.outputs:
                      ins = None
                      if dmap and idx in dmap:
                          vidx = dmap[idx]
                          assert len(vidx) == 1, (
                              "Here we only support "
                              "the possibility to "
                              "destroy one input"
                          )
                          ins = node.inputs[vidx[0]]
                      if vmap and idx in vmap:
                          assert ins is None
                          vidx = vmap[idx]
                          assert len(vidx) == 1, (
                              "Here we only support "
                              "the possibility to "
                              "view one input"
                          )
                          ins = node.inputs[vidx[0]]
                      if ins is not None:
                          # This is needed for destroy_map in case it
                          # return a partial view that is destroyed. So
                          # the output could be different then the
                          # input.
                          assert isinstance(ins, theano.Variable)
                          # We keep track of view only again the original
                          origin = view_of.get(ins, ins)
                          view_of[out] = origin
                          viewof_change.append(out)
                          viewed_by[origin].append(out)
                          viewedby_add[origin].append(out)
                      else:
                          mem_created += var_mem[out]
                      idx += 1
  
                  mem_count += mem_created
                  max_mem_count = max(max_mem_count, mem_count)
  
                  # Mimic the combination of Theano and Python gc.
                  for ins in node.inputs:
                      assert not (ins in view_of and viewed_by[ins])
                      # We track of the original var, so this shouldn't
                      # happen
                      if (
                          dependencies[ins]
                          and ins not in fgraph.outputs
                          and ins.owner
                          and all(
                              [
                                  compute_map[v][0]
                                  for v in dependencies[ins]
                              ]
                          )
                      ):
                          if ins not in view_of and not viewed_by.get(
                              ins, []
                          ):
                              mem_freed += var_mem[ins]
                          elif ins in view_of:
                              origin = view_of[ins]
                              viewed_by[origin].remove(ins)
                              viewedby_remove[origin].append(ins)
                              if (
                                  not viewed_by[origin]
                                  and origin not in fgraph.inputs
                                  and not isinstance(
                                      origin, theano.Constant
                                  )
                              ):
                                  mem_freed += var_mem[origin]
                      else:
                          # ins is viewed_by something else, so its
                          # memory isn't freed
                          pass
  
                  mem_count -= mem_freed
  
                  done_set.add(node)
                  frozen_set = frozenset(done_set)
                  if (
                      done_dict.get(frozen_set, max_mem_count + 1)
                      > max_mem_count
                  ):
                      # check if frozen_set is in done_set
                      # no, add it to done_set
                      # yes, then compare the past mem and current mem
                      # bigger, update the value and continue
                      # smaller, stop this iteration, move to next node
                      done_dict[frozen_set] = max_mem_count
  
                      for var in node.outputs:
                          for c, _ in var.clients:
                              if c != "output":
                                  deps = (
                                      c.inputs + c.destroy_dependencies
                                  )
                                  if all(
                                      compute_map[v][0] for v in deps
                                  ):
                                      new_exec_nodes.add(c)
  
                      if not new_exec_nodes:
                          # Check and Update mem_bound
                          if max_mem_count < mem_bound:
                              mem_bound = max_mem_count
                      else:
                          min_memory_generator(
                              new_exec_nodes, viewed_by, view_of
                          )
  
                  # Reset track variables
                  done_set.remove(node)
                  mem_count -= mem_created
                  max_mem_count = max_storage
                  mem_count += mem_freed
                  for var in node.outputs:
                      compute_map[var][0] = 0
  
                  for k_remove, v_remove in iteritems(viewedby_remove):
                      for i in v_remove:
                          viewed_by[k_remove].append(i)
  
                  for k_add, v_add in iteritems(viewedby_add):
                      for i in v_add:
                          viewed_by[k_add].remove(i)
  
                  for k in viewof_change:
                      del view_of[k]
  
          # two data structure used to mimic Python gc
          viewed_by = {}  # {var1: [vars that view var1]}
          # The len of the list is the value of python ref
          # count. But we use a list, not just the ref count value.
          # This is more safe to help detect potential bug  in the algo
          for var in fgraph.variables:
              viewed_by[var] = []
          view_of = {}  # {var1: original var viewed by var1}
          # The orignal mean that we don't keep trac of all the intermediate
          # relationship in the view.
  
          min_memory_generator(executable_nodes, viewed_by, view_of)
  
          return mem_bound
  
      for fgraph, nodes_mem in iteritems(fct_memory):
          # Sum of the size of all variables in bytes
          sum_size = sum(
              sum(v for v in val if not isinstance(v, str))
              for key, val in iteritems(nodes_mem)
          )
  
          order = fgraph.toposort()
          # A list of intermediate variable that are not need
          # after the execution of the corresponding node.
          # It mean that after executing the node,
          # the corresponding variable can be gc.
  
          # Store the max of some stats by any function in this profile.
          max_sum_size = max(max_sum_size, sum_size)
  
          def compute_max_stats(running_memory, stats):
              (
                  max_node_memory_size,
                  max_running_max_memory_size,
                  max_node_memory_saved_by_view,
                  max_node_memory_saved_by_inplace,
              ) = stats
  
              max_node_memory_size[0] = max(
                  max_node_memory_size[0], sum(running_memory[0])
              )
              max_running_max_memory_size[0] = max(
                  max_running_max_memory_size[0], sum(running_memory[2])
              )
  
              # Separate CPU and GPU
              max_node_memory_size[1] = max(
                  max_node_memory_size[1], running_memory[0][0]
              )
              max_node_memory_size[2] = max(
                  max_node_memory_size[2], running_memory[0][1]
              )
              max_running_max_memory_size[1] = max(
                  max_running_max_memory_size[1], running_memory[2][0]
              )
              max_running_max_memory_size[2] = max(
                  max_running_max_memory_size[2], running_memory[2][1]
              )
  
              max_node_memory_saved_by_inplace = max(
                  max_node_memory_saved_by_inplace, running_memory[3]
              )
              max_node_memory_saved_by_view = max(
                  max_node_memory_saved_by_view, running_memory[4]
              )
              return (
                  max_node_memory_size,
                  max_running_max_memory_size,
                  max_node_memory_saved_by_view,
                  max_node_memory_saved_by_inplace,
              )
  
          new_order = fgraph.profile.node_executed_order
          # A list of new executed node order
          for i, (ord, ignore_dmap) in enumerate(
              [
                  (order, False),
                  (order, True),
                  (new_order, False),
                  (new_order, True),
              ]
          ):
              running_memory = count_running_memory(
                  ord, fgraph, nodes_mem, ignore_dmap=ignore_dmap
              )
  
              stats[i] = compute_max_stats(running_memory, stats[i])
  
          # Config: whether print min memory peak
          if config.profiling.min_peak_memory:
              node_list = fgraph.apply_nodes
              ttt = time.time()
              min_peak = count_minimum_peak(
                  node_list, fgraph, nodes_mem
              )
              min_peak_time += time.time() - ttt
              min_max_peak = max(min_max_peak, min_peak)
  
          del fgraph, nodes_mem
  
      if len(fct_memory) > 1:
          print(
              "Memory Profile (the max between all functions in "
              "that profile)",
              file=file,
          )
      else:
          print("Memory Profile", file=file)
  
      print("(Sparse variables are ignored)", file=file)
      print(
          "(For values in brackets, it's for linker = c|py", file=file
      )
  
      def print_stats(stats1, stats2):
          (_, max_running_max_memory_size, _, _) = stats1
          (_, new_max_running_max_memory_size, _, _) = stats2
  
          print(
              "        CPU: %dKB (%dKB)"
              % (
                  (
                      int(
                          round(
                              new_max_running_max_memory_size[1]
                              / 1024.0
                          )
                      ),
                      int(
                          round(max_running_max_memory_size[1] / 1024.0)
                      ),
                  )
              ),
              file=file,
          )
          print(
              "        GPU: %dKB (%dKB)"
              % (
                  (
                      int(
                          round(
                              new_max_running_max_memory_size[2]
                              / 1024.0
                          )
                      ),
                      int(
                          round(max_running_max_memory_size[2] / 1024.0)
                      ),
                  )
              ),
              file=file,
          )
          print(
              "        CPU + GPU: %dKB (%dKB)"
              % (
                  int(
                      round(new_max_running_max_memory_size[0] / 1024.0)
                  ),
                  int(round(max_running_max_memory_size[0] / 1024.0)),
              ),
              file=file,
          )
  
      print("---", file=file)
      print("    Max peak memory with current setting", file=file)
      print_stats(stats[0], stats[2])
      print(
          "    Max peak memory with current setting and Theano flag optimizer_excluding=inplace",
          file=file,
      )
      print_stats(stats[1], stats[3])
  
      (max_node_memory_size, _, _, _) = stats[0]
      (new_max_node_memory_size, _, _, _) = stats[2]
      print(
          "    Max peak memory if allow_gc=False (linker don't make a difference)",
          file=file,
      )
      print(
          "        CPU: %dKB"
          % int(round(new_max_node_memory_size[1] / 1024.0)),
          file=file,
      )
      print(
          "        GPU: %dKB"
          % int(round(new_max_node_memory_size[2] / 1024.0)),
          file=file,
      )
      print(
          "        CPU + GPU: %dKB"
          % int(round(new_max_node_memory_size[0] / 1024.0)),
          file=file,
      )
      print("---", file=file)
  
      if min_max_peak:
          print(
              "    Minimum peak from all valid apply node order is "
              "%dKB(took %.3fs to compute)"
              % (int(round(min_max_peak / 1024.0)), min_peak_time),
              file=file,
          )
  
          print("---", file=file)
  
      print("", file=file)
      if len(fct_memory) > 1:
          print(
              "    This list is based on all functions in the profile",
              file=file,
          )
      print(
          "    <Sum apply outputs (bytes)>"
          " <Apply outputs shape>"
          " <created/inplace/view>"
          " <Apply node>",
          file=file,
      )
      print("", file=file)
      items = list(node_mem.items())
      items.sort(key=lambda a: a[1], reverse=True)
      for idx, (node, node_outputs_size) in enumerate(items[:N]):
          code = ["c"] * len(node.outputs)
          for out, inp in iteritems(
              getattr(node.op, "destroy_map", {})
          ):
              code[out] = "i"
          for out, inp in iteritems(getattr(node.op, "view_map", {})):
              code[out] = "v"
          shapes = str(fct_shapes[node.fgraph][node])
  
          if all(
              [hasattr(out.type, "get_size") for out in node.outputs]
          ):
              size = "%9dB" % node_outputs_size
              if node_outputs_size < config.profiling.min_memory_size:
                  N = idx
                  break
          else:
              size = "%10s" % "Unknown"
  
          print(
              "     %s  %s %s %s"
              % (size, shapes, " ".join(code), node),
              file=file,
          )
  
      sum_remaining = sum(size for _, size in items[N:])
      size_sum_dense = sum(node_mem.values())
      if size_sum_dense == 0:
          p = "0%"
      else:
          p = "(%.2f%%)" % (float(sum_remaining) / size_sum_dense * 100)
      print(
          "   ... (remaining %i Apply account for %4dB/%dB (%s) of the"
          " Apply with dense outputs sizes)"
          % (
              max(0, len(node_mem) - N),
              sum_remaining,
              size_sum_dense,
              p,
          ),
          file=file,
      )
      print("", file=file)
      if N == 0:
          print(
              "    All Apply nodes have output sizes that take less "
              "than %dB." % config.profiling.min_memory_size,
              file=file,
          )
      print(
          "    <created/inplace/view> is taken from the Op's declaration.",
          file=file,
      )
      print(
          "    Apply nodes marked 'inplace' or 'view' may"
          " actually allocate memory, this is not reported"
          " here. If you use DebugMode, warnings will be"
          " emitted in those cases.",
          file=file,
      )
      print("", file=file)
  
imports: from __future__ import absolute_import, print_function, division
  import atexit
  import copy
  import logging
  import operator
  import os
  import sys
  import time
  from collections import defaultdict
  from six import iteritems
  import warnings
  import numpy
  import theano
  from theano.gof import graph
label_desc: Use image node_id 18 for calling the ProfileStats obj's underlying member method code with example usage: obj.summary_memory(file, N) and returns: list, mem_bound, max_node_memory_size,  max_running_max_memory_size,  max_node_memory_saved_by_view,  max_node_memory_saved_by_inplace