graph6.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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. """Functions for reading and writing graphs in the *graph6* format.
  4. The *graph6* file format is suitable for small graphs or large dense
  5. graphs. For large sparse graphs, use the *sparse6* format.
  6. For more information, see the `graph6`_ homepage.
  7. .. _graph6: http://users.cecs.anu.edu.au/~bdm/data/formats.html
  8. """
  9. from itertools import islice
  10. import networkx as nx
  11. from networkx.exception import NetworkXError
  12. from networkx.utils import not_implemented_for, open_file
  13. __all__ = ["from_graph6_bytes", "read_graph6", "to_graph6_bytes", "write_graph6"]
  14. def _generate_graph6_bytes(G, nodes, header):
  15. """Yield bytes in the graph6 encoding of a graph.
  16. `G` is an undirected simple graph. `nodes` is the list of nodes for
  17. which the node-induced subgraph will be encoded; if `nodes` is the
  18. list of all nodes in the graph, the entire graph will be
  19. encoded. `header` is a Boolean that specifies whether to generate
  20. the header ``b'>>graph6<<'`` before the remaining data.
  21. This function generates `bytes` objects in the following order:
  22. 1. the header (if requested),
  23. 2. the encoding of the number of nodes,
  24. 3. each character, one-at-a-time, in the encoding of the requested
  25. node-induced subgraph,
  26. 4. a newline character.
  27. This function raises :exc:`ValueError` if the graph is too large for
  28. the graph6 format (that is, greater than ``2 ** 36`` nodes).
  29. """
  30. n = len(G)
  31. if n >= 2**36:
  32. raise ValueError(
  33. "graph6 is only defined if number of nodes is less " "than 2 ** 36"
  34. )
  35. if header:
  36. yield b">>graph6<<"
  37. for d in n_to_data(n):
  38. yield str.encode(chr(d + 63))
  39. # This generates the same as `(v in G[u] for u, v in combinations(G, 2))`,
  40. # but in "column-major" order instead of "row-major" order.
  41. bits = (nodes[j] in G[nodes[i]] for j in range(1, n) for i in range(j))
  42. chunk = list(islice(bits, 6))
  43. while chunk:
  44. d = sum(b << 5 - i for i, b in enumerate(chunk))
  45. yield str.encode(chr(d + 63))
  46. chunk = list(islice(bits, 6))
  47. yield b"\n"
  48. def from_graph6_bytes(bytes_in):
  49. """Read a simple undirected graph in graph6 format from bytes.
  50. Parameters
  51. ----------
  52. bytes_in : bytes
  53. Data in graph6 format, without a trailing newline.
  54. Returns
  55. -------
  56. G : Graph
  57. Raises
  58. ------
  59. NetworkXError
  60. If bytes_in is unable to be parsed in graph6 format
  61. ValueError
  62. If any character ``c`` in bytes_in does not satisfy
  63. ``63 <= ord(c) < 127``.
  64. Examples
  65. --------
  66. >>> G = nx.from_graph6_bytes(b"A_")
  67. >>> sorted(G.edges())
  68. [(0, 1)]
  69. See Also
  70. --------
  71. read_graph6, write_graph6
  72. References
  73. ----------
  74. .. [1] Graph6 specification
  75. <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
  76. """
  77. def bits():
  78. """Returns sequence of individual bits from 6-bit-per-value
  79. list of data values."""
  80. for d in data:
  81. for i in [5, 4, 3, 2, 1, 0]:
  82. yield (d >> i) & 1
  83. if bytes_in.startswith(b">>graph6<<"):
  84. bytes_in = bytes_in[10:]
  85. data = [c - 63 for c in bytes_in]
  86. if any(c > 63 for c in data):
  87. raise ValueError("each input character must be in range(63, 127)")
  88. n, data = data_to_n(data)
  89. nd = (n * (n - 1) // 2 + 5) // 6
  90. if len(data) != nd:
  91. raise NetworkXError(
  92. f"Expected {n * (n - 1) // 2} bits but got {len(data) * 6} in graph6"
  93. )
  94. G = nx.Graph()
  95. G.add_nodes_from(range(n))
  96. for (i, j), b in zip(((i, j) for j in range(1, n) for i in range(j)), bits()):
  97. if b:
  98. G.add_edge(i, j)
  99. return G
  100. @not_implemented_for("directed")
  101. @not_implemented_for("multigraph")
  102. def to_graph6_bytes(G, nodes=None, header=True):
  103. """Convert a simple undirected graph to bytes in graph6 format.
  104. Parameters
  105. ----------
  106. G : Graph (undirected)
  107. nodes: list or iterable
  108. Nodes are labeled 0...n-1 in the order provided. If None the ordering
  109. given by ``G.nodes()`` is used.
  110. header: bool
  111. If True add '>>graph6<<' bytes to head of data.
  112. Raises
  113. ------
  114. NetworkXNotImplemented
  115. If the graph is directed or is a multigraph.
  116. ValueError
  117. If the graph has at least ``2 ** 36`` nodes; the graph6 format
  118. is only defined for graphs of order less than ``2 ** 36``.
  119. Examples
  120. --------
  121. >>> nx.to_graph6_bytes(nx.path_graph(2))
  122. b'>>graph6<<A_\\n'
  123. See Also
  124. --------
  125. from_graph6_bytes, read_graph6, write_graph6_bytes
  126. Notes
  127. -----
  128. The returned bytes end with a newline character.
  129. The format does not support edge or node labels, parallel edges or
  130. self loops. If self loops are present they are silently ignored.
  131. References
  132. ----------
  133. .. [1] Graph6 specification
  134. <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
  135. """
  136. if nodes is not None:
  137. G = G.subgraph(nodes)
  138. H = nx.convert_node_labels_to_integers(G)
  139. nodes = sorted(H.nodes())
  140. return b"".join(_generate_graph6_bytes(H, nodes, header))
  141. @open_file(0, mode="rb")
  142. def read_graph6(path):
  143. """Read simple undirected graphs in graph6 format from path.
  144. Parameters
  145. ----------
  146. path : file or string
  147. File or filename to write.
  148. Returns
  149. -------
  150. G : Graph or list of Graphs
  151. If the file contains multiple lines then a list of graphs is returned
  152. Raises
  153. ------
  154. NetworkXError
  155. If the string is unable to be parsed in graph6 format
  156. Examples
  157. --------
  158. You can read a graph6 file by giving the path to the file::
  159. >>> import tempfile
  160. >>> with tempfile.NamedTemporaryFile(delete=False) as f:
  161. ... _ = f.write(b">>graph6<<A_\\n")
  162. ... _ = f.seek(0)
  163. ... G = nx.read_graph6(f.name)
  164. >>> list(G.edges())
  165. [(0, 1)]
  166. You can also read a graph6 file by giving an open file-like object::
  167. >>> import tempfile
  168. >>> with tempfile.NamedTemporaryFile() as f:
  169. ... _ = f.write(b">>graph6<<A_\\n")
  170. ... _ = f.seek(0)
  171. ... G = nx.read_graph6(f)
  172. >>> list(G.edges())
  173. [(0, 1)]
  174. See Also
  175. --------
  176. from_graph6_bytes, write_graph6
  177. References
  178. ----------
  179. .. [1] Graph6 specification
  180. <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
  181. """
  182. glist = []
  183. for line in path:
  184. line = line.strip()
  185. if not len(line):
  186. continue
  187. glist.append(from_graph6_bytes(line))
  188. if len(glist) == 1:
  189. return glist[0]
  190. else:
  191. return glist
  192. @not_implemented_for("directed")
  193. @not_implemented_for("multigraph")
  194. @open_file(1, mode="wb")
  195. def write_graph6(G, path, nodes=None, header=True):
  196. """Write a simple undirected graph to a path in graph6 format.
  197. Parameters
  198. ----------
  199. G : Graph (undirected)
  200. path : str
  201. The path naming the file to which to write the graph.
  202. nodes: list or iterable
  203. Nodes are labeled 0...n-1 in the order provided. If None the ordering
  204. given by ``G.nodes()`` is used.
  205. header: bool
  206. If True add '>>graph6<<' string to head of data
  207. Raises
  208. ------
  209. NetworkXNotImplemented
  210. If the graph is directed or is a multigraph.
  211. ValueError
  212. If the graph has at least ``2 ** 36`` nodes; the graph6 format
  213. is only defined for graphs of order less than ``2 ** 36``.
  214. Examples
  215. --------
  216. You can write a graph6 file by giving the path to a file::
  217. >>> import tempfile
  218. >>> with tempfile.NamedTemporaryFile(delete=False) as f:
  219. ... nx.write_graph6(nx.path_graph(2), f.name)
  220. ... _ = f.seek(0)
  221. ... print(f.read())
  222. b'>>graph6<<A_\\n'
  223. See Also
  224. --------
  225. from_graph6_bytes, read_graph6
  226. Notes
  227. -----
  228. The function writes a newline character after writing the encoding
  229. of the graph.
  230. The format does not support edge or node labels, parallel edges or
  231. self loops. If self loops are present they are silently ignored.
  232. References
  233. ----------
  234. .. [1] Graph6 specification
  235. <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
  236. """
  237. return write_graph6_file(G, path, nodes=nodes, header=header)
  238. @not_implemented_for("directed")
  239. @not_implemented_for("multigraph")
  240. def write_graph6_file(G, f, nodes=None, header=True):
  241. """Write a simple undirected graph to a file-like object in graph6 format.
  242. Parameters
  243. ----------
  244. G : Graph (undirected)
  245. f : file-like object
  246. The file to write.
  247. nodes: list or iterable
  248. Nodes are labeled 0...n-1 in the order provided. If None the ordering
  249. given by ``G.nodes()`` is used.
  250. header: bool
  251. If True add '>>graph6<<' string to head of data
  252. Raises
  253. ------
  254. NetworkXNotImplemented
  255. If the graph is directed or is a multigraph.
  256. ValueError
  257. If the graph has at least ``2 ** 36`` nodes; the graph6 format
  258. is only defined for graphs of order less than ``2 ** 36``.
  259. Examples
  260. --------
  261. You can write a graph6 file by giving an open file-like object::
  262. >>> import tempfile
  263. >>> with tempfile.NamedTemporaryFile() as f:
  264. ... nx.write_graph6(nx.path_graph(2), f)
  265. ... _ = f.seek(0)
  266. ... print(f.read())
  267. b'>>graph6<<A_\\n'
  268. See Also
  269. --------
  270. from_graph6_bytes, read_graph6
  271. Notes
  272. -----
  273. The function writes a newline character after writing the encoding
  274. of the graph.
  275. The format does not support edge or node labels, parallel edges or
  276. self loops. If self loops are present they are silently ignored.
  277. References
  278. ----------
  279. .. [1] Graph6 specification
  280. <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
  281. """
  282. if nodes is not None:
  283. G = G.subgraph(nodes)
  284. H = nx.convert_node_labels_to_integers(G)
  285. nodes = sorted(H.nodes())
  286. for b in _generate_graph6_bytes(H, nodes, header):
  287. f.write(b)
  288. def data_to_n(data):
  289. """Read initial one-, four- or eight-unit value from graph6
  290. integer sequence.
  291. Return (value, rest of seq.)"""
  292. if data[0] <= 62:
  293. return data[0], data[1:]
  294. if data[1] <= 62:
  295. return (data[1] << 12) + (data[2] << 6) + data[3], data[4:]
  296. return (
  297. (data[2] << 30)
  298. + (data[3] << 24)
  299. + (data[4] << 18)
  300. + (data[5] << 12)
  301. + (data[6] << 6)
  302. + data[7],
  303. data[8:],
  304. )
  305. def n_to_data(n):
  306. """Convert an integer to one-, four- or eight-unit graph6 sequence.
  307. This function is undefined if `n` is not in ``range(2 ** 36)``.
  308. """
  309. if n <= 62:
  310. return [n]
  311. elif n <= 258047:
  312. return [63, (n >> 12) & 0x3F, (n >> 6) & 0x3F, n & 0x3F]
  313. else: # if n <= 68719476735:
  314. return [
  315. 63,
  316. 63,
  317. (n >> 30) & 0x3F,
  318. (n >> 24) & 0x3F,
  319. (n >> 18) & 0x3F,
  320. (n >> 12) & 0x3F,
  321. (n >> 6) & 0x3F,
  322. n & 0x3F,
  323. ]