123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- """Test sequences for graphiness.
- """
- import heapq
- import networkx as nx
- __all__ = [
- "is_graphical",
- "is_multigraphical",
- "is_pseudographical",
- "is_digraphical",
- "is_valid_degree_sequence_erdos_gallai",
- "is_valid_degree_sequence_havel_hakimi",
- ]
- def is_graphical(sequence, method="eg"):
- """Returns True if sequence is a valid degree sequence.
- A degree sequence is valid if some graph can realize it.
- Parameters
- ----------
- sequence : list or iterable container
- A sequence of integer node degrees
- method : "eg" | "hh" (default: 'eg')
- The method used to validate the degree sequence.
- "eg" corresponds to the Erdős-Gallai algorithm
- [EG1960]_, [choudum1986]_, and
- "hh" to the Havel-Hakimi algorithm
- [havel1955]_, [hakimi1962]_, [CL1996]_.
- Returns
- -------
- valid : bool
- True if the sequence is a valid degree sequence and False if not.
- Examples
- --------
- >>> G = nx.path_graph(4)
- >>> sequence = (d for n, d in G.degree())
- >>> nx.is_graphical(sequence)
- True
- References
- ----------
- .. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960.
- .. [choudum1986] S.A. Choudum. "A simple proof of the Erdős-Gallai theorem on
- graph sequences." Bulletin of the Australian Mathematical Society, 33,
- pp 67-70, 1986. https://doi.org/10.1017/S0004972700002872
- .. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs"
- Casopis Pest. Mat. 80, 477-480, 1955.
- .. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as
- Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962.
- .. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs",
- Chapman and Hall/CRC, 1996.
- """
- if method == "eg":
- valid = is_valid_degree_sequence_erdos_gallai(list(sequence))
- elif method == "hh":
- valid = is_valid_degree_sequence_havel_hakimi(list(sequence))
- else:
- msg = "`method` must be 'eg' or 'hh'"
- raise nx.NetworkXException(msg)
- return valid
- def _basic_graphical_tests(deg_sequence):
- # Sort and perform some simple tests on the sequence
- deg_sequence = nx.utils.make_list_of_ints(deg_sequence)
- p = len(deg_sequence)
- num_degs = [0] * p
- dmax, dmin, dsum, n = 0, p, 0, 0
- for d in deg_sequence:
- # Reject if degree is negative or larger than the sequence length
- if d < 0 or d >= p:
- raise nx.NetworkXUnfeasible
- # Process only the non-zero integers
- elif d > 0:
- dmax, dmin, dsum, n = max(dmax, d), min(dmin, d), dsum + d, n + 1
- num_degs[d] += 1
- # Reject sequence if it has odd sum or is oversaturated
- if dsum % 2 or dsum > n * (n - 1):
- raise nx.NetworkXUnfeasible
- return dmax, dmin, dsum, n, num_degs
- def is_valid_degree_sequence_havel_hakimi(deg_sequence):
- r"""Returns True if deg_sequence can be realized by a simple graph.
- The validation proceeds using the Havel-Hakimi theorem
- [havel1955]_, [hakimi1962]_, [CL1996]_.
- Worst-case run time is $O(s)$ where $s$ is the sum of the sequence.
- Parameters
- ----------
- deg_sequence : list
- A list of integers where each element specifies the degree of a node
- in a graph.
- Returns
- -------
- valid : bool
- True if deg_sequence is graphical and False if not.
- Notes
- -----
- The ZZ condition says that for the sequence d if
- .. math::
- |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)}
- then d is graphical. This was shown in Theorem 6 in [1]_.
- References
- ----------
- .. [1] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory
- of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992).
- .. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs"
- Casopis Pest. Mat. 80, 477-480, 1955.
- .. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as
- Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962.
- .. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs",
- Chapman and Hall/CRC, 1996.
- """
- try:
- dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence)
- except nx.NetworkXUnfeasible:
- return False
- # Accept if sequence has no non-zero degrees or passes the ZZ condition
- if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1):
- return True
- modstubs = [0] * (dmax + 1)
- # Successively reduce degree sequence by removing the maximum degree
- while n > 0:
- # Retrieve the maximum degree in the sequence
- while num_degs[dmax] == 0:
- dmax -= 1
- # If there are not enough stubs to connect to, then the sequence is
- # not graphical
- if dmax > n - 1:
- return False
- # Remove largest stub in list
- num_degs[dmax], n = num_degs[dmax] - 1, n - 1
- # Reduce the next dmax largest stubs
- mslen = 0
- k = dmax
- for i in range(dmax):
- while num_degs[k] == 0:
- k -= 1
- num_degs[k], n = num_degs[k] - 1, n - 1
- if k > 1:
- modstubs[mslen] = k - 1
- mslen += 1
- # Add back to the list any non-zero stubs that were removed
- for i in range(mslen):
- stub = modstubs[i]
- num_degs[stub], n = num_degs[stub] + 1, n + 1
- return True
- def is_valid_degree_sequence_erdos_gallai(deg_sequence):
- r"""Returns True if deg_sequence can be realized by a simple graph.
- The validation is done using the Erdős-Gallai theorem [EG1960]_.
- Parameters
- ----------
- deg_sequence : list
- A list of integers
- Returns
- -------
- valid : bool
- True if deg_sequence is graphical and False if not.
- Notes
- -----
- This implementation uses an equivalent form of the Erdős-Gallai criterion.
- Worst-case run time is $O(n)$ where $n$ is the length of the sequence.
- Specifically, a sequence d is graphical if and only if the
- sum of the sequence is even and for all strong indices k in the sequence,
- .. math::
- \sum_{i=1}^{k} d_i \leq k(k-1) + \sum_{j=k+1}^{n} \min(d_i,k)
- = k(n-1) - ( k \sum_{j=0}^{k-1} n_j - \sum_{j=0}^{k-1} j n_j )
- A strong index k is any index where d_k >= k and the value n_j is the
- number of occurrences of j in d. The maximal strong index is called the
- Durfee index.
- This particular rearrangement comes from the proof of Theorem 3 in [2]_.
- The ZZ condition says that for the sequence d if
- .. math::
- |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)}
- then d is graphical. This was shown in Theorem 6 in [2]_.
- References
- ----------
- .. [1] A. Tripathi and S. Vijay. "A note on a theorem of Erdős & Gallai",
- Discrete Mathematics, 265, pp. 417-420 (2003).
- .. [2] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory
- of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992).
- .. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960.
- """
- try:
- dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence)
- except nx.NetworkXUnfeasible:
- return False
- # Accept if sequence has no non-zero degrees or passes the ZZ condition
- if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1):
- return True
- # Perform the EG checks using the reformulation of Zverovich and Zverovich
- k, sum_deg, sum_nj, sum_jnj = 0, 0, 0, 0
- for dk in range(dmax, dmin - 1, -1):
- if dk < k + 1: # Check if already past Durfee index
- return True
- if num_degs[dk] > 0:
- run_size = num_degs[dk] # Process a run of identical-valued degrees
- if dk < k + run_size: # Check if end of run is past Durfee index
- run_size = dk - k # Adjust back to Durfee index
- sum_deg += run_size * dk
- for v in range(run_size):
- sum_nj += num_degs[k + v]
- sum_jnj += (k + v) * num_degs[k + v]
- k += run_size
- if sum_deg > k * (n - 1) - k * sum_nj + sum_jnj:
- return False
- return True
- def is_multigraphical(sequence):
- """Returns True if some multigraph can realize the sequence.
- Parameters
- ----------
- sequence : list
- A list of integers
- Returns
- -------
- valid : bool
- True if deg_sequence is a multigraphic degree sequence and False if not.
- Notes
- -----
- The worst-case run time is $O(n)$ where $n$ is the length of the sequence.
- References
- ----------
- .. [1] S. L. Hakimi. "On the realizability of a set of integers as
- degrees of the vertices of a linear graph", J. SIAM, 10, pp. 496-506
- (1962).
- """
- try:
- deg_sequence = nx.utils.make_list_of_ints(sequence)
- except nx.NetworkXError:
- return False
- dsum, dmax = 0, 0
- for d in deg_sequence:
- if d < 0:
- return False
- dsum, dmax = dsum + d, max(dmax, d)
- if dsum % 2 or dsum < 2 * dmax:
- return False
- return True
- def is_pseudographical(sequence):
- """Returns True if some pseudograph can realize the sequence.
- Every nonnegative integer sequence with an even sum is pseudographical
- (see [1]_).
- Parameters
- ----------
- sequence : list or iterable container
- A sequence of integer node degrees
- Returns
- -------
- valid : bool
- True if the sequence is a pseudographic degree sequence and False if not.
- Notes
- -----
- The worst-case run time is $O(n)$ where n is the length of the sequence.
- References
- ----------
- .. [1] F. Boesch and F. Harary. "Line removal algorithms for graphs
- and their degree lists", IEEE Trans. Circuits and Systems, CAS-23(12),
- pp. 778-782 (1976).
- """
- try:
- deg_sequence = nx.utils.make_list_of_ints(sequence)
- except nx.NetworkXError:
- return False
- return sum(deg_sequence) % 2 == 0 and min(deg_sequence) >= 0
- def is_digraphical(in_sequence, out_sequence):
- r"""Returns True if some directed graph can realize the in- and out-degree
- sequences.
- Parameters
- ----------
- in_sequence : list or iterable container
- A sequence of integer node in-degrees
- out_sequence : list or iterable container
- A sequence of integer node out-degrees
- Returns
- -------
- valid : bool
- True if in and out-sequences are digraphic False if not.
- Notes
- -----
- This algorithm is from Kleitman and Wang [1]_.
- The worst case runtime is $O(s \times \log n)$ where $s$ and $n$ are the
- sum and length of the sequences respectively.
- References
- ----------
- .. [1] D.J. Kleitman and D.L. Wang
- Algorithms for Constructing Graphs and Digraphs with Given Valences
- and Factors, Discrete Mathematics, 6(1), pp. 79-88 (1973)
- """
- try:
- in_deg_sequence = nx.utils.make_list_of_ints(in_sequence)
- out_deg_sequence = nx.utils.make_list_of_ints(out_sequence)
- except nx.NetworkXError:
- return False
- # Process the sequences and form two heaps to store degree pairs with
- # either zero or non-zero out degrees
- sumin, sumout, nin, nout = 0, 0, len(in_deg_sequence), len(out_deg_sequence)
- maxn = max(nin, nout)
- maxin = 0
- if maxn == 0:
- return True
- stubheap, zeroheap = [], []
- for n in range(maxn):
- in_deg, out_deg = 0, 0
- if n < nout:
- out_deg = out_deg_sequence[n]
- if n < nin:
- in_deg = in_deg_sequence[n]
- if in_deg < 0 or out_deg < 0:
- return False
- sumin, sumout, maxin = sumin + in_deg, sumout + out_deg, max(maxin, in_deg)
- if in_deg > 0:
- stubheap.append((-1 * out_deg, -1 * in_deg))
- elif out_deg > 0:
- zeroheap.append(-1 * out_deg)
- if sumin != sumout:
- return False
- heapq.heapify(stubheap)
- heapq.heapify(zeroheap)
- modstubs = [(0, 0)] * (maxin + 1)
- # Successively reduce degree sequence by removing the maximum out degree
- while stubheap:
- # Take the first value in the sequence with non-zero in degree
- (freeout, freein) = heapq.heappop(stubheap)
- freein *= -1
- if freein > len(stubheap) + len(zeroheap):
- return False
- # Attach out stubs to the nodes with the most in stubs
- mslen = 0
- for i in range(freein):
- if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0]):
- stubout = heapq.heappop(zeroheap)
- stubin = 0
- else:
- (stubout, stubin) = heapq.heappop(stubheap)
- if stubout == 0:
- return False
- # Check if target is now totally connected
- if stubout + 1 < 0 or stubin < 0:
- modstubs[mslen] = (stubout + 1, stubin)
- mslen += 1
- # Add back the nodes to the heap that still have available stubs
- for i in range(mslen):
- stub = modstubs[i]
- if stub[1] < 0:
- heapq.heappush(stubheap, stub)
- else:
- heapq.heappush(zeroheap, stub[0])
- if freeout < 0:
- heapq.heappush(zeroheap, freeout)
- return True
|