def graph_map(f, graph): ''' Maps function f over the nodes in graph. >>> sorted( graph_map(str, Graph({1: [2, 3]})).edges() ) [('1', '2'), ('1', '3')] >>> sorted( graph_map(str, DiGraph({1: [2, 3]})).edges() ) [('1', '2'), ('1', '3')] ''' g = graph return g.__class__(map_items(lambda k,v: (f(k), map_keys(f, v)), g.adj))
def graph_map(f, graph): ''' Maps function f over the nodes in graph. >>> sorted( graph_map(str, Graph({1: [2, 3]})).edges() ) [('1', '2'), ('1', '3')] >>> sorted( graph_map(str, DiGraph({1: [2, 3]})).edges() ) [('1', '2'), ('1', '3')] ''' g = graph return g.__class__(map_items(lambda k, v: (f(k), map_keys(f, v)), g.adj))
def transformAssign(self, node): # Only transform constant assignments if not is_const(node.expr): return self._transform_children(node) # '__foo' names come from each lhs name at the head of the assignment: # 'l = a,b = 0,1' -> 'l = a,b = __a,__b', { '__a':0, '__b':1 } # # Unpack the rhs enough to map each '__foo' name to a value. # 'a,l = 0,[1,2]' -> 'a,l = __a,__l', { '__a':0, '__l':[1,2] } # 'a,l = 2' -> SyntaxError def lvalue_name(node): 'Construct a "magic" name to represent an l-value.' prefix = sep = '__' dot = '_' if isinstance(node, AssName): return prefix + node.name elif isinstance(node, AssAttr): name = node.attrname expr = node.expr while isinstance(expr, Getattr): name = sep.join([expr.attrname, name]) expr = expr.expr if isinstance(expr, Name): expr_name = expr.name else: expr_name = dot return prefix + sep.join([expr_name, name]) # In these trees, strings and tuples are leaves leaves = (str, tuple, AssAttr, AssName) tree_zip = partial(tree.tree_zip, leaves=leaves) flatten = partial(tree.flatten, leaves=leaves) tree_embeds = partial(tree.tree_embeds, leaves=leaves) # Grab the (right-most) lhs and the rhs lhs, rhs = node.nodes[-1], node.expr # Associate constants with l-value names if not tree_embeds(lhs, rhs): raise SyntaxError('Not enough r-values to unpack: %s' % node) zipped = flatten(tree_zip(lhs, rhs)) const_ast_for = map_keys(lambda v: lvalue_name(v), dict(zipped)) # Gather name<->const mappings for names we haven't seen before name_for = {} for name in const_ast_for.keys(): if name not in self.const_for.keys(): self.const_for[name] = eval_ast(Expression(const_ast_for[name])) assert const_ast_for[name] not in name_for name_for[const_ast_for[name]] = name class C(Transformer): def transform(self, node): if isinstance(node, Node) and node in name_for.keys(): return Name(name_for[node]) else: return super(C, self).transform(node) return Assign(node.nodes, C().transform(rhs))
def restrict(self, inputs=(), outputs=()): ''' The minimal sub-block that computes 'outputs' from 'inputs'. 'inputs' and 'outputs' must be subsets of 'self.inputs' and 'self.outputs', respectively. ''' # TODO Elaborate docstring inputs = set(inputs) outputs = set(outputs) # TODO Restrict recursively # Look for results in the cache cache_key = (frozenset(inputs), frozenset(outputs)) if cache_key in self.__restrictions: return self.__restrictions[cache_key] if not (inputs or outputs): raise ValueError('Must provide inputs or outputs') if not inputs.issubset(self.inputs): raise ValueError('Unknown inputs: %s' % (inputs - self.inputs)) if not outputs.issubset(self.all_outputs): raise ValueError('Unknown outputs: %s' %(outputs-self.all_outputs)) # If we don't decompose, then we are already as restricted as possible if self.sub_blocks is None: return self # We use the mock constructors `In` and `Out` to separate input and # output names in the dep graph in order to avoid cyclic graphs (in # case input and output names overlap) in_, out = object(), object() # (singletons) In = lambda x: (x, in_) Out = lambda x: (x, out) def wrap_names(wrap): "Wrap names and leave everything else alone" def g(x): if isinstance(x, basestring): return wrap(x) else: return x return g g = self._dep_graph # Decorate input names with `In` and output names with `Out` so that # `g` isn't cyclic. (Whose responsibility is this?) g = g.__class__( map_keys(wrap_names(Out), map_values(lambda l: map(wrap_names(In), l), g.adj)) ) # Find the subgraph reachable from inputs, and then find its subgraph # reachable from outputs. (We could also flip the order.) if inputs: inputs = set(map(In, inputs)) g = reachable_graph(g.reverse(), *inputs).reverse() if outputs: outputs = set(map(Out, outputs)) g = reachable_graph(g, *outputs.intersection(g.nodes())) # Create a new block from the remaining sub-blocks (ordered input to # output, ignoring the variables at the ends) and give it our filename b = Block(node for node in reversed(topological_sort(g)) if isinstance(node, Block)) b.filename = self.filename # Cache result self.__restrictions[cache_key] = b return b
def transformAssign(self, node): # Only transform constant assignments if not is_const(node.expr): return self._transform_children(node) # '__foo' names come from each lhs name at the head of the assignment: # 'l = a,b = 0,1' -> 'l = a,b = __a,__b', { '__a':0, '__b':1 } # # Unpack the rhs enough to map each '__foo' name to a value. # 'a,l = 0,[1,2]' -> 'a,l = __a,__l', { '__a':0, '__l':[1,2] } # 'a,l = 2' -> SyntaxError def lvalue_name(node): 'Construct a "magic" name to represent an l-value.' prefix = sep = '__' dot = '_' if isinstance(node, AssName): return prefix + node.name elif isinstance(node, AssAttr): name = node.attrname expr = node.expr while isinstance(expr, Getattr): name = sep.join([expr.attrname, name]) expr = expr.expr if isinstance(expr, Name): expr_name = expr.name else: expr_name = dot return prefix + sep.join([expr_name, name]) # In these trees, strings and tuples are leaves leaves = (str, tuple, AssAttr, AssName) tree_zip = partial(tree.tree_zip, leaves=leaves) flatten = partial(tree.flatten, leaves=leaves) tree_embeds = partial(tree.tree_embeds, leaves=leaves) # Grab the (right-most) lhs and the rhs lhs, rhs = node.nodes[-1], node.expr # Associate constants with l-value names if not tree_embeds(lhs, rhs): raise SyntaxError('Not enough r-values to unpack: %s' % node) zipped = flatten(tree_zip(lhs, rhs)) const_ast_for = map_keys(lambda v: lvalue_name(v), dict(zipped)) # Gather name<->const mappings for names we haven't seen before name_for = {} for name in const_ast_for.keys(): if name not in self.const_for.keys(): self.const_for[name] = eval_ast(Expression( const_ast_for[name])) assert const_ast_for[name] not in name_for name_for[const_ast_for[name]] = name class C(Transformer): def transform(self, node): if isinstance(node, Node) and node in name_for.keys(): return Name(name_for[node]) else: return super(C, self).transform(node) return Assign(node.nodes, C().transform(rhs))