| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- """
- Generators for the small graph atlas.
- """
- import gzip
- import importlib.resources
- from itertools import islice
- import networkx as nx
- __all__ = ["graph_atlas", "graph_atlas_g"]
- #: The total number of graphs in the atlas.
- #:
- #: The graphs are labeled starting from 0 and extending to (but not
- #: including) this number.
- NUM_GRAPHS = 1253
- #: The path to the data file containing the graph edge lists.
- #:
- #: This is the absolute path of the gzipped text file containing the
- #: edge list for each graph in the atlas. The file contains one entry
- #: per graph in the atlas, in sequential order, starting from graph
- #: number 0 and extending through graph number 1252 (see
- #: :data:`NUM_GRAPHS`). Each entry looks like
- #:
- #: .. sourcecode:: text
- #:
- #: GRAPH 6
- #: NODES 3
- #: 0 1
- #: 0 2
- #:
- #: where the first two lines are the graph's index in the atlas and the
- #: number of nodes in the graph, and the remaining lines are the edge
- #: list.
- #:
- #: This file was generated from a Python list of graphs via code like
- #: the following::
- #:
- #: import gzip
- #: from networkx.generators.atlas import graph_atlas_g
- #: from networkx.readwrite.edgelist import write_edgelist
- #:
- #: with gzip.open('atlas.dat.gz', 'wb') as f:
- #: for i, G in enumerate(graph_atlas_g()):
- #: f.write(bytes(f'GRAPH {i}\n', encoding='utf-8'))
- #: f.write(bytes(f'NODES {len(G)}\n', encoding='utf-8'))
- #: write_edgelist(G, f, data=False)
- #:
- # Path to the atlas file
- ATLAS_FILE = importlib.resources.files("networkx.generators") / "atlas.dat.gz"
- def _generate_graphs():
- """Sequentially read the file containing the edge list data for the
- graphs in the atlas and generate the graphs one at a time.
- This function reads the file given in :data:`.ATLAS_FILE`.
- """
- with gzip.open(ATLAS_FILE, "rb") as f:
- line = f.readline()
- while line and line.startswith(b"GRAPH"):
- # The first two lines of each entry tell us the index of the
- # graph in the list and the number of nodes in the graph.
- # They look like this:
- #
- # GRAPH 3
- # NODES 2
- #
- graph_index = int(line[6:].rstrip())
- line = f.readline()
- num_nodes = int(line[6:].rstrip())
- # The remaining lines contain the edge list, until the next
- # GRAPH line (or until the end of the file).
- edgelist = []
- line = f.readline()
- while line and not line.startswith(b"GRAPH"):
- edgelist.append(line.rstrip())
- line = f.readline()
- G = nx.Graph()
- G.name = f"G{graph_index}"
- G.add_nodes_from(range(num_nodes))
- G.add_edges_from(tuple(map(int, e.split())) for e in edgelist)
- yield G
- @nx._dispatchable(graphs=None, returns_graph=True)
- def graph_atlas(i):
- """Returns graph number `i` from the Graph Atlas.
- For more information, see :func:`.graph_atlas_g`.
- Parameters
- ----------
- i : int
- The index of the graph from the atlas to get. The graph at index
- 0 is assumed to be the null graph.
- Returns
- -------
- list
- A list of :class:`~networkx.Graph` objects, the one at index *i*
- corresponding to the graph *i* in the Graph Atlas.
- See also
- --------
- graph_atlas_g
- Notes
- -----
- The time required by this function increases linearly with the
- argument `i`, since it reads a large file sequentially in order to
- generate the graph [1]_.
- References
- ----------
- .. [1] Ronald C. Read and Robin J. Wilson, *An Atlas of Graphs*.
- Oxford University Press, 1998.
- """
- if not (0 <= i < NUM_GRAPHS):
- raise ValueError(f"index must be between 0 and {NUM_GRAPHS}")
- return next(islice(_generate_graphs(), i, None))
- @nx._dispatchable(graphs=None, returns_graph=True)
- def graph_atlas_g():
- """Returns the list of all graphs with up to seven nodes named in the
- Graph Atlas.
- The graphs are listed in increasing order by
- 1. number of nodes,
- 2. number of edges,
- 3. degree sequence (for example 111223 < 112222),
- 4. number of automorphisms,
- in that order, with three exceptions as described in the *Notes*
- section below. This causes the list to correspond with the index of
- the graphs in the Graph Atlas [atlas]_, with the first graph,
- ``G[0]``, being the null graph.
- Returns
- -------
- list
- A list of :class:`~networkx.Graph` objects, the one at index *i*
- corresponding to the graph *i* in the Graph Atlas.
- Examples
- --------
- >>> from pprint import pprint
- >>> atlas = nx.graph_atlas_g()
- There are 1253 graphs in the atlas
- >>> len(atlas)
- 1253
- The number of graphs with *n* nodes, where *n* ranges from 0 to 7:
- >>> from collections import Counter
- >>> num_nodes_per_graph = [len(G) for G in atlas]
- >>> Counter(num_nodes_per_graph)
- Counter({7: 1044, 6: 156, 5: 34, 4: 11, 3: 4, 2: 2, 0: 1, 1: 1})
- Since the atlas is ordered by the number of nodes in the graph, all graphs
- with *n* nodes can be obtained by slicing the atlas. For example, all
- graphs with 5 nodes:
- >>> G5_list = atlas[19:53]
- >>> all(len(G) == 5 for G in G5_list)
- True
- Or all graphs with at least 3 nodes but fewer than 7 nodes:
- >>> G3_6_list = atlas[4:209]
- More generally, the indices that partition the atlas by the number of nodes
- per graph:
- >>> import itertools
- >>> partition_indices = [0] + list(
- ... itertools.accumulate(Counter(num_nodes_per_graph).values()) # cumsum
- ... )
- >>> partition_indices
- [0, 1, 2, 4, 8, 19, 53, 209, 1253]
- >>> partition_mapping = dict(enumerate(itertools.pairwise(partition_indices)))
- >>> pprint(partition_mapping)
- {0: (0, 1),
- 1: (1, 2),
- 2: (2, 4),
- 3: (4, 8),
- 4: (8, 19),
- 5: (19, 53),
- 6: (53, 209),
- 7: (209, 1253)}
- See also
- --------
- graph_atlas
- Notes
- -----
- This function may be expensive in both time and space, since it
- reads a large file sequentially in order to populate the list.
- Although the NetworkX atlas functions match the order of graphs
- given in the "Atlas of Graphs" book, there are (at least) three
- errors in the ordering described in the book. The following three
- pairs of nodes violate the lexicographically nondecreasing sorted
- degree sequence rule:
- - graphs 55 and 56 with degree sequences 001111 and 000112,
- - graphs 1007 and 1008 with degree sequences 3333444 and 3333336,
- - graphs 1012 and 1213 with degree sequences 1244555 and 1244456.
- References
- ----------
- .. [atlas] Ronald C. Read and Robin J. Wilson,
- *An Atlas of Graphs*.
- Oxford University Press, 1998.
- """
- return list(_generate_graphs())
|