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