class_docstr: `FunctionMaker` is the class to `create` `Function` instances.
  
  This class has the fgraph, the optimizer, and the linker. When
  copying a `Function`, there is no need to duplicate the
  `FunctionMaker` instance. Deepcopy still copies both, which can
  variable in re-compilation.
  
  Parameters
  ----------
  inputs : list of SymbolicInput instances
  outputs : list of SymbolicOutput instances
      Outputs may also be a single Variable (not a list), in which case the
      functions produced by FunctionMaker will return their output value
      directly.
  mode : Mode instance
      Telling FunctionMaker how to optimize and link. None means to use the
      `config.mode`.
  accept_inplace : bool
      True iff it is acceptable to have inplace operations in the graph from
      the inputs to the outputs.
  on_unused_input : {'raise', 'warn', 'ignore', None}
      What to do if a variable in the 'inputs' list is not used in the graph.
      Possible values are:
      - 'raise': raise an error
      - 'warn': log a warning
      - 'ignore': do not do anything
      - None: Use the value in the Theano flags on_unused_input.
  name : str
      An optional name for this function. If used, the profile mode will
      print the time spent in this function.
class_docstr_tok: ["`","FunctionMaker","`","is","the","class","to","`","create","`","`","Function","`","instances",".","This","class","has","the","fgraph",",","the","optimizer",",","and","the","linker",".","When","copying","a","`","Function","`",",","there","is","no","need","to","duplicate","the","`","FunctionMaker","`","instance",".","Deepcopy","still","copies","both",",","which","can","variable","in","re-compilation",".","Parameters","--","--","--","--","--","inputs",":","list","of","SymbolicInput","instances","outputs",":","list","of","SymbolicOutput","instances","Outputs","may","also","be","a","single","Variable","(","not","a","list",")",",","in","which","case","the","functions","produced","by","FunctionMaker","will","return","their","output","value","directly",".","mode",":","Mode","instance","Telling","FunctionMaker","how","to","optimize","and","link",".","None","means","to","use","the","`","config.mode","`",".","accept_inplace",":","bool","True","iff","it","is","acceptable","to","have","inplace","operations","in","the","graph","from","the","inputs","to","the","outputs",".","on_unused_input",":","{","'raise","'",",","'warn","'",",","'ignore","'",",","None","}","What","to","do","if","a","variable","in","the","'inputs","'","list","is","not","used","in","the","graph",".","Possible","values","are",":","-","'raise","'",":","raise","an","error","-","'warn","'",":","log","a","warning","-","'ignore","'",":","do","not","do","anything","-","None",":","Use","the","value","in","the","Theano","flags","on_unused_input",".","name",":","str","An","optional","name","for","this","function",".","If","used",",","the","profile","mode","will","print","the","time","spent","in","this","function","."]
code: def optimize_graph_with_cache(self, optimizer, inputs, outputs):
      # This function is not finished
      from theano.gof.compilelock import get_lock, release_lock
      import os.path
  
      graph_db_file = os.path.join(
          theano.config.compiledir, "optimized_graphs.pkl"
      )
  
      # the inputs, outputs, and size of the graph to be optimized
      inputs_new = [inp.variable for inp in inputs]
      outputs_new = [out.variable for out in outputs]
      size_new = len(self.fgraph.apply_nodes)
      get_lock()
      # Beginning of cache optimizations.
      # Could be refactored in different functions.
  
      def load_graph_db():
          if os.path.isfile(graph_db_file):
              print("graph_db already exists")
          else:
              # create graph_db
              with open(graph_db_file, "wb") as f:
                  print("create new graph_db in %s" % graph_db_file)
          # load the graph_db dictionary
          try:
              with open(graph_db_file, "rb") as f:
                  # Temporary hack to allow
                  # theano.scan_module.tests.test_scan.T_Scan to
                  # finish. Should be changed in definitive version.
                  tmp = theano.config.unpickle_function
                  theano.config.unpickle_function = False
                  graph_db = pickle.load(f)
              print("graph_db loaded and it is not empty")
          except EOFError as e:
              # the file has nothing in it
              print(e)
              print("graph_db loaded and it is empty")
              graph_db = {}
          finally:
              theano.config.unpickle_function = tmp
  
          return graph_db
  
      def find_same_graph_in_db(graph_db):
          # If found_graph_in_db is None, then need to optimize.
          # Otherwise, return the graph found.
          found_graph_in_db = None
          # The sole purpose of this loop is to set 'need_optimize' by
          # going through graph_db, looking for graph that has the same
          # computation performed.
          for graph_old, graph_optimized in iteritems(graph_db):
              inputs_old = graph_old.inputs
              outputs_old = graph_old.outputs
              size_old = len(graph_old.apply_nodes)
              # Some heuristics to check is the same graphs have
              # already been optimized before.
              if len(inputs_new) != len(inputs_old):
                  # If the inputs are of different size,
                  # two graphs are for sure different
                  print(
                      "need to optimize, because input size is different"
                  )
                  continue
              elif len(outputs_new) != len(outputs_old):
                  # If the inputs are of different size,
                  # two graphs are for sure different
                  print(
                      "need to optimize, because output size is different"
                  )
                  continue
              elif not all(
                  input_new.type == input_old.type
                  for input_new, input_old in zip(
                      inputs_new, inputs_old
                  )
              ):
                  print(
                      "need to optimize, because inputs are of different "
                      "types"
                  )
                  continue
              elif not all(
                  output_new.type == output_old.type
                  for output_new, output_old in zip(
                      outputs_new, outputs_old
                  )
              ):
                  print(
                      "need to optimize, because outputs are of different "
                      "types"
                  )
                  continue
              elif not size_old == size_new:
                  print(
                      "need to optimize, because numbers of nodes in graph"
                      " are different"
                  )
                  continue
              else:
                  flags = []
                  for i, (output_new, output_old) in enumerate(
                      zip(outputs_new, outputs_old)
                  ):
                      print("loop through outputs node for both graphs")
                      graph_old.variables = set(
                          gof.graph.variables(
                              graph_old.inputs, graph_old.outputs
                          )
                      )
  
                      # using clone allowed to avoid a lot of errors
                      # deep copy seemed to had.
                      f2 = graph_old.clone(check_integrity=False)
                      t1 = output_new
                      t2 = f2.outputs[i]
  
                      # Used to remove "already used by another graph error
                      def removeAllFgraph(remove):
                          if hasattr(remove, "fgraph"):
                              del remove.fgraph
                          if hasattr(remove, "owner"):
                              if remove.owner is None:
                                  pass
                              else:
                                  if hasattr(remove.owner, "fgraph"):
                                      del remove.owner.fgraph
                                  if hasattr(remove.owner, "inputs"):
                                      remove.owner.inputs = [
                                          removeAllFgraph(i)
                                          for i in remove.owner.inputs
                                      ]
                                      for o in remove.owner.outputs:
                                          if hasattr(o, "fgraph"):
                                              del o.fgraph
                          return remove
  
                      t2 = removeAllFgraph(t2)
  
                      givens = dict(
                          izip(
                              gof.graph.inputs([t1]),
                              gof.graph.inputs([t2]),
                          )
                      )
  
                      temp = dict(
                          izip(
                              gof.graph.inputs([t1]),
                              gof.graph.inputs([t2]),
                          )
                      )
  
                      # hack to remove inconstent entry in givens
                      # seems to work that but source of inconsistency
                      # could be worth investigating.
                      for key, value in iteritems(temp):
                          if key.type != value.type:
                              del givens[key]
  
                      flag = is_same_graph(t1, t2, givens=givens)
  
                      flags.append(flag)
  
                  is_same = all(flags)
                  if is_same:
                      # found the match
                      print("found a match, no need to optimize")
                      found_graph_in_db = graph_optimized
                      break
          return found_graph_in_db
  
      graph_db = load_graph_db()
      print(
          "loaded graph_db from %s, size=%d"
          % (graph_db_file, len(graph_db))
      )
      found_graph = find_same_graph_in_db(graph_db)
      if found_graph:
          self.fgraph = found_graph
          optimizer_profile = None
      else:
          # this is a brand new graph, optimize it, save it to graph_db
          print("graph not found in graph_db, optimizing the graph")
          self.fgraph.variables = set(
              gof.graph.variables(
                  self.fgraph.inputs, self.fgraph.outputs
              )
          )
          # check_integrity parameters was added to ignore
          # "excess cached variables" errors. Works that way
          # but once again the error couldbe worth
          # investigating.
          before_opt = self.fgraph.clone(check_integrity=False)
          optimizer_profile = optimizer(self.fgraph)
          graph_db.update({before_opt: self.fgraph})
          with open(graph_db_file, "wb") as f:
              pickle.dump(graph_db, f, -1)
          print("new graph saved into graph_db")
      release_lock()
      return optimizer_profile
  
imports: from __future__ import absolute_import, print_function, division
  import copy
  from six import string_types, iteritems, iterkeys
  from six.moves import xrange
  import six.moves.copyreg
  import six.moves.cPickle
  from itertools import chain
  import time
  import warnings
  import numpy
  import theano
  from theano import config, gof
  from theano.compat import izip
  from theano.gof import graph
  import theano.compile.profiling
  from theano.compile.io import In, SymbolicInput, SymbolicOutput
  from theano.compile.ops import deep_copy_op, view_op
  from theano.gof.graph import is_same_graph
  from theano.gof.op import ops_with_inner_function
  import logging
label_desc: Use image node_id 4 for calling the FunctionMaker obj's underlying member method code with example usage: obj.optimize_graph_with_cache(optimizer, inputs, outputs) and returns: optimizer_profile, graph_db, found_graph_in_db, remove