| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- """
- =======================
- Distance-regular graphs
- =======================
- """
- from collections import defaultdict
- from itertools import combinations_with_replacement
- from math import log
- import networkx as nx
- from networkx.utils import not_implemented_for
- from .distance_measures import diameter
- __all__ = [
- "is_distance_regular",
- "is_strongly_regular",
- "intersection_array",
- "global_parameters",
- ]
- @nx._dispatchable
- def is_distance_regular(G):
- """Returns True if the graph is distance regular, False otherwise.
- A connected graph G is distance-regular if for any nodes x,y
- and any integers i,j=0,1,...,d (where d is the graph
- diameter), the number of vertices at distance i from x and
- distance j from y depends only on i,j and the graph distance
- between x and y, independently of the choice of x and y.
- Parameters
- ----------
- G: Networkx graph (undirected)
- Returns
- -------
- bool
- True if the graph is Distance Regular, False otherwise
- Examples
- --------
- >>> G = nx.hypercube_graph(6)
- >>> nx.is_distance_regular(G)
- True
- See Also
- --------
- intersection_array, global_parameters
- Notes
- -----
- For undirected and simple graphs only
- References
- ----------
- .. [1] Brouwer, A. E.; Cohen, A. M.; and Neumaier, A.
- Distance-Regular Graphs. New York: Springer-Verlag, 1989.
- .. [2] Weisstein, Eric W. "Distance-Regular Graph."
- http://mathworld.wolfram.com/Distance-RegularGraph.html
- """
- try:
- intersection_array(G)
- return True
- except nx.NetworkXError:
- return False
- def global_parameters(b, c):
- """Returns global parameters for a given intersection array.
- Given a distance-regular graph G with diameter d and integers b_i,
- c_i,i = 0,....,d such that for any 2 vertices x,y in G at a distance
- i=d(x,y), there are exactly c_i neighbors of y at a distance of i-1 from x
- and b_i neighbors of y at a distance of i+1 from x.
- Thus, a distance regular graph has the global parameters,
- [[c_0,a_0,b_0],[c_1,a_1,b_1],......,[c_d,a_d,b_d]] for the
- intersection array [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d]
- where a_i+b_i+c_i=k , k= degree of every vertex.
- Parameters
- ----------
- b : list
- c : list
- Returns
- -------
- iterable
- An iterable over three tuples.
- Examples
- --------
- >>> G = nx.dodecahedral_graph()
- >>> b, c = nx.intersection_array(G)
- >>> list(nx.global_parameters(b, c))
- [(0, 0, 3), (1, 0, 2), (1, 1, 1), (1, 1, 1), (2, 0, 1), (3, 0, 0)]
- References
- ----------
- .. [1] Weisstein, Eric W. "Global Parameters."
- From MathWorld--A Wolfram Web Resource.
- http://mathworld.wolfram.com/GlobalParameters.html
- See Also
- --------
- intersection_array
- """
- return ((y, b[0] - x - y, x) for x, y in zip(b + [0], [0] + c))
- @not_implemented_for("directed")
- @not_implemented_for("multigraph")
- @nx._dispatchable
- def intersection_array(G):
- """Returns the intersection array of a distance-regular graph.
- Given a distance-regular graph G with integers b_i, c_i,i = 0,....,d
- such that for any 2 vertices x,y in G at a distance i=d(x,y), there
- are exactly c_i neighbors of y at a distance of i-1 from x and b_i
- neighbors of y at a distance of i+1 from x.
- A distance regular graph's intersection array is given by,
- [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d]
- Parameters
- ----------
- G: Networkx graph (undirected)
- Returns
- -------
- b,c: tuple of lists
- Examples
- --------
- >>> G = nx.icosahedral_graph()
- >>> nx.intersection_array(G)
- ([5, 2, 1], [1, 2, 5])
- References
- ----------
- .. [1] Weisstein, Eric W. "Intersection Array."
- From MathWorld--A Wolfram Web Resource.
- http://mathworld.wolfram.com/IntersectionArray.html
- See Also
- --------
- global_parameters
- """
- # the input graph is very unlikely to be distance-regular: here are the
- # number a(n) of connected simple graphs, and the number b(n) of
- # distance-regular graphs among them:
- #
- # n | 1 2 3 4 5 6 7 8 9 10
- # -----+------------------------------------------------------------------
- # a(n) | 1 1 2 6 21 112 853 11117 261080 11716571 https://oeis.org/A001349
- # b(n) | 1 1 1 2 2 4 2 5 4 7 https://oeis.org/A241814
- #
- # in light of this, let's compute shortest path lengths as we go instead of
- # precomputing them all
- # test for regular graph (all degrees must be equal)
- if not nx.is_regular(G) or not nx.is_connected(G):
- raise nx.NetworkXError("Graph is not distance regular.")
- path_length = defaultdict(dict)
- bint = {} # 'b' intersection array
- cint = {} # 'c' intersection array
- # see https://doi.org/10.1016/j.ejc.2004.07.004, Theorem 1.5, page 81:
- # the diameter of a distance-regular graph is at most (8 log_2 n) / 3,
- # so let's compute it as we go in the hope that we can stop early
- diam = 0
- max_diameter_for_dr_graphs = (8 * log(len(G), 2)) / 3
- for u, v in combinations_with_replacement(G, 2):
- # compute needed shortest path lengths
- pl_u = path_length[u]
- if v not in pl_u:
- pl_u.update(nx.single_source_shortest_path_length(G, u))
- for x, distance in pl_u.items():
- path_length[x][u] = distance
- i = path_length[u][v]
- diam = max(diam, i)
- # diameter too large: graph can't be distance-regular
- if diam > max_diameter_for_dr_graphs:
- raise nx.NetworkXError("Graph is not distance regular.")
- vnbrs = G[v]
- # compute needed path lengths
- for n in vnbrs:
- pl_n = path_length[n]
- if u not in pl_n:
- pl_n.update(nx.single_source_shortest_path_length(G, n))
- for x, distance in pl_n.items():
- path_length[x][n] = distance
- # number of neighbors of v at a distance of i-1 from u
- c = sum(1 for n in vnbrs if pl_u[n] == i - 1)
- # number of neighbors of v at a distance of i+1 from u
- b = sum(1 for n in vnbrs if pl_u[n] == i + 1)
- # b, c are independent of u and v
- if cint.get(i, c) != c or bint.get(i, b) != b:
- raise nx.NetworkXError("Graph is not distance regular")
- bint[i] = b
- cint[i] = c
- return (
- [bint.get(j, 0) for j in range(diam)],
- [cint.get(j + 1, 0) for j in range(diam)],
- )
- # TODO There is a definition for directed strongly regular graphs.
- @not_implemented_for("directed")
- @not_implemented_for("multigraph")
- @nx._dispatchable
- def is_strongly_regular(G):
- """Returns True if and only if the given graph is strongly
- regular.
- An undirected graph is *strongly regular* if
- * it is regular,
- * each pair of adjacent vertices has the same number of neighbors in
- common,
- * each pair of nonadjacent vertices has the same number of neighbors
- in common.
- Each strongly regular graph is a distance-regular graph.
- Conversely, if a distance-regular graph has diameter two, then it is
- a strongly regular graph. For more information on distance-regular
- graphs, see :func:`is_distance_regular`.
- Parameters
- ----------
- G : NetworkX graph
- An undirected graph.
- Returns
- -------
- bool
- Whether `G` is strongly regular.
- Examples
- --------
- The cycle graph on five vertices is strongly regular. It is
- two-regular, each pair of adjacent vertices has no shared neighbors,
- and each pair of nonadjacent vertices has one shared neighbor::
- >>> G = nx.cycle_graph(5)
- >>> nx.is_strongly_regular(G)
- True
- """
- # Here is an alternate implementation based directly on the
- # definition of strongly regular graphs:
- #
- # return (all_equal(G.degree().values())
- # and all_equal(len(common_neighbors(G, u, v))
- # for u, v in G.edges())
- # and all_equal(len(common_neighbors(G, u, v))
- # for u, v in non_edges(G)))
- #
- # We instead use the fact that a distance-regular graph of diameter
- # two is strongly regular.
- return is_distance_regular(G) and diameter(G) == 2
|