1cef7893435aa41160dd1255c43cb8498279738ccChris Craik'''
2cef7893435aa41160dd1255c43cb8498279738ccChris Craikaltgraph.GraphUtil - Utility classes and functions
3cef7893435aa41160dd1255c43cb8498279738ccChris Craik==================================================
4cef7893435aa41160dd1255c43cb8498279738ccChris Craik'''
5cef7893435aa41160dd1255c43cb8498279738ccChris Craik
6cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport random
7cef7893435aa41160dd1255c43cb8498279738ccChris Craikfrom collections import deque
8cef7893435aa41160dd1255c43cb8498279738ccChris Craikfrom altgraph import Graph
9cef7893435aa41160dd1255c43cb8498279738ccChris Craikfrom altgraph import GraphError
10cef7893435aa41160dd1255c43cb8498279738ccChris Craik
11cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef generate_random_graph(node_num, edge_num, self_loops=False, multi_edges=False):
12cef7893435aa41160dd1255c43cb8498279738ccChris Craik    '''
13cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Generates and returns a :py:class:`~altgraph.Graph.Graph` instance with *node_num* nodes
14cef7893435aa41160dd1255c43cb8498279738ccChris Craik    randomly connected by *edge_num* edges.
15cef7893435aa41160dd1255c43cb8498279738ccChris Craik    '''
16cef7893435aa41160dd1255c43cb8498279738ccChris Craik    g = Graph.Graph()
17cef7893435aa41160dd1255c43cb8498279738ccChris Craik
18cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if not multi_edges:
19cef7893435aa41160dd1255c43cb8498279738ccChris Craik        if self_loops:
20cef7893435aa41160dd1255c43cb8498279738ccChris Craik            max_edges = node_num * node_num
21cef7893435aa41160dd1255c43cb8498279738ccChris Craik        else:
22cef7893435aa41160dd1255c43cb8498279738ccChris Craik            max_edges = node_num * (node_num-1)
23cef7893435aa41160dd1255c43cb8498279738ccChris Craik
24cef7893435aa41160dd1255c43cb8498279738ccChris Craik        if edge_num > max_edges:
25cef7893435aa41160dd1255c43cb8498279738ccChris Craik            raise GraphError("inconsistent arguments to 'generate_random_graph'")
26cef7893435aa41160dd1255c43cb8498279738ccChris Craik
27cef7893435aa41160dd1255c43cb8498279738ccChris Craik    nodes = range(node_num)
28cef7893435aa41160dd1255c43cb8498279738ccChris Craik
29cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for node in nodes:
30cef7893435aa41160dd1255c43cb8498279738ccChris Craik        g.add_node(node)
31cef7893435aa41160dd1255c43cb8498279738ccChris Craik
32cef7893435aa41160dd1255c43cb8498279738ccChris Craik    while 1:
33cef7893435aa41160dd1255c43cb8498279738ccChris Craik        head = random.choice(nodes)
34cef7893435aa41160dd1255c43cb8498279738ccChris Craik        tail = random.choice(nodes)
35cef7893435aa41160dd1255c43cb8498279738ccChris Craik
36cef7893435aa41160dd1255c43cb8498279738ccChris Craik        # loop defense
37cef7893435aa41160dd1255c43cb8498279738ccChris Craik        if head == tail and not self_loops:
38cef7893435aa41160dd1255c43cb8498279738ccChris Craik            continue
39cef7893435aa41160dd1255c43cb8498279738ccChris Craik
40cef7893435aa41160dd1255c43cb8498279738ccChris Craik        # multiple edge defense
41cef7893435aa41160dd1255c43cb8498279738ccChris Craik        if g.edge_by_node(head,tail) is not None and not multi_edges:
42cef7893435aa41160dd1255c43cb8498279738ccChris Craik            continue
43cef7893435aa41160dd1255c43cb8498279738ccChris Craik
44cef7893435aa41160dd1255c43cb8498279738ccChris Craik        # add the edge
45cef7893435aa41160dd1255c43cb8498279738ccChris Craik        g.add_edge(head, tail)
46cef7893435aa41160dd1255c43cb8498279738ccChris Craik        if g.number_of_edges() >= edge_num:
47cef7893435aa41160dd1255c43cb8498279738ccChris Craik            break
48cef7893435aa41160dd1255c43cb8498279738ccChris Craik
49cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return g
50cef7893435aa41160dd1255c43cb8498279738ccChris Craik
51cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef generate_scale_free_graph(steps, growth_num, self_loops=False, multi_edges=False):
52cef7893435aa41160dd1255c43cb8498279738ccChris Craik    '''
53cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Generates and returns a :py:class:`~altgraph.Graph.Graph` instance that will have *steps* \* *growth_num* nodes
54cef7893435aa41160dd1255c43cb8498279738ccChris Craik    and a scale free (powerlaw) connectivity. Starting with a fully connected graph with *growth_num* nodes
55cef7893435aa41160dd1255c43cb8498279738ccChris Craik    at every step *growth_num* nodes are added to the graph and are connected to existing nodes with
56cef7893435aa41160dd1255c43cb8498279738ccChris Craik    a probability proportional to the degree of these existing nodes.
57cef7893435aa41160dd1255c43cb8498279738ccChris Craik    '''
58cef7893435aa41160dd1255c43cb8498279738ccChris Craik    # FIXME: The code doesn't seem to do what the documentation claims.
59cef7893435aa41160dd1255c43cb8498279738ccChris Craik    graph = Graph.Graph()
60cef7893435aa41160dd1255c43cb8498279738ccChris Craik
61cef7893435aa41160dd1255c43cb8498279738ccChris Craik    # initialize the graph
62cef7893435aa41160dd1255c43cb8498279738ccChris Craik    store = []
63cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for i in range(growth_num):
64cef7893435aa41160dd1255c43cb8498279738ccChris Craik        #store   += [ i ] * (growth_num - 1)
65cef7893435aa41160dd1255c43cb8498279738ccChris Craik        for j in range(i + 1, growth_num):
66cef7893435aa41160dd1255c43cb8498279738ccChris Craik            store.append(i)
67cef7893435aa41160dd1255c43cb8498279738ccChris Craik            store.append(j)
68cef7893435aa41160dd1255c43cb8498279738ccChris Craik            graph.add_edge(i,j)
69cef7893435aa41160dd1255c43cb8498279738ccChris Craik
70cef7893435aa41160dd1255c43cb8498279738ccChris Craik    # generate
71cef7893435aa41160dd1255c43cb8498279738ccChris Craik    for node in range(growth_num, steps * growth_num):
72cef7893435aa41160dd1255c43cb8498279738ccChris Craik        graph.add_node(node)
73cef7893435aa41160dd1255c43cb8498279738ccChris Craik        while ( graph.out_degree(node) < growth_num ):
74cef7893435aa41160dd1255c43cb8498279738ccChris Craik            nbr = random.choice(store)
75cef7893435aa41160dd1255c43cb8498279738ccChris Craik
76cef7893435aa41160dd1255c43cb8498279738ccChris Craik            # loop defense
77cef7893435aa41160dd1255c43cb8498279738ccChris Craik            if node == nbr and not self_loops:
78cef7893435aa41160dd1255c43cb8498279738ccChris Craik                continue
79cef7893435aa41160dd1255c43cb8498279738ccChris Craik
80cef7893435aa41160dd1255c43cb8498279738ccChris Craik            # multi edge defense
81cef7893435aa41160dd1255c43cb8498279738ccChris Craik            if graph.edge_by_node(node, nbr) and not multi_edges:
82cef7893435aa41160dd1255c43cb8498279738ccChris Craik                continue
83cef7893435aa41160dd1255c43cb8498279738ccChris Craik
84cef7893435aa41160dd1255c43cb8498279738ccChris Craik            graph.add_edge(node, nbr)
85cef7893435aa41160dd1255c43cb8498279738ccChris Craik
86cef7893435aa41160dd1255c43cb8498279738ccChris Craik
87cef7893435aa41160dd1255c43cb8498279738ccChris Craik        for nbr in graph.out_nbrs(node):
88cef7893435aa41160dd1255c43cb8498279738ccChris Craik            store.append(node)
89cef7893435aa41160dd1255c43cb8498279738ccChris Craik            store.append(nbr)
90cef7893435aa41160dd1255c43cb8498279738ccChris Craik
91cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return graph
92cef7893435aa41160dd1255c43cb8498279738ccChris Craik
93cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef filter_stack(graph, head, filters):
94cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
95cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Perform a walk in a depth-first order starting
96cef7893435aa41160dd1255c43cb8498279738ccChris Craik    at *head*.
97cef7893435aa41160dd1255c43cb8498279738ccChris Craik
98cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Returns (visited, removes, orphans).
99cef7893435aa41160dd1255c43cb8498279738ccChris Craik
100cef7893435aa41160dd1255c43cb8498279738ccChris Craik    * visited: the set of visited nodes
101cef7893435aa41160dd1255c43cb8498279738ccChris Craik    * removes: the list of nodes where the node
102cef7893435aa41160dd1255c43cb8498279738ccChris Craik      data does not all *filters*
103cef7893435aa41160dd1255c43cb8498279738ccChris Craik    * orphans: tuples of (last_good, node),
104cef7893435aa41160dd1255c43cb8498279738ccChris Craik      where node is not in removes, is directly
105cef7893435aa41160dd1255c43cb8498279738ccChris Craik      reachable from a node in *removes* and
106cef7893435aa41160dd1255c43cb8498279738ccChris Craik      *last_good* is the closest upstream node that is not
107cef7893435aa41160dd1255c43cb8498279738ccChris Craik      in *removes*.
108cef7893435aa41160dd1255c43cb8498279738ccChris Craik    """
109cef7893435aa41160dd1255c43cb8498279738ccChris Craik
110cef7893435aa41160dd1255c43cb8498279738ccChris Craik    visited, removes, orphans = set([head]), set(), set()
111cef7893435aa41160dd1255c43cb8498279738ccChris Craik    stack = deque([(head, head)])
112cef7893435aa41160dd1255c43cb8498279738ccChris Craik    get_data = graph.node_data
113cef7893435aa41160dd1255c43cb8498279738ccChris Craik    get_edges = graph.out_edges
114cef7893435aa41160dd1255c43cb8498279738ccChris Craik    get_tail = graph.tail
115cef7893435aa41160dd1255c43cb8498279738ccChris Craik
116cef7893435aa41160dd1255c43cb8498279738ccChris Craik    while stack:
117cef7893435aa41160dd1255c43cb8498279738ccChris Craik        last_good, node = stack.pop()
118cef7893435aa41160dd1255c43cb8498279738ccChris Craik        data = get_data(node)
119cef7893435aa41160dd1255c43cb8498279738ccChris Craik        if data is not None:
120cef7893435aa41160dd1255c43cb8498279738ccChris Craik            for filtfunc in filters:
121cef7893435aa41160dd1255c43cb8498279738ccChris Craik                if not filtfunc(data):
122cef7893435aa41160dd1255c43cb8498279738ccChris Craik                    removes.add(node)
123cef7893435aa41160dd1255c43cb8498279738ccChris Craik                    break
124cef7893435aa41160dd1255c43cb8498279738ccChris Craik            else:
125cef7893435aa41160dd1255c43cb8498279738ccChris Craik                last_good = node
126cef7893435aa41160dd1255c43cb8498279738ccChris Craik        for edge in get_edges(node):
127cef7893435aa41160dd1255c43cb8498279738ccChris Craik            tail = get_tail(edge)
128cef7893435aa41160dd1255c43cb8498279738ccChris Craik            if last_good is not node:
129cef7893435aa41160dd1255c43cb8498279738ccChris Craik                orphans.add((last_good, tail))
130cef7893435aa41160dd1255c43cb8498279738ccChris Craik            if tail not in visited:
131cef7893435aa41160dd1255c43cb8498279738ccChris Craik                visited.add(tail)
132cef7893435aa41160dd1255c43cb8498279738ccChris Craik                stack.append((last_good, tail))
133cef7893435aa41160dd1255c43cb8498279738ccChris Craik
134cef7893435aa41160dd1255c43cb8498279738ccChris Craik    orphans = [(last_good, tail) for (last_good, tail) in orphans if tail not in removes]
135cef7893435aa41160dd1255c43cb8498279738ccChris Craik    #orphans.sort()
136cef7893435aa41160dd1255c43cb8498279738ccChris Craik
137cef7893435aa41160dd1255c43cb8498279738ccChris Craik    return visited, removes, orphans
138