Example #1
0
def iterate(graph, wl_state, iteration, test_mode=False):
    '''Performs one iteration of the Weisfeiler-Lehman algorithm.
    :param graph: A Networkx graph or a Hypergraph
    :param wl_state: A dictionary containing 2 sub-dictionaries:
    "labels" contains a mapping from the full original graph labels or
    labels generated during the Weisfeiler & Lehman iterations to their
    corresponding WL short unique labels; "next_labels" contains the next label
    number for each WL iteration.
    :param iteration: The current iteration number.
    :return A tuple of the form (new_graph, new_labels_list), where
    new_graph is the resulting graph from the iteration, new_labels_list
    is the new list of labels for the current iteration of the algorithm.
    '''
    def get_new_label(node, neighbors):
        def get_direction(u, v):
            '''Get the direction of the edge between nodes u and v
            '''
            res = 0
            if type(graph) is not Hypergraph:
                if graph.has_edge(u, v) and graph.has_edge(v, u):
                    res = 0
                elif graph.has_edge(u, v):
                    res = 1
                elif graph.has_edge(v, u):
                    res = -1
                else:
                    raise Exception("There is no edge between {0} and {1}.".format(u, v))
            else:
                if u.startswith(u"e_"):
                    # u represents an edge -> all the neighbors are nodes
                    n = v
                    e = u
                    k = -1
                else:
                    # u represents a node -> all the neighbors are edges
                    n = u
                    e = v
                    k = 1
                dir_perms = graph.node[e]["direction"]
                dir_perm_0 = next(iter(dir_perms))
                if len(dir_perm_0) > 2:
                    raise Exception("Weisfeiler-Lehman is not implemented for hypergraphs with edges of order > 2.")
                if len(dir_perms) != 1:
                    res = 0
                elif dir_perm_0.index(n) == 0:
                    res = k
                elif dir_perm_0.index(n) == 1:
                    res = -k
                else:
                    raise Exception("Strange direction encoding of an edge. Are {0} and {1} connected?".format(u, v))
            return "out" if res > 0 else "in" if res < 0 else "any"
        
        node_label = graph.node[node]["labels"][0]
        label_extensions = {"any" : [], "in" : [], "out" : []}
        
        for neighbor in neighbors:
            neighbor_label = graph.node[neighbor]["labels"][0]
            direction = get_direction(node, neighbor)
            label_extensions[direction].append(neighbor_label)
        
        label_extension = []
        for direction in ["any", "in", "out"]:
            if label_extensions[direction]:
                label_extensions[direction].sort()
                label_extension.append("{0}({1})".format(direction, ",".join(label_extensions[direction])))
        label_extension = ",".join(label_extension)
        
        return "{0};{1}".format(node_label, label_extension)
    
    new_graph = graph.copy()
    
    if iteration not in wl_state["next_labels"]:
        wl_state["next_labels"][iteration] = 0
    
    if test_mode:
        nodes = sorted(graph.node, key=lambda n: graph.node[n]["labels"][0])
    else:
        nodes = graph.node
    
    for node in nodes:
        if type(graph) is not Hypergraph:
            neighbors = nxext.get_all_neighbors(graph, node)
        else:
            neighbors = graph.bipartite_graph.neighbors(node)
        new_node_label = get_new_label(node, neighbors)
        if new_node_label not in wl_state["labels"]:
                wl_state["labels"][new_node_label] = "wl_{0}.{1}".format(iteration, wl_state["next_labels"][iteration])
                wl_state["next_labels"][iteration] += 1
        new_graph.node[node]["labels"] = [wl_state["labels"][new_node_label]]
    
    return new_graph, wl_state
Example #2
0
def process_raw_feature(raw_feature, hypergraph, max_nodes=6):
    '''Turns a raw feature to a usable feature or a collection of features, depending on
    the type of the rule according to which the feature was reduced (fixed, pattern or dynamic).
    :param raw_feature: A ReducibleFeature extracted from Arnborg & Proskurowski
    :param hypergraph: A Hypergraph which contains the nodes of the raw feature.
    :param max_nodes: (default value 6) A number of nodes that a pattern or a dynamic feature can
    have before being disassembled in subfeatures of size max_nodes.
    :return A collection containing one or more features depending on the type of the raw feature.
    '''
    assert type(raw_feature) is ReducibleFeature
    assert type(hypergraph) is Hypergraph
    assert max_nodes > 3
    def get_feature_type(raw_feature):
        '''Get the type of the raw feature according to the rule it was reduced by.
        :param raw_feature: A ReducibleFeature.
        :return Type of the raw feature: 0 for "fixed", 1 for "pattern", 2 for "dynamic".
        '''
        rule = raw_feature.get_full_rule()
        if rule in ["2.1.0.0", "2.2.0.0", "4.2.0.0", "4.3.0.0", "5.2.3.1"]:
            return 1
        elif rule in ["5.2.2.0", "5.2.3.2", "5.2.4.0"]:
            return 2
        else:
            return 0
    
    def sliding_window(seq, window_size):
        # Returns a sliding window (of width window_size) over data from the iterable seq
        #   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...
        it = iter(seq)
        result = tuple(islice(it, window_size))
        if len(result) == window_size:
            yield result
        for elem in it:
            result = result[1:] + (elem,)
            yield result
    
    feature_type = get_feature_type(raw_feature)
    if feature_type == 1:
        # pattern
        nodes_count = raw_feature.number_of_nodes()
        if nodes_count > max_nodes:
            rule = raw_feature.get_full_rule()
            if rule == "2.1.0.0":
                # chain: extract all subpaths of length max_nodes using a sliding window
                s = raw_feature.peripheral_nodes[0]
                t = raw_feature.peripheral_nodes[1]
                path = [s] + raw_feature.reducible_nodes + [t]
                for subpath in sliding_window(path, max_nodes):
                    yield hypergraph.subgraph_with_labels(set(subpath))
                raise StopIteration
            elif rule == "2.2.0.0":
                # ring: extract all subpaths of length max_nodes using a sliding window
                cycle = raw_feature.peripheral_nodes + raw_feature.reducible_nodes
                for subpath in sliding_window(cycle + cycle[:max_nodes - 1], max_nodes):
                    yield hypergraph.subgraph_with_labels(set(subpath))
                raise StopIteration
            elif rule == "4.2.0.0":
                # buddy: If there are more than 3 buddies create a buddy
                # configuration with 3 buddies for each possible combination
                assert len(raw_feature.peripheral_nodes) == 3
                buddy_nodes = raw_feature.reducible_nodes
                for buddy_nodes_subgroup in itertools.combinations(buddy_nodes, max_nodes - 3):
                    yield hypergraph.subgraph_with_labels(set(buddy_nodes_subgroup) | set(raw_feature.peripheral_nodes))
                raise StopIteration
            elif rule == "4.3.0.0":
                # cube: similar approach as for wheel (5.2.3.1)
                if nodes_count > max_nodes + 1:
                    reducible_neigh = [set(hypergraph.neighbors(node)) for node in raw_feature.reducible_nodes]
                    hub_node = reduce(lambda a, b: a.intersection(b), reducible_neigh)
                    ring_subgraph = hypergraph.subgraph(raw_feature.reducible_nodes | (raw_feature.peripheral_nodes - hub_node))
                    ring = nx.cycle_basis(ring_subgraph)
                    if len(ring) > 0:
                        ring = ring[0]
                        for subpath in sliding_window(ring + ring[:max_nodes - 1], max_nodes):
                            yield hypergraph.subgraph_with_labels(set(subpath) | hub_node)
                        raise StopIteration
                    else:
                        # if there is no ring in the feature, treat it as a fixed feature
                        # TODO: This can lead to a large number of shingles. Better solution?
                        pass
            elif rule == "5.2.3.1":
                # wheel: extract all cake-slice subpaths of length max_node using a sliding window
                if nodes_count > max_nodes + 1:
                    ring_subgraph = hypergraph.subgraph(raw_feature.reducible_nodes)
                    ring = nx.cycle_basis(ring_subgraph)
                    if len(ring) > 0:
                        ring = ring[0]
                        for subpath in sliding_window(ring + ring[:max_nodes - 1], max_nodes):
                            yield hypergraph.subgraph_with_labels(set(subpath) | set(raw_feature.peripheral_nodes))
                        raise StopIteration
                    else:
                        # if there is no ring in the feature, treat it as a fixed feature
                        # TODO: This can lead to a large number of shingles. Better solution?
                        pass
    elif feature_type == 2:
        # dynamic (the reducible nodes are always of degree 3):
        # for each pair of adjacent nodes u, v let a new feature be
        # the subgraph that contains u, v and all neighbors of u and v.
        nodes_count = raw_feature.number_of_nodes()
        if nodes_count > max_nodes:
            nodes = set(raw_feature.reducible_nodes) | set(raw_feature.peripheral_nodes)
            feature_graph = hypergraph.subgraph(nodes)
            adj_nodes = nxext.get_all_adjacent_nodes(feature_graph)
            neighbors = {node: nxext.get_all_neighbors(feature_graph, node) for node in nodes}
            for u, v in adj_nodes:
                node_subgroup = set([u, v] + neighbors[u] + neighbors[v])
                yield hypergraph.subgraph_with_labels(set(node_subgroup))
            raise StopIteration
    
    # fixed or pattern/dynamic with <= max_nodes number of nodes
    yield raw_feature.as_subgraph(hypergraph)