def forest_isomorphism(f1, f2): #TODO: do this at a higher level, since both iso testers need it if sum(1 for v in f1.vertices()) != sum(1 for v in f2.vertices()) or \ sum(1 for e in f1.edges()) != sum(1 for e in f2.edges()): return None results = [] for forest in (f1,f2): generator = depth_first_generator(forest) visitor = AggregateVisitor(backend=forest, visitors={Depth:None}) traverse(root_vertices=graph.vertices(), visitor=visitor, generator=depth_first_generator(forest)) results.append(visitor.Depth) f1_depth, f2_depth = results f1_by_depths = ydict(backend=f1) f2_by_depths = ydict(backend=f2) max_depth = 0 for forest, depth, index_by_depths in [(f1, f1_depth, f1_by_depths), (f2, f2_depth, f2_by_depths)]: for v in forest.vertices(): d = depth[v] max_depth = max(d, max_depth) index_by_depths[d] = index_by_depths.setdefault(d, ydeque(backend=forest)).append(v) prev_f1_canonical_id = ydict(backend=f1) prev_f2_canonical_id = ydict(backend=f2) if len(f1_by_depths[max_depth]) != len(f2_by_depths[max_depth]): return for v in f1_by_depths[max_depth]: prev_f1_canonical_id[v] = 0 for v in f2_by_depths[max_depth]: prev_f2_canonical_id[v] = 0 for depth in xrange(max_depth - 1, -1, -1): f1_vertex_id = ydict(backend=f1) f2_vertex_id = ydict(backend=f2) if len(f1_by_depths[depth]) != len(f2_by_depths[depth]): return # don't do two-phase canonical id -> id transformation. instead, compute canonical ids # on the fly by storing a lexicographically sorted list of ids and binary searching for # existing ids each time for v in f1_by_depths[depth]: f1_vertex_id[v] = ydeque(backend=f2_vertex_id, ysorted(backend=f2_vertex_id, (prev_f1_canonical_id[w] for u,w in f1.edges(source=v)))) for v in f2_by_depths[depth]: f2_vertex_id[v] = ydeque(backend=f2_vertex_id, ysorted(backend=f2_vertex_id, (prev_f2_canonical_id[w] for u,w in f2.edges(source=v))))
def isomorphism(g1, g2): if sum(1 for v in g1.vertices()) != sum(1 for v in g2.vertices()) or \ sum(1 for e in g1.edges()) != sum(1 for e in g2.edges()): return None test_funcs = [partial(reachability_signature, reach=1), partial(reachability_signature, reach=2), partial(reachability_signature, reach=4)] g1cf = canonical_form(g1, test_funcs) g2cf = canonical_form(g2, test_funcs) sorted_g1cf = ysorted(g1, g1cf.itervalues()) sorted_g2cf = ysorted(g2, g2cf.itervalues()) for x,y in izip(sorted_g1cf, sorted_g2cf): if x != y: return None g2_cform_inv = function_inverse(g2cf, g2) iso = ydict(backend=g1) contexts = ydeque(backend=g1) for v in g1.vertices(): g2_inv_image = ydeque(backend=contexts) for image_element in g2_cform_inv[g1cf[v]]: g2_inv_image.append(image_element) contexts.append((v, g2_inv_image)) index = 0 while index >= 0 and index < len(contexts): vertex = contexts[index][0] if contexts[index][1]: v_prime = contexts[index][1].popleft() iso[vertex] = v_prime if is_a_partial_isomorphism(g1, g2, iso): index += 1 else: if vertex in iso: del iso[vertex] g2_inv_image = ydeque(backend=contexts) for image_element in g2_cform_inv[g1cf[vertex]]: g2_inv_image.append(image_element) (index, vertex, str([x for x in g2_inv_image])) contexts[index] = (vertex, g2_inv_image) index -= 1 if index < 0: return None else: return iso
def make_tree(graph, source, edge_weight = None): parent = ydict(backend=graph) distance = ydict(backend=graph) queue = ydeque(backend=graph) queue_members = ydict(backend=graph) queue.append(source) queue_members[source] = True distance[source] = 0 curr_pass = 0 curr_last = source num_vertices = sum(1 for v in graph.vertices()) while queue: v = queue.popleft() del queue_members[v] for e in graph.edges(source = v): w = other_vertex(e, v) dw, dv, e_weight = distance.get(w), distance[v], edge_weight[e] if dw is None or dv + e_weight < dw: distance[w] = dv + e_weight parent[w] = e if w not in queue_members: queue.append(w) queue_members[w] = True if v == curr_last and queue: curr_pass += 1 curr_last = queue[-1] if curr_pass >= num_vertices: raise NegativeCycleError return parent
def __init__(self, graph): self.description = 'depth-first search topological sort' finished_vertices = ydict(backend=graph) generator = depth_first_generator(graph) # Bootstrap the depth-first search generator with all vertices # of indegree 0. If there are no such vertices, raise an exception. found_starting_vertex = False for v in graph.vertices(): try: graph.edges(target=v).next() except StopIteration: generator.bootstrap(v) found_starting_vertex = True if not found_starting_vertex: raise NoIndegreeZeroVertices # Topological ordering is now the reverse of the postorder. But # a topological ordering still might not be possible if there are # cycles, so we'll check for those here and raise an exception # if there is one. self.topological_order = ydeque(backend=graph) # TODO: don't use the generator directly here, use a visitor for e in generator.events(): if e['type'] == 'VERTEX' and e['state'] == 'FINISHED': self.topological_order.append(e['vertex']) elif e['type'] == 'EDGE' and e['target_state'] == 'DISCOVERED': raise CycleExists
def reachability_signature(g, reach): sig = ydict(backend=g) for v in g.vertices(): sig[v] = ydeque(sig, [1]) for i in range(reach): for u in g.vertices(): val = sum(sig[v][i] for x,v in g.edges(source=u)) sig[u].append(val) return ydict(g, ((k,v[-1]) for k,v in sig.iteritems()))
def function_inverse(f, backend): inverse = ydict(backend=backend) for key, value in f.iteritems(): inverse.setdefault(value,ydeque(backend=inverse)).append(key) return inverse
def __init__(self, graph): base_generator.__init__(self, graph) self.__deque = ydeque(backend=graph)
def __init__(self, agg): CompositeVisitor.__init__(self, agg) self.S = ydeque(backend=agg) self.P = ydeque(backend=agg) self.next_component_id = 0