Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/readwrite/sparse6.py: 54%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

124 statements  

1# Original author: D. Eppstein, UC Irvine, August 12, 2003. 

2# The original code at https://www.ics.uci.edu/~eppstein/PADS/ is public domain. 

3"""Functions for reading and writing graphs in the *sparse6* format. 

4 

5The *sparse6* file format is a space-efficient format for large sparse 

6graphs. For small graphs or large dense graphs, use the *graph6* file 

7format. 

8 

9For more information, see the `sparse6`_ homepage. 

10 

11.. _sparse6: https://users.cecs.anu.edu.au/~bdm/data/formats.html 

12 

13""" 

14 

15import networkx as nx 

16from networkx.exception import NetworkXError 

17from networkx.readwrite.graph6 import data_to_n, n_to_data 

18from networkx.utils import not_implemented_for, open_file 

19 

20__all__ = ["from_sparse6_bytes", "read_sparse6", "to_sparse6_bytes", "write_sparse6"] 

21 

22 

23def _generate_sparse6_bytes(G, nodes, header): 

24 """Yield bytes in the sparse6 encoding of a graph. 

25 

26 `G` is an undirected simple graph. `nodes` is the list of nodes for 

27 which the node-induced subgraph will be encoded; if `nodes` is the 

28 list of all nodes in the graph, the entire graph will be 

29 encoded. `header` is a Boolean that specifies whether to generate 

30 the header ``b'>>sparse6<<'`` before the remaining data. 

31 

32 This function generates `bytes` objects in the following order: 

33 

34 1. the header (if requested), 

35 2. the encoding of the number of nodes, 

36 3. each character, one-at-a-time, in the encoding of the requested 

37 node-induced subgraph, 

38 4. a newline character. 

39 

40 This function raises :exc:`ValueError` if the graph is too large for 

41 the graph6 format (that is, greater than ``2 ** 36`` nodes). 

42 

43 """ 

44 n = len(G) 

45 if n >= 2**36: 

46 raise ValueError( 

47 "sparse6 is only defined if number of nodes is less than 2 ** 36" 

48 ) 

49 if header: 

50 yield b">>sparse6<<" 

51 yield b":" 

52 for d in n_to_data(n): 

53 yield str.encode(chr(d + 63)) 

54 

55 k = 1 

56 while 1 << k < n: 

57 k += 1 

58 

59 def enc(x): 

60 """Big endian k-bit encoding of x""" 

61 return [1 if (x & 1 << (k - 1 - i)) else 0 for i in range(k)] 

62 

63 edges = sorted((max(u, v), min(u, v)) for u, v in G.edges()) 

64 bits = [] 

65 curv = 0 

66 for v, u in edges: 

67 if v == curv: # current vertex edge 

68 bits.append(0) 

69 bits.extend(enc(u)) 

70 elif v == curv + 1: # next vertex edge 

71 curv += 1 

72 bits.append(1) 

73 bits.extend(enc(u)) 

74 else: # skip to vertex v and then add edge to u 

75 curv = v 

76 bits.append(1) 

77 bits.extend(enc(v)) 

78 bits.append(0) 

79 bits.extend(enc(u)) 

80 if k < 6 and n == (1 << k) and ((-len(bits)) % 6) >= k and curv < (n - 1): 

81 # Padding special case: small k, n=2^k, 

82 # more than k bits of padding needed, 

83 # current vertex is not (n-1) -- 

84 # appending 1111... would add a loop on (n-1) 

85 bits.append(0) 

86 bits.extend([1] * ((-len(bits)) % 6)) 

87 else: 

88 bits.extend([1] * ((-len(bits)) % 6)) 

89 

90 data = [ 

91 (bits[i + 0] << 5) 

92 + (bits[i + 1] << 4) 

93 + (bits[i + 2] << 3) 

94 + (bits[i + 3] << 2) 

95 + (bits[i + 4] << 1) 

96 + (bits[i + 5] << 0) 

97 for i in range(0, len(bits), 6) 

98 ] 

99 

100 for d in data: 

101 yield str.encode(chr(d + 63)) 

102 yield b"\n" 

103 

104 

105@nx._dispatchable(graphs=None, returns_graph=True) 

106def from_sparse6_bytes(string): 

107 """Read an undirected graph in sparse6 format from string. 

108 

109 Parameters 

110 ---------- 

111 string : string 

112 Data in sparse6 format 

113 

114 Returns 

115 ------- 

116 G : Graph 

117 

118 Raises 

119 ------ 

120 NetworkXError 

121 If the string is unable to be parsed in sparse6 format 

122 

123 Examples 

124 -------- 

125 >>> G = nx.from_sparse6_bytes(b":A_") 

126 >>> sorted(G.edges()) 

127 [(0, 1), (0, 1), (0, 1)] 

128 

129 See Also 

130 -------- 

131 read_sparse6, write_sparse6 

132 

133 References 

134 ---------- 

135 .. [1] Sparse6 specification 

136 <https://users.cecs.anu.edu.au/~bdm/data/formats.html> 

137 

138 """ 

139 if string.startswith(b">>sparse6<<"): 

140 string = string[11:] 

141 if not string.startswith(b":"): 

142 raise NetworkXError("Expected leading colon in sparse6") 

143 

144 chars = [c - 63 for c in string[1:]] 

145 n, data = data_to_n(chars) 

146 k = 1 

147 while 1 << k < n: 

148 k += 1 

149 

150 def parseData(): 

151 """Returns stream of pairs b[i], x[i] for sparse6 format.""" 

152 chunks = iter(data) 

153 d = None # partial data word 

154 dLen = 0 # how many unparsed bits are left in d 

155 

156 while 1: 

157 if dLen < 1: 

158 try: 

159 d = next(chunks) 

160 except StopIteration: 

161 return 

162 dLen = 6 

163 dLen -= 1 

164 b = (d >> dLen) & 1 # grab top remaining bit 

165 

166 x = d & ((1 << dLen) - 1) # partially built up value of x 

167 xLen = dLen # how many bits included so far in x 

168 while xLen < k: # now grab full chunks until we have enough 

169 try: 

170 d = next(chunks) 

171 except StopIteration: 

172 return 

173 dLen = 6 

174 x = (x << 6) + d 

175 xLen += 6 

176 x = x >> (xLen - k) # shift back the extra bits 

177 dLen = xLen - k 

178 yield b, x 

179 

180 v = 0 

181 

182 G = nx.MultiGraph() 

183 G.add_nodes_from(range(n)) 

184 

185 multigraph = False 

186 for b, x in parseData(): 

187 if b == 1: 

188 v += 1 

189 # padding with ones can cause overlarge number here 

190 if x >= n or v >= n: 

191 break 

192 elif x > v: 

193 v = x 

194 else: 

195 if G.has_edge(x, v): 

196 multigraph = True 

197 G.add_edge(x, v) 

198 if not multigraph: 

199 G = nx.Graph(G) 

200 return G 

201 

202 

203def to_sparse6_bytes(G, nodes=None, header=True): 

204 """Convert an undirected graph to bytes in sparse6 format. 

205 

206 Parameters 

207 ---------- 

208 G : Graph (undirected) 

209 

210 nodes: list or iterable 

211 Nodes are labeled 0...n-1 in the order provided. If None the ordering 

212 given by ``G.nodes()`` is used. 

213 

214 header: bool 

215 If True add '>>sparse6<<' bytes to head of data. 

216 

217 Raises 

218 ------ 

219 NetworkXNotImplemented 

220 If the graph is directed. 

221 

222 ValueError 

223 If the graph has at least ``2 ** 36`` nodes; the sparse6 format 

224 is only defined for graphs of order less than ``2 ** 36``. 

225 

226 Examples 

227 -------- 

228 >>> nx.to_sparse6_bytes(nx.path_graph(2)) 

229 b'>>sparse6<<:An\\n' 

230 

231 See Also 

232 -------- 

233 to_sparse6_bytes, read_sparse6, write_sparse6_bytes 

234 

235 Notes 

236 ----- 

237 The returned bytes end with a newline character. 

238 

239 The format does not support edge or node labels. 

240 

241 References 

242 ---------- 

243 .. [1] Graph6 specification 

244 <https://users.cecs.anu.edu.au/~bdm/data/formats.html> 

245 

246 """ 

247 if nodes is not None: 

248 G = G.subgraph(nodes) 

249 G = nx.convert_node_labels_to_integers(G, ordering="sorted") 

250 return b"".join(_generate_sparse6_bytes(G, nodes, header)) 

251 

252 

253@open_file(0, mode="rb") 

254@nx._dispatchable(graphs=None, returns_graph=True) 

255def read_sparse6(path): 

256 """Read an undirected graph in sparse6 format from path. 

257 

258 Parameters 

259 ---------- 

260 path : file or string 

261 Filename or file handle to read. 

262 Filenames ending in .gz or .bz2 will be decompressed. 

263 

264 Returns 

265 ------- 

266 G : Graph/Multigraph or list of Graphs/MultiGraphs 

267 If the file contains multiple lines then a list of graphs is returned 

268 

269 Raises 

270 ------ 

271 NetworkXError 

272 If the string is unable to be parsed in sparse6 format 

273 

274 Examples 

275 -------- 

276 You can read a sparse6 file by giving the path to the file:: 

277 

278 >>> import tempfile 

279 >>> with tempfile.NamedTemporaryFile(delete=False) as f: 

280 ... _ = f.write(b">>sparse6<<:An\\n") 

281 ... _ = f.seek(0) 

282 ... G = nx.read_sparse6(f.name) 

283 >>> list(G.edges()) 

284 [(0, 1)] 

285 

286 You can also read a sparse6 file by giving an open file-like object:: 

287 

288 >>> import tempfile 

289 >>> with tempfile.NamedTemporaryFile() as f: 

290 ... _ = f.write(b">>sparse6<<:An\\n") 

291 ... _ = f.seek(0) 

292 ... G = nx.read_sparse6(f) 

293 >>> list(G.edges()) 

294 [(0, 1)] 

295 

296 See Also 

297 -------- 

298 read_sparse6, from_sparse6_bytes 

299 

300 References 

301 ---------- 

302 .. [1] Sparse6 specification 

303 <https://users.cecs.anu.edu.au/~bdm/data/formats.html> 

304 

305 """ 

306 glist = [] 

307 for line in path: 

308 line = line.strip() 

309 if not len(line): 

310 continue 

311 glist.append(from_sparse6_bytes(line)) 

312 if len(glist) == 1: 

313 return glist[0] 

314 else: 

315 return glist 

316 

317 

318@not_implemented_for("directed") 

319@open_file(1, mode="wb") 

320def write_sparse6(G, path, nodes=None, header=True): 

321 """Write graph G to given path in sparse6 format. 

322 

323 Parameters 

324 ---------- 

325 G : Graph (undirected) 

326 

327 path : file or string 

328 File or filename to write. 

329 Filenames ending in .gz or .bz2 will be compressed. 

330 

331 nodes: list or iterable 

332 Nodes are labeled 0...n-1 in the order provided. If None the ordering 

333 given by G.nodes() is used. 

334 

335 header: bool 

336 If True add '>>sparse6<<' string to head of data 

337 

338 Raises 

339 ------ 

340 NetworkXError 

341 If the graph is directed 

342 

343 Examples 

344 -------- 

345 You can write a sparse6 file by giving the path to the file:: 

346 

347 >>> import tempfile 

348 >>> with tempfile.NamedTemporaryFile(delete=False) as f: 

349 ... nx.write_sparse6(nx.path_graph(2), f.name) 

350 ... print(f.read()) 

351 b'>>sparse6<<:An\\n' 

352 

353 You can also write a sparse6 file by giving an open file-like object:: 

354 

355 >>> with tempfile.NamedTemporaryFile() as f: 

356 ... nx.write_sparse6(nx.path_graph(2), f) 

357 ... _ = f.seek(0) 

358 ... print(f.read()) 

359 b'>>sparse6<<:An\\n' 

360 

361 See Also 

362 -------- 

363 read_sparse6, from_sparse6_bytes 

364 

365 Notes 

366 ----- 

367 The format does not support edge or node labels. 

368 

369 References 

370 ---------- 

371 .. [1] Sparse6 specification 

372 <https://users.cecs.anu.edu.au/~bdm/data/formats.html> 

373 

374 """ 

375 if nodes is not None: 

376 G = G.subgraph(nodes) 

377 G = nx.convert_node_labels_to_integers(G, ordering="sorted") 

378 for b in _generate_sparse6_bytes(G, nodes, header): 

379 path.write(b)