def __import__(self, node, check = True): # We import the nodes in topological order. We only are interested # in new nodes, so we use all variables we know of as if they were the input set. # (the functions in the graph module only use the input set to # know where to stop going down) new_nodes = graph.io_toposort(self.variables, node.outputs) if check: for node in new_nodes: if hasattr(node, 'env') and node.env is not self: raise Exception("%s is already owned by another env" % node) for r in node.inputs: if hasattr(r, 'env') and r.env is not self: raise Exception("%s is already owned by another env" % r) if r.owner is None and not isinstance(r, graph.Value) and r not in self.inputs: raise TypeError("An input of the graph, used to compute "+str(node)+", was not provided and not given a value", r) for node in new_nodes: assert node not in self.nodes self.__setup_node__(node) self.nodes.add(node) for output in node.outputs: self.__setup_r__(output) self.variables.add(output) for i, input in enumerate(node.inputs): if input not in self.variables: self.__setup_r__(input) self.variables.add(input) self.__add_clients__(input, [(node, i)]) assert node.env is self self.execute_callbacks('on_import', node)
def toposort(self): """WRITEME Returns an ordering of the graph's Apply nodes such that: - All the nodes of the inputs of a node are before that node. - Satisfies the orderings provided by each feature that has an 'orderings' method. If a feature has an 'orderings' method, it will be called with this FunctionGraph as sole argument. It should return a dictionary of {node: predecessors} where predecessors is a list of nodes that should be computed before the key node. """ if len(self.apply_nodes) < 2: # optimization # when there are 0 or 1 nodes, no sorting is necessary # This special case happens a lot because the OpWiseCLinker produces # 1-element graphs. return list(self.apply_nodes) fg = self ords = self.orderings() order = graph.io_toposort(fg.inputs, fg.outputs, ords) return order
def apply(self, env, start_from = None): if start_from is None: start_from = env.outputs q = deque(graph.io_toposort(env.inputs, start_from)) def importer(node): if node is not current_node: q.append(node) def pruner(node): if node is not current_node: try: q.remove(node) except ValueError: pass u = self.attach_updater(env, importer, pruner) try: while q: if self.order == 'out_to_in': node = q.pop() else: node = q.popleft() current_node = node self.process_node(env, node) except Exception: self.detach_updater(env, u) raise self.detach_updater(env, u)
def apply(self, env, start_from=None): if start_from is None: start_from = env.outputs q = deque(graph.io_toposort(env.inputs, start_from)) def importer(node): if node is not current_node: q.append(node) def pruner(node): if node is not current_node: try: q.remove(node) except ValueError: pass u = self.attach_updater(env, importer, pruner) try: while q: if self.order == 'out_to_in': node = q.pop() else: node = q.popleft() current_node = node self.process_node(env, node) except Exception: self.detach_updater(env, u) raise self.detach_updater(env, u)
def apply(self, env, start_from = None): if start_from is None: start_from = env.outputs changed = True max_use_abort = False opt_name = None process_count = {} while changed and not max_use_abort: changed = False #apply global optimizer env.change_tracker.reset() for gopt in self.global_optimizers: gopt.apply(env) if env.change_tracker.changed: changed = True #apply local optimizer for node in start_from: assert node in env.outputs q = deque(graph.io_toposort(env.inputs, start_from)) max_use = len(q) * self.max_use_ratio def importer(node): if node is not current_node: q.append(node) def pruner(node): if node is not current_node: try: q.remove(node) except ValueError: pass u = self.attach_updater(env, importer, pruner) try: while q: node = q.pop() current_node = node for lopt in self.local_optimizers: process_count.setdefault(lopt, 0) if process_count[lopt] > max_use: max_use_abort = True opt_name = (getattr(lopt, "name", None) or getattr(lopt, "__name__", None) or "") else: lopt_change = self.process_node(env, node, lopt) if lopt_change: process_count[lopt] += 1 changed = True if node not in env.nodes: break# go to next node finally: self.detach_updater(env, u) self.detach_updater(env, u) #TODO: erase this line, it's redundant at best if max_use_abort: _logger.error("EquilibriumOptimizer max'ed out by '%s'" % opt_name + ". You can safely raise the current threshold of " + "%f with the theano flag 'optdb.max_use_ratio'." % config.optdb.max_use_ratio)
def toposort(self): """WRITEME Returns an ordering of the graph's Apply nodes such that: - All the nodes of the inputs of a node are before that node. - Satisfies the orderings provided by each feature that has an 'orderings' method. If a feature has an 'orderings' method, it will be called with this env as sole argument. It should return a dictionary of {node: predecessors} where predecessors is a list of nodes that should be computed before the key node. """ if len(self.nodes) < 2: # optimization # when there are 0 or 1 nodes, no sorting is necessary # This special case happens a lot because the OpWiseCLinker produces # 1-element graphs. return list(self.nodes) env = self ords = self.orderings() order = graph.io_toposort(env.inputs, env.outputs, ords) return order
def __import__(self, node, check = True): # We import the nodes in topological order. We only are interested # in new nodes, so we use all variables we know of as if they were the input set. # (the functions in the graph module only use the input set to # know where to stop going down) new_nodes = graph.io_toposort(self.variables, node.outputs) if check: for node in new_nodes: if hasattr(node, 'env') and node.env is not self: raise Exception("%s is already owned by another env" % node) for r in node.inputs: if hasattr(r, 'env') and r.env is not self: raise Exception("%s is already owned by another env" % r) if r.owner is None and not isinstance(r, graph.Value) and r not in self.inputs: #Verbose error message #Show a complete chain of variables from the missing input to an output if config.exception_verbosity == 'high': def find_path_to(output_var, input_var): """ Returns a list of each variable on a (not necessarily unique) path from input_var to output_var, where each variable in the list has the preceding variable as one of its inputs. Returns None if no path exists""" #If output and input are the same we have a singleton path if output_var is input_var: return [output_var] #If output has no inputs then there is no path owner = output_var.owner if owner is None: return None #If input_var is an input to the output node, there is a #simple two element path inputs = owner.inputs if input_var in inputs: return [input_var, output_var] #Otherwise we must recurse by searching for a path to one #of our inputs, then appending the output to that path for ipt in inputs: path = find_path_to(ipt, input_var) if path is not None: path.append(output_var) return path #Since none of the above methods returned a path, there is none return None #Try different outputs until we find one that has a path to the missing input for output in self.outputs: path = find_path_to(output, r) if path is not None: break #if there is no path then r isn't really a graph input so we shouldn't be running error #handler code in the first place assert path is not None raise TypeError('A variable that is an input to the graph was neither provided as an ' 'input to the function nor given a value. A chain of variables leading from ' 'this input to an output is '+str(path)+'. This chain may not be unique') #Standard error message raise TypeError("An input of the graph, used to compute "+str(node)+", was not provided and not given a value", r) for node in new_nodes: assert node not in self.nodes self.__setup_node__(node) self.nodes.add(node) for output in node.outputs: self.__setup_r__(output) self.variables.add(output) for i, input in enumerate(node.inputs): if input not in self.variables: self.__setup_r__(input) self.variables.add(input) self.__add_clients__(input, [(node, i)]) assert node.env is self self.execute_callbacks('on_import', node)
def __import__(self, apply_node, check=True): node = apply_node # We import the nodes in topological order. We only are interested # in new nodes, so we use all variables we know of as if they were the input set. # (the functions in the graph module only use the input set to # know where to stop going down) new_nodes = graph.io_toposort(self.variables, node.outputs) if check: for node in new_nodes: if hasattr(node, 'fgraph') and node.fgraph is not self: raise Exception("%s is already owned by another fgraph" % node) for r in node.inputs: if hasattr(r, 'fgraph') and r.fgraph is not self: raise Exception( "%s is already owned by another fgraph" % r) if r.owner is None and not isinstance( r, graph.Constant) and r not in self.inputs: #Verbose error message #Show a complete chain of variables from the missing input to an output if config.exception_verbosity == 'high': def find_path_to(output_var, input_var): """ Returns a list of each variable on a (not necessarily unique) path from input_var to output_var, where each variable in the list has the preceding variable as one of its inputs. Returns None if no path exists""" #If output and input are the same we have a singleton path if output_var is input_var: return [output_var] #If output has no inputs then there is no path owner = output_var.owner if owner is None: return None #If input_var is an input to the output node, there is a #simple two element path inputs = owner.inputs if input_var in inputs: return [input_var, output_var] #Otherwise we must recurse by searching for a path to one #of our inputs, then appending the output to that path for ipt in inputs: path = find_path_to(ipt, input_var) if path is not None: path.append(output_var) return path #Since none of the above methods returned a path, there is none return None #Try different outputs until we find one that has a path to the missing input for output in self.outputs: path = find_path_to(output, r) if path is not None: break #if there is no path then r isn't really a graph input so we shouldn't be running error #handler code in the first place assert path is not None raise MissingInputError(( 'A variable that is an input to the graph was ' 'neither provided as an input to the function ' 'nor given a value. A chain of variables ' 'leading from this input to an output is %s. ' 'This chain may not be unique' % str(path))) #Standard error message raise MissingInputError( ("An input of the graph, used to compute %s, " "was not provided and not given a value" % str(node)), r) for node in new_nodes: assert node not in self.apply_nodes self.__setup_node__(node) self.apply_nodes.add(node) for output in node.outputs: self.__setup_r__(output) self.variables.add(output) for i, input in enumerate(node.inputs): if input not in self.variables: self.__setup_r__(input) self.variables.add(input) self.__add_clients__(input, [(node, i)]) assert node.fgraph is self self.execute_callbacks('on_import', node)
def _list_of_nodes(env): return list(graph.io_toposort(env.inputs, env.outputs))
def on_detach(self, fgraph): for node in graph.io_toposort(fgraph.inputs, fgraph.outputs): self.on_prune(fgraph, node)
def on_detach(self, env): for node in graph.io_toposort(env.inputs, env.outputs): self.on_prune(env, node)
def on_attach(self, env): for node in graph.io_toposort(env.inputs, env.outputs): self.on_import(env, node)
def on_attach(self, fgraph): for node in graph.io_toposort(fgraph.inputs, fgraph.outputs): self.on_import(fgraph, node)