def validate(self, fgraph): """Return None Raise InconsistencyError when a) orderings() raises an error b) orderings cannot be topologically sorted. """ if self.destroyers: ords = self.orderings(fgraph) if _contains_cycle(fgraph, ords): raise InconsistencyError("Dependency graph contains cycles") else: #James's Conjecture: #If there are no destructive ops, then there can be no cycles. #FB: This isn't always True. It can happend that #optimization introduce node that depend on itself. This #is very rare and should not happen in general. It will be #caught later. The error will be far from the source. But #doing this conjecture should speed up compilation most of #the time. The user should create such dependency except #if he mess too much with the internal. pass return True
def _build_droot_impact(self): droot = {} # destroyed view + nonview variables -> foundation impact = {} # destroyed nonview variable -> it + all views of it root_destroyer = {} # root -> destroyer apply for app in self.destroyers: for output_idx, input_idx_list in app.op.destroy_map.items(): if len(input_idx_list) != 1: raise NotImplementedError() input_idx = input_idx_list[0] input = app.inputs[input_idx] input_root = getroot(input, self.view_i) if input_root in droot: raise InconsistencyError("Multiple destroyers of %s" % input_root) droot[input_root] = input_root root_destroyer[input_root] = app #input_impact = set([input_root]) #add_impact(input_root, self.view_o, input_impact) input_impact = get_impact(input_root, self.view_o) for v in input_impact: assert v not in droot droot[v] = input_root impact[input_root] = input_impact impact[input_root].add(input_root) return droot, impact, root_destroyer
def refresh_droot_impact(self): """ Makes sure self.droot, self.impact, and self.root_destroyer are up to date, and returns them. (see docstrings for these properties above) """ if self.stale_droot: droot = OrderedDict( ) # destroyed view + nonview variables -> foundation impact = OrderedDict( ) # destroyed nonview variable -> it + all views of it root_destroyer = OrderedDict() # root -> destroyer apply for app in self.destroyers: for output_idx, input_idx_list in app.op.destroy_map.items(): if len(input_idx_list) != 1: raise NotImplementedError() input_idx = input_idx_list[0] input = app.inputs[input_idx] input_root = getroot(input, self.view_i) if input_root in droot: raise InconsistencyError("Multiple destroyers of %s" % input_root) droot[input_root] = input_root root_destroyer[input_root] = app input_impact = get_impact(input_root, self.view_o) for v in input_impact: assert v not in droot droot[v] = input_root impact[input_root] = input_impact impact[input_root].add(input_root) self.droot, self.impact, self.root_destroyer = droot, impact, root_destroyer self.stale_droot = False return self.droot, self.impact, self.root_destroyer
def validate(self, fgraph): """Return None Raise InconsistencyError when a) orderings() raises an error b) orderings cannot be topologically sorted. """ if self.destroyers: ords = self.orderings(fgraph) if _contains_cycle(fgraph, ords): raise InconsistencyError("Dependency graph contains cycles") else: #James's Conjecture: #If there are no destructive ops, then there can be no cycles. pass return True
def orderings(self, fgraph): """Return orderings induced by destructive operations. Raise InconsistencyError when a) attempting to destroy indestructable variable, or b) attempting to destroy a value multiple times, or c) an Apply destroys (illegally) one of its own inputs by aliasing """ rval = OrderedDict() if self.destroyers: # BUILD DATA STRUCTURES # CHECK for multiple destructions during construction of variables droot, impact, __ignore = self.refresh_droot_impact() # check for destruction of constants illegal_destroy = [r for r in droot if \ getattr(r.tag,'indestructible', False) or \ isinstance(r, graph.Constant)] if illegal_destroy: raise InconsistencyError( "Attempting to destroy indestructible variables: %s" % illegal_destroy) # add destroyed variable clients as computational dependencies for app in self.destroyers: # for each destroyed input... for output_idx, input_idx_list in app.op.destroy_map.items(): destroyed_idx = input_idx_list[0] destroyed_variable = app.inputs[destroyed_idx] root = droot[destroyed_variable] root_impact = impact[root] # we generally want to put all clients of things which depend on root # as pre-requisites of app. # But, app is itself one such client! # App will always be a client of the node we're destroying # (destroyed_variable, but the tricky thing is when it is also a client of # *another variable* viewing on the root. Generally this is illegal, (e.g., # add_inplace(x, x.T). In some special cases though, the in-place op will # actually be able to work properly with multiple destroyed inputs (e.g, # add_inplace(x, x). An Op that can still work in this case should declare # so via the 'destroyhandler_tolerate_same' attribute or # 'destroyhandler_tolerate_aliased' attribute. # # destroyhandler_tolerate_same should be a list of pairs of the form # [(idx0, idx1), (idx0, idx2), ...] # The first element of each pair is the input index of a destroyed # variable. # The second element of each pair is the index of a different input where # we will permit exactly the same variable to appear. # For example, add_inplace.tolerate_same might be [(0,1)] if the destroyed # input is also allowed to appear as the second argument. # # destroyhandler_tolerate_aliased is the same sort of list of # pairs. # op.destroyhandler_tolerate_aliased = [(idx0, idx1)] tells the # destroyhandler to IGNORE an aliasing between a destroyed # input idx0 and another input idx1. # This is generally a bad idea, but it is safe in some # cases, such as # - the op reads from the aliased idx1 before modifying idx0 # - the idx0 and idx1 are guaranteed not to overlap (e.g. # they are pointed at different rows of a matrix). # #CHECK FOR INPUT ALIASING # OPT: pre-compute this on import tolerate_same = getattr(app.op, 'destroyhandler_tolerate_same', []) assert isinstance(tolerate_same, list) tolerated = OrderedSet(idx1 for idx0, idx1 in tolerate_same if idx0 == destroyed_idx) tolerated.add(destroyed_idx) tolerate_aliased = getattr( app.op, 'destroyhandler_tolerate_aliased', []) assert isinstance(tolerate_aliased, list) ignored = OrderedSet(idx1 for idx0, idx1 in tolerate_aliased if idx0 == destroyed_idx) #print 'tolerated', tolerated #print 'ignored', ignored for i, input in enumerate(app.inputs): if i in ignored: continue if input in root_impact \ and (i not in tolerated or input is not destroyed_variable): raise InconsistencyError( "Input aliasing: %s (%i, %i)" % (app, destroyed_idx, i)) # add the rule: app must be preceded by all other Apply instances that # depend on destroyed_input root_clients = OrderedSet() for r in root_impact: assert not [ a for a, c in self.clients[r].items() if not c ] root_clients.update( [a for a, c in self.clients[r].items() if c]) root_clients.remove(app) if root_clients: rval[app] = root_clients return rval