~ubuntu-branches/ubuntu/trusty/python-igraph/trusty

« back to all changes in this revision

Viewing changes to igraph/formula.py

  • Committer: Package Import Robot
  • Author(s): TANIGUCHI Takaki
  • Date: 2012-03-17 17:23:55 UTC
  • Revision ID: package-import@ubuntu.com-20120317172355-e9iki37igmxnlq38
Tags: upstream-0.5.4
ImportĀ upstreamĀ versionĀ 0.5.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
"""
 
3
Implementation of `igraph.Graph.Formula()`
 
4
 
 
5
You should use this module directly only if you have a very strong reason
 
6
to do so. In almost all cases, you are better off with calling
 
7
`igraph.Graph.Formula()`.
 
8
"""
 
9
 
 
10
from cStringIO import StringIO
 
11
from igraph.datatypes import UniqueIdGenerator
 
12
 
 
13
import tokenize
 
14
import token
 
15
 
 
16
__all__ = ["Formula"]
 
17
 
 
18
def generate_edges(formula):
 
19
    """Parses an edge specification from the head of the given
 
20
    formula part and yields the following:
 
21
    
 
22
      - startpoint(s) of the edge by vertex names
 
23
      - endpoint(s) of the edge by names or an empty list if the vertices are isolated
 
24
      - a pair of bools to denote whether we had arrowheads at the start and end vertices 
 
25
    """
 
26
    if formula == "":
 
27
        yield [], [""], [False, False]
 
28
        return
 
29
 
 
30
    name_tokens = set([token.NAME, token.NUMBER, token.STRING])
 
31
    edge_chars = "<>-+"
 
32
    start_names, end_names, arrowheads = [], [], [False, False]
 
33
    parsing_vertices = True
 
34
 
 
35
    # Tokenize the formula
 
36
    token_gen = tokenize.generate_tokens(StringIO(formula).next)
 
37
    for token_type, tok, (_, start_char), (_, end_char), _ in token_gen:
 
38
        # Do the state transitions
 
39
        if parsing_vertices:
 
40
            if tok in edge_chars and token_type == token.OP:
 
41
                parsing_vertices = False
 
42
                # Check the edge we currently have
 
43
                if start_names and end_names:
 
44
                    # We have a whole edge
 
45
                    yield start_names, end_names, arrowheads
 
46
                start_names, end_names, arrowheads = end_names, [], [False, False]
 
47
        else:
 
48
            if tok not in edge_chars:
 
49
                parsing_vertices = True
 
50
 
 
51
        if parsing_vertices:
 
52
            # We are parsing vertex names at the moment
 
53
            if token_type in name_tokens:
 
54
                # We found a vertex name
 
55
                if token_type == token.STRING:
 
56
                    end_names.append(eval(tok))
 
57
                else:
 
58
                    end_names.append(str(tok))
 
59
            elif tok == ":" and token_type == token.OP:
 
60
                # Separating semicolon between vertex names, we just go on
 
61
                continue
 
62
            elif token_type == token.ENDMARKER:
 
63
                # End markers are fine
 
64
                pass
 
65
            else:
 
66
                raise SyntaxError, "invalid token found in edge specification: %s" % formula
 
67
        else:
 
68
            # We are parsing an edge operator
 
69
            if   tok == "<":  arrowheads[0] = True
 
70
            elif tok == ">":  arrowheads[1] = True
 
71
            elif tok == "<>": arrowheads = [True, True]
 
72
            elif tok == "+":
 
73
                pass   # TODO!
 
74
 
 
75
    # The final edge
 
76
    yield start_names, end_names, arrowheads
 
77
 
 
78
 
 
79
def Formula(klass, formula = None, attr = "name"):
 
80
    """Graph.Formula(formula = None, attr = "name")
 
81
    
 
82
    Generates a graph from a graph formula
 
83
 
 
84
    A graph formula is a simple string representation of a graph.
 
85
    It is very handy for creating small graphs quickly. The string
 
86
    consists of vertex names separated by edge operators. An edge
 
87
    operator is a sequence of dashes (C{-}) that may or may not
 
88
    start with an arrowhead (C{<} at the beginning of the sequence
 
89
    or C{>} at the end of the sequence). The edge operators can
 
90
    be arbitrarily long, i.e., you may use as many dashes to draw
 
91
    them as you like. This makes a total of four different edge
 
92
    operators:
 
93
 
 
94
      - C{-----} makes an undirected edge
 
95
      - C{<----} makes a directed edge pointing from the vertex
 
96
        on the right hand side of the operator to the vertex on
 
97
        the left hand side
 
98
      - C{---->} is the opposite of C{<----}
 
99
      - C{<--->} creates a mutual directed edge pair between
 
100
        the two vertices
 
101
 
 
102
    If you only use the undirected edge operator (C{-----}),
 
103
    the graph will be undirected. Otherwise it will be directed.
 
104
    Vertex names used in the formula will be assigned to the
 
105
    C{name} vertex attribute of the graph.
 
106
 
 
107
    Some simple examples:
 
108
 
 
109
      >>> print Graph.Formula()           # empty graph
 
110
      Undirected graph (|V| = 0, |E| = 0)
 
111
      >>> g = Graph.Formula("A-B")        # undirected graph
 
112
      >>> g.vs["name"]
 
113
      ["A", "B"]
 
114
      >>> print g
 
115
      Undirected graph (|V| = 2, |E| = 1)
 
116
      >>> g.get_edgelist()
 
117
      >>> g2 = Graph.Formula("A-----------B")
 
118
      >>> g2.isomorphic(g)
 
119
      True
 
120
      >>> g = Graph.Formula("A  --->  B") # directed graph
 
121
      >>> g.vs["name"]
 
122
      ["A", "B"]
 
123
      >>> print g
 
124
      Directed graph (|V| = 2, |E| = 1)
 
125
      
 
126
    If you have may disconnected componnets, you can separate them
 
127
    with commas. You can also specify isolated vertices:
 
128
 
 
129
      >>> g = Graph.Formula("A--B, C--D, E--F, G--H, I, J, K")
 
130
      >>> print ", ".join(g.vs["name"])
 
131
      A, B, C, D, E, F, G, H, I, J, K
 
132
      >>> g.clusters().membership
 
133
      [0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 6]
 
134
 
 
135
    The colon (C{:}) operator can be used to specify vertex sets.
 
136
    If an edge operator connects two vertex sets, then every vertex
 
137
    from the first vertex set will be connected to every vertex in
 
138
    the second set:
 
139
 
 
140
      >>> g = Graph.Formula("A:B:C:D --- E:F:G")
 
141
      >>> g.isomorphic(Graph.Full_Bipartite(4, 3))
 
142
      True
 
143
 
 
144
    Note that you have to quote vertex names if they include spaces
 
145
    or special characters:
 
146
 
 
147
      >>> g = Graph.Formula('"this is" +- "a silly" -+ "graph here"')
 
148
      >>> g.vs["name"]
 
149
      ['this is', 'a silly', 'graph here']
 
150
 
 
151
    @param formula: the formula itself
 
152
    @param attr: name of the vertex attribute where the vertex names
 
153
                 will be stored
 
154
    @return: the constructed graph:
 
155
    """
 
156
    
 
157
    # If we have no formula, return an empty graph
 
158
    if formula is None: return klass(0, vertex_attrs = {attr: []})
 
159
 
 
160
    vertex_ids, edges, directed = UniqueIdGenerator(), [], False
 
161
    # Loop over each part in the formula
 
162
    for part in formula.split(","):
 
163
        # Drop newlines from the part
 
164
        part = part.strip().replace("\n", "").replace("\t", "")
 
165
        # Parse the first vertex specification from the formula
 
166
        for start_names, end_names, arrowheads in generate_edges(part):
 
167
            start_ids = [vertex_ids[name] for name in start_names]
 
168
            end_ids   = [vertex_ids[name] for name in end_names]
 
169
            if not arrowheads[0] and not arrowheads[1]:
 
170
                # This is an undirected edge. Do we have a directed graph?
 
171
                if not directed:
 
172
                    # Nope, add the edge
 
173
                    edges.extend([(id1, id2) for id1 in start_ids for id2 in end_ids])
 
174
            else:
 
175
                # This is a directed edge
 
176
                directed = True
 
177
                if arrowheads[1]:
 
178
                    edges.extend([(id1, id2) for id1 in start_ids for id2 in end_ids])
 
179
                if arrowheads[0]:
 
180
                    edges.extend([(id2, id1) for id1 in start_ids for id2 in end_ids])
 
181
 
 
182
    # Grab the vertex names into a list
 
183
    names = sorted(((v, k) for k, v in vertex_ids._ids.iteritems()))
 
184
    names = [k for _, k in names]
 
185
    # Construct and return the graph
 
186
    return klass(len(names), edges, directed, vertex_attrs={attr: names})