def _base(self, code, inputs, outputs, conditional_outputs, dep_graph): b = Block(code) # Compare inputs and outputs self.assertEqual(set(b.inputs), set(inputs)) self.assertEqual(set(b.outputs), set(outputs)) self.assertEqual(set(b.conditional_outputs), set(conditional_outputs)) # Compare dependency graphs: since the real dep_graphs contain crazy # block objects, the given dep_graph specifies them by their index in # the sequence, e.g. # for code # 'a=z; b=f(a,y)' # the given dep_graph should be # { '0':'z', # Command 0 depends on input z # '1':'0y', # Command 1 depends on command 0 and input y # 'a':'0', # Output a depends on command 0 # 'b':'1' } # Output b depends on command 1 # Resolve indices to blocks def num_to_block(x): '''If x is number-like, lookup the xth block in b.sub_blocks; otherwise, leave x unchanged.''' try: return b.sub_blocks[int(x)] except ValueError: # If int(x) fails return x except TypeError: # In case b doesn't decompose return b dep_graph = graph.map(num_to_block, dep_graph) # Hand-make a trivial dep graph if 'b' doesn't have sub-blocks if b.sub_blocks is not None: b_dep_graph = b._dep_graph else: b_dep_graph = {} if b.inputs: b_dep_graph[b] = b.inputs for o in b.all_outputs: b_dep_graph[o] = set([b]) # Compare dep_graphs self.assertEqual( map_values(set, b_dep_graph), map_values(set, dep_graph))
def _base(self, code, inputs, outputs, conditional_outputs, dep_graph): b = Block(code) # Compare inputs and outputs self.assertEqual(set(b.inputs), set(inputs)) self.assertEqual(set(b.outputs), set(outputs)) self.assertEqual(set(b.conditional_outputs), set(conditional_outputs)) # Compare dependency graphs: since the real dep_graphs contain crazy # block objects, the given dep_graph specifies them by their index in # the sequence, e.g. # for code # 'a=z; b=f(a,y)' # the given dep_graph should be # { '0':'z', # Command 0 depends on input z # '1':'0y', # Command 1 depends on command 0 and input y # 'a':'0', # Output a depends on command 0 # 'b':'1' } # Output b depends on command 1 # Resolve indices to blocks def num_to_block(x): '''If x is number-like, lookup the xth block in b.sub_blocks; otherwise, leave x unchanged.''' try: return b.sub_blocks[int(x)] except ValueError: # If int(x) fails return x except TypeError: # In case b doesn't decompose return b dep_graph = graph.map(num_to_block, dep_graph) # Hand-make a trivial dep graph if 'b' doesn't have sub-blocks if b.sub_blocks is not None: b_dep_graph = b._dep_graph else: b_dep_graph = {} if b.inputs: b_dep_graph[b] = b.inputs for o in b.all_outputs: b_dep_graph[o] = set([b]) # Compare dep_graphs self.assertEqual(map_values(set, b_dep_graph), map_values(set, dep_graph))
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