comparison env/lib/python3.7/site-packages/networkx/readwrite/sparse6.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # Original author: D. Eppstein, UC Irvine, August 12, 2003.
2 # The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain.
3 # Copyright (C) 2004-2019 by
4 # Aric Hagberg <hagberg@lanl.gov>
5 # Dan Schult <dschult@colgate.edu>
6 # Pieter Swart <swart@lanl.gov>
7 # Tomas Gavenciak <gavento@ucw.cz>
8 # All rights reserved.
9 # BSD license.
10 #
11 # Authors: Tomas Gavenciak <gavento@ucw.cz>
12 # Aric Hagberg <aric.hagberg@lanl.gov>
13 """Functions for reading and writing graphs in the *sparse6* format.
14
15 The *sparse6* file format is a space-efficient format for large sparse
16 graphs. For small graphs or large dense graphs, use the *graph6* file
17 format.
18
19 For more information, see the `sparse6`_ homepage.
20
21 .. _sparse6: http://users.cecs.anu.edu.au/~bdm/data/formats.html
22
23 """
24 from itertools import chain
25 import math
26 import sys
27
28 import networkx as nx
29 from networkx.exception import NetworkXError
30 from networkx.utils import open_file, not_implemented_for
31 from networkx.readwrite.graph6 import data_to_n, n_to_data
32
33 __all__ = ['from_sparse6_bytes', 'read_sparse6', 'to_sparse6_bytes',
34 'write_sparse6']
35
36
37 def _generate_sparse6_bytes(G, nodes, header):
38 """Yield bytes in the sparse6 encoding of a graph.
39
40 `G` is an undirected simple graph. `nodes` is the list of nodes for
41 which the node-induced subgraph will be encoded; if `nodes` is the
42 list of all nodes in the graph, the entire graph will be
43 encoded. `header` is a Boolean that specifies whether to generate
44 the header ``b'>>sparse6<<'`` before the remaining data.
45
46 This function generates `bytes` objects in the following order:
47
48 1. the header (if requested),
49 2. the encoding of the number of nodes,
50 3. each character, one-at-a-time, in the encoding of the requested
51 node-induced subgraph,
52 4. a newline character.
53
54 This function raises :exc:`ValueError` if the graph is too large for
55 the graph6 format (that is, greater than ``2 ** 36`` nodes).
56
57 """
58 n = len(G)
59 if n >= 2 ** 36:
60 raise ValueError('sparse6 is only defined if number of nodes is less '
61 'than 2 ** 36')
62 if header:
63 yield b'>>sparse6<<'
64 yield b':'
65 for d in n_to_data(n):
66 yield str.encode(chr(d + 63))
67
68 k = 1
69 while 1 << k < n:
70 k += 1
71
72 def enc(x):
73 """Big endian k-bit encoding of x"""
74 return [1 if (x & 1 << (k - 1 - i)) else 0 for i in range(k)]
75
76 edges = sorted((max(u, v), min(u, v)) for u, v in G.edges())
77 bits = []
78 curv = 0
79 for (v, u) in edges:
80 if v == curv: # current vertex edge
81 bits.append(0)
82 bits.extend(enc(u))
83 elif v == curv + 1: # next vertex edge
84 curv += 1
85 bits.append(1)
86 bits.extend(enc(u))
87 else: # skip to vertex v and then add edge to u
88 curv = v
89 bits.append(1)
90 bits.extend(enc(v))
91 bits.append(0)
92 bits.extend(enc(u))
93 if k < 6 and n == (1 << k) and ((-len(bits)) % 6) >= k and curv < (n - 1):
94 # Padding special case: small k, n=2^k,
95 # more than k bits of padding needed,
96 # current vertex is not (n-1) --
97 # appending 1111... would add a loop on (n-1)
98 bits.append(0)
99 bits.extend([1] * ((-len(bits)) % 6))
100 else:
101 bits.extend([1] * ((-len(bits)) % 6))
102
103 data = [(bits[i + 0] << 5) + (bits[i + 1] << 4) + (bits[i + 2] << 3) + (bits[i + 3] << 2) +
104 (bits[i + 4] << 1) + (bits[i + 5] << 0) for i in range(0, len(bits), 6)]
105
106 for d in data:
107 yield str.encode(chr(d + 63))
108 yield b'\n'
109
110
111 def from_sparse6_bytes(string):
112 """Read an undirected graph in sparse6 format from string.
113
114 Parameters
115 ----------
116 string : string
117 Data in sparse6 format
118
119 Returns
120 -------
121 G : Graph
122
123 Raises
124 ------
125 NetworkXError
126 If the string is unable to be parsed in sparse6 format
127
128 Examples
129 --------
130 >>> G = nx.from_sparse6_bytes(b':A_')
131 >>> sorted(G.edges())
132 [(0, 1), (0, 1), (0, 1)]
133
134 See Also
135 --------
136 read_sparse6, write_sparse6
137
138 References
139 ----------
140 .. [1] Sparse6 specification
141 <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
142
143 """
144 if string.startswith(b'>>sparse6<<'):
145 string = string[11:]
146 if not string.startswith(b':'):
147 raise NetworkXError('Expected leading colon in sparse6')
148
149 if sys.version_info < (3, ):
150 chars = [ord(c) - 63 for c in string[1:]]
151 else:
152 chars = [c - 63 for c in string[1:]]
153 n, data = data_to_n(chars)
154 k = 1
155 while 1 << k < n:
156 k += 1
157
158 def parseData():
159 """Returns stream of pairs b[i], x[i] for sparse6 format."""
160 chunks = iter(data)
161 d = None # partial data word
162 dLen = 0 # how many unparsed bits are left in d
163
164 while 1:
165 if dLen < 1:
166 try:
167 d = next(chunks)
168 except StopIteration:
169 return
170 dLen = 6
171 dLen -= 1
172 b = (d >> dLen) & 1 # grab top remaining bit
173
174 x = d & ((1 << dLen) - 1) # partially built up value of x
175 xLen = dLen # how many bits included so far in x
176 while xLen < k: # now grab full chunks until we have enough
177 try:
178 d = next(chunks)
179 except StopIteration:
180 return
181 dLen = 6
182 x = (x << 6) + d
183 xLen += 6
184 x = (x >> (xLen - k)) # shift back the extra bits
185 dLen = xLen - k
186 yield b, x
187
188 v = 0
189
190 G = nx.MultiGraph()
191 G.add_nodes_from(range(n))
192
193 multigraph = False
194 for b, x in parseData():
195 if b == 1:
196 v += 1
197 # padding with ones can cause overlarge number here
198 if x >= n or v >= n:
199 break
200 elif x > v:
201 v = x
202 else:
203 if G.has_edge(x, v):
204 multigraph = True
205 G.add_edge(x, v)
206 if not multigraph:
207 G = nx.Graph(G)
208 return G
209
210
211 def to_sparse6_bytes(G, nodes=None, header=True):
212 """Convert an undirected graph to bytes in sparse6 format.
213
214 Parameters
215 ----------
216 G : Graph (undirected)
217
218 nodes: list or iterable
219 Nodes are labeled 0...n-1 in the order provided. If None the ordering
220 given by ``G.nodes()`` is used.
221
222 header: bool
223 If True add '>>sparse6<<' bytes to head of data.
224
225 Raises
226 ------
227 NetworkXNotImplemented
228 If the graph is directed.
229
230 ValueError
231 If the graph has at least ``2 ** 36`` nodes; the sparse6 format
232 is only defined for graphs of order less than ``2 ** 36``.
233
234 Examples
235 --------
236 >>> nx.to_sparse6_bytes(nx.path_graph(2)) # doctest: +SKIP
237 b'>>sparse6<<:An\\n'
238
239 See Also
240 --------
241 to_sparse6_bytes, read_sparse6, write_sparse6_bytes
242
243 Notes
244 -----
245 The returned bytes end with a newline character.
246
247 The format does not support edge or node labels.
248
249 References
250 ----------
251 .. [1] Graph6 specification
252 <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
253
254 """
255 if nodes is not None:
256 G = G.subgraph(nodes)
257 G = nx.convert_node_labels_to_integers(G, ordering='sorted')
258 return b''.join(_generate_sparse6_bytes(G, nodes, header))
259
260
261 @open_file(0, mode='rb')
262 def read_sparse6(path):
263 """Read an undirected graph in sparse6 format from path.
264
265 Parameters
266 ----------
267 path : file or string
268 File or filename to write.
269
270 Returns
271 -------
272 G : Graph/Multigraph or list of Graphs/MultiGraphs
273 If the file contains multiple lines then a list of graphs is returned
274
275 Raises
276 ------
277 NetworkXError
278 If the string is unable to be parsed in sparse6 format
279
280 Examples
281 --------
282 You can read a sparse6 file by giving the path to the file::
283
284 >>> import tempfile
285 >>> with tempfile.NamedTemporaryFile() as f:
286 ... _ = f.write(b'>>sparse6<<:An\\n')
287 ... _ = f.seek(0)
288 ... G = nx.read_sparse6(f.name)
289 >>> list(G.edges())
290 [(0, 1)]
291
292 You can also read a sparse6 file by giving an open file-like object::
293
294 >>> import tempfile
295 >>> with tempfile.NamedTemporaryFile() as f:
296 ... _ = f.write(b'>>sparse6<<:An\\n')
297 ... _ = f.seek(0)
298 ... G = nx.read_sparse6(f)
299 >>> list(G.edges())
300 [(0, 1)]
301
302 See Also
303 --------
304 read_sparse6, from_sparse6_bytes
305
306 References
307 ----------
308 .. [1] Sparse6 specification
309 <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
310
311 """
312 glist = []
313 for line in path:
314 line = line.strip()
315 if not len(line):
316 continue
317 glist.append(from_sparse6_bytes(line))
318 if len(glist) == 1:
319 return glist[0]
320 else:
321 return glist
322
323
324 @not_implemented_for('directed')
325 @open_file(1, mode='wb')
326 def write_sparse6(G, path, nodes=None, header=True):
327 """Write graph G to given path in sparse6 format.
328
329 Parameters
330 ----------
331 G : Graph (undirected)
332
333 path : file or string
334 File or filename to write
335
336 nodes: list or iterable
337 Nodes are labeled 0...n-1 in the order provided. If None the ordering
338 given by G.nodes() is used.
339
340 header: bool
341 If True add '>>sparse6<<' string to head of data
342
343 Raises
344 ------
345 NetworkXError
346 If the graph is directed
347
348 Examples
349 --------
350 You can write a sparse6 file by giving the path to the file::
351
352 >>> import tempfile
353 >>> with tempfile.NamedTemporaryFile() as f:
354 ... nx.write_sparse6(nx.path_graph(2), f.name)
355 ... print(f.read()) # doctest: +SKIP
356 b'>>sparse6<<:An\\n'
357
358 You can also write a sparse6 file by giving an open file-like object::
359
360 >>> with tempfile.NamedTemporaryFile() as f:
361 ... nx.write_sparse6(nx.path_graph(2), f)
362 ... _ = f.seek(0)
363 ... print(f.read()) # doctest: +SKIP
364 b'>>sparse6<<:An\\n'
365
366 See Also
367 --------
368 read_sparse6, from_sparse6_bytes
369
370 Notes
371 -----
372 The format does not support edge or node labels.
373
374 References
375 ----------
376 .. [1] Sparse6 specification
377 <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
378
379 """
380 if nodes is not None:
381 G = G.subgraph(nodes)
382 G = nx.convert_node_labels_to_integers(G, ordering='sorted')
383 for b in _generate_sparse6_bytes(G, nodes, header):
384 path.write(b)