author shellac Mon, 22 Mar 2021 18:12:50 +0000
comparison
equal inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """
2 *****
3 Pajek
4 *****
5 Read graphs in Pajek format.
6
7 This implementation handles directed and undirected graphs including
8 those with self loops and parallel edges.
9
10 Format
11 ------
13 for format information.
14
15 """
16
17 import warnings
18
19 import networkx as nx
20 from networkx.utils import open_file
21
22 __all__ = ["read_pajek", "parse_pajek", "generate_pajek", "write_pajek"]
23
24
25 def generate_pajek(G):
26 """Generate lines in Pajek graph format.
27
28 Parameters
29 ----------
30 G : graph
31 A Networkx graph
32
33 References
34 ----------
36 for format information.
37 """
38 if G.name == "":
39 name = "NetworkX"
40 else:
41 name = G.name
42 # Apparently many Pajek format readers can't process this line
43 # So we'll leave it out for now.
44 # yield '*network %s'%name
45
46 # write nodes with attributes
47 yield f"*vertices {G.order()}"
48 nodes = list(G)
49 # make dictionary mapping nodes to integers
50 nodenumber = dict(zip(nodes, range(1, len(nodes) + 1)))
51 for n in nodes:
52 # copy node attributes and pop mandatory attributes
53 # to avoid duplication.
54 na = G.nodes.get(n, {}).copy()
55 x = na.pop("x", 0.0)
56 y = na.pop("y", 0.0)
57 try:
58 id = int(na.pop("id", nodenumber[n]))
59 except ValueError as e:
60 e.args += (
61 (
62 "Pajek format requires 'id' to be an int()."
63 " Refer to the 'Relabeling nodes' section."
64 ),
65 )
66 raise
67 nodenumber[n] = id
68 shape = na.pop("shape", "ellipse")
69 s = " ".join(map(make_qstr, (id, n, x, y, shape)))
70 # only optional attributes are left in na.
71 for k, v in na.items():
72 if isinstance(v, str) and v.strip() != "":
73 s += f" {make_qstr(k)} {make_qstr(v)}"
74 else:
75 warnings.warn(
76 f"Node attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}."
77 )
78 yield s
79
80 # write edges with attributes
81 if G.is_directed():
82 yield "*arcs"
83 else:
84 yield "*edges"
85 for u, v, edgedata in G.edges(data=True):
86 d = edgedata.copy()
87 value = d.pop("weight", 1.0) # use 1 as default edge value
88 s = " ".join(map(make_qstr, (nodenumber[u], nodenumber[v], value)))
89 for k, v in d.items():
90 if isinstance(v, str) and v.strip() != "":
91 s += f" {make_qstr(k)} {make_qstr(v)}"
92 else:
93 warnings.warn(
94 f"Edge attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}."
95 )
96 yield s
97
98
99 @open_file(1, mode="wb")
100 def write_pajek(G, path, encoding="UTF-8"):
101 """Write graph in Pajek format to path.
102
103 Parameters
104 ----------
105 G : graph
106 A Networkx graph
107 path : file or string
108 File or filename to write.
109 Filenames ending in .gz or .bz2 will be compressed.
110
111 Examples
112 --------
113 >>> G = nx.path_graph(4)
114 >>> nx.write_pajek(G, "test.net")
115
116 Warnings
117 --------
118 Optional node attributes and edge attributes must be non-empty strings.
119 Otherwise it will not be written into the file. You will need to
120 convert those attributes to strings if you want to keep them.
121
122 References
123 ----------
125 for format information.
126 """
127 for line in generate_pajek(G):
128 line += "\n"
129 path.write(line.encode(encoding))
130
131
132 @open_file(0, mode="rb")
134 """Read graph in Pajek format from path.
135
136 Parameters
137 ----------
138 path : file or string
139 File or filename to write.
140 Filenames ending in .gz or .bz2 will be uncompressed.
141
142 Returns
143 -------
144 G : NetworkX MultiGraph or MultiDiGraph.
145
146 Examples
147 --------
148 >>> G = nx.path_graph(4)
149 >>> nx.write_pajek(G, "test.net")
151
152 To create a Graph instead of a MultiGraph use
153
154 >>> G1 = nx.Graph(G)
155
156 References
157 ----------
159 for format information.
160 """
161 lines = (line.decode(encoding) for line in path)
162 return parse_pajek(lines)
163
164
165 def parse_pajek(lines):
166 """Parse Pajek format graph from string or iterable.
167
168 Parameters
169 ----------
170 lines : string or iterable
171 Data in Pajek format.
172
173 Returns
174 -------
175 G : NetworkX graph
176
178 --------
180
181 """
182 import shlex
183
184 # multigraph=False
185 if isinstance(lines, str):
186 lines = iter(lines.split("\n"))
187 lines = iter([line.rstrip("\n") for line in lines])
188 G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes
189 labels = [] # in the order of the file, needed for matrix
190 while lines:
191 try:
192 l = next(lines)
193 except: # EOF
194 break
195 if l.lower().startswith("*network"):
196 try:
197 label, name = l.split(None, 1)
198 except ValueError:
199 # Line was not of the form: *network NAME
200 pass
201 else:
202 G.graph["name"] = name
203 elif l.lower().startswith("*vertices"):
204 nodelabels = {}
205 l, nnodes = l.split()
206 for i in range(int(nnodes)):
207 l = next(lines)
208 try:
209 splitline = [
210 x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
211 ]
212 except AttributeError:
213 splitline = shlex.split(str(l))
214 id, label = splitline[0:2]
215 labels.append(label)
217 nodelabels[id] = label
218 G.nodes[label]["id"] = id
219 try:
220 x, y, shape = splitline[2:5]
221 G.nodes[label].update(
222 {"x": float(x), "y": float(y), "shape": shape}
223 )
224 except:
225 pass
226 extra_attr = zip(splitline[5::2], splitline[6::2])
227 G.nodes[label].update(extra_attr)
228 elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"):
229 if l.lower().startswith("*edge"):
230 # switch from multidigraph to multigraph
231 G = nx.MultiGraph(G)
232 if l.lower().startswith("*arcs"):
233 # switch to directed with multiple arcs for each existing edge
234 G = G.to_directed()
235 for l in lines:
236 try:
237 splitline = [
238 x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
239 ]
240 except AttributeError:
241 splitline = shlex.split(str(l))
242
243 if len(splitline) < 2:
244 continue
245 ui, vi = splitline[0:2]
246 u = nodelabels.get(ui, ui)
247 v = nodelabels.get(vi, vi)
248 # parse the data attached to this edge and put in a dictionary
249 edge_data = {}
250 try:
251 # there should always be a single value on the edge?
252 w = splitline[2:3]
253 edge_data.update({"weight": float(w[0])})
254 except:
255 pass
256 # if there isn't, just assign a 1
257 # edge_data.update({'value':1})
258 extra_attr = zip(splitline[3::2], splitline[4::2])
259 edge_data.update(extra_attr)
260 # if G.has_edge(u,v):
261 # multigraph=True
263 elif l.lower().startswith("*matrix"):
264 G = nx.DiGraph(G)
266 (labels[row], labels[col], {"weight": int(data)})
267 for (row, line) in enumerate(lines)
268 for (col, data) in enumerate(line.split())
269 if int(data) != 0
270 )