Path: blob/master/src/sage/combinat/degree_sequences.pyx
8817 views
r"""1Degree sequences23The present module implements the ``DegreeSequences`` class, whose instances4represent the integer sequences of length `n`::56sage: DegreeSequences(6)7Degree sequences on 6 elements89With the object ``DegreeSequences(n)``, one can :1011* Check whether a sequence is indeed a degree sequence ::1213sage: DS = DegreeSequences(5)14sage: [4, 3, 3, 3, 3] in DS15True16sage: [4, 4, 0, 0, 0] in DS17False1819* List all the possible degree sequences of length `n`::2021sage: for seq in DegreeSequences(4):22... print seq23[0, 0, 0, 0]24[1, 1, 0, 0]25[2, 1, 1, 0]26[3, 1, 1, 1]27[1, 1, 1, 1]28[2, 2, 1, 1]29[2, 2, 2, 0]30[3, 2, 2, 1]31[2, 2, 2, 2]32[3, 3, 2, 2]33[3, 3, 3, 3]3435.. NOTE::3637Given a degree sequence, one can obtain a graph realizing it by using38:meth:`sage.graphs.graph_generators.graphs.DegreeSequence`. For instance ::3940sage: ds = [3, 3, 2, 2, 2, 2, 2, 1, 1, 0]41sage: g = graphs.DegreeSequence(ds)42sage: g.degree_sequence()43[3, 3, 2, 2, 2, 2, 2, 1, 1, 0]4445Definitions46~~~~~~~~~~~4748A sequence of integers `d_1,...,d_n` is said to be a *degree sequence* (or49*graphic* sequence) if there exists a graph in which vertex `i` is of degree50`d_i`. It is often required to be *non-increasing*, i.e. that `d_1 \geq ... \geq51d_n`.5253An integer sequence need not necessarily be a degree sequence. Indeed, in a54degree sequence of length `n` no integer can be larger than `n-1` -- the degree55of a vertex is at most `n-1` -- and the sum of them is at most `n(n-1)`.5657Degree sequences are completely characterized by a result from Erdos and Gallai:5859**Erdos and Gallai:** *The sequence of integers* `d_1\geq ... \geq d_n` *is a60degree sequence if and only if* `\sum_i d_i` is even and `\forall i`6162.. MATH::63\sum_{j\leq i}d_j \leq j(j-1) + \sum_{j>i}\min(d_j,i)6465Alternatively, a degree sequence can be defined recursively :6667**Havel and Hakimi:** *The sequence of integers* `d_1\geq ... \geq d_n` *is a68degree sequence if and only if* `d_2-1,...,d_{d_1+1}-1, d_{d_1+2}, ...,d_n` *is69also a degree sequence.*7071Or equivalently :7273**Havel and Hakimi (bis):** *If there is a realization of an integer sequence as74a graph (i.e. if the sequence is a degree sequence), then it can be realized in75such a way that the vertex of maximum degree* `\Delta` *is adjacent to the*76`\Delta` *vertices of highest degree (except itself, of course).*777879Algorithms80~~~~~~~~~~8182**Checking whether a given sequence is a degree sequence**8384This is tested using Erdos and Gallai's criterion. It is also checked that the85given sequence is non-increasing and has length `n`.8687**Iterating through the sequences of length** `n`8889From Havel and Hakimi's recursive definition of a degree sequence, one can build90an enumeration algorithm as done in [RCES]_. It consists in trying to **extend**91a current degree sequence on `n` elements into a degree sequence on `n+1`92elements by adding a vertex of degree larger than those already present in the93sequence. This can be seen as **reversing** the reduction operation described in94Havel and Hakimi's characterization. This operation can appear in several95different ways :9697* Extensions of a degree sequence that do **not** change the value of the98maximum element99100* If the maximum element of a given degree sequence is `0`, then one can101remove it to reduce the sequence, following Havel and Hakimi's102rule. Conversely, if the maximum element of the (current) sequence is103`0`, then one can always extend it by adding a new element of degree104`0` to the sequence.105106.. MATH::1070, 0, 0 \xrightarrow{Extension} {\bf 0}, 0, 0, 0 \xrightarrow{Extension} {\bf 0}, 0, 0, ..., 0, 0, 0 \xrightarrow{Reduction} 0, 0, 0, 0 \xrightarrow{Reduction} 0, 0, 0108109* If there are at least `\Delta+1` elements of (maximum) degree `\Delta`110in a given degree sequence, then one can reduce it by removing a111vertex of degree `\Delta` and decreasing the values of `\Delta`112elements of value `\Delta` to `\Delta-1`. Conversely, if the maximum113element of the (current) sequence is `d>0`, then one can add a new114element of degree `d` to the sequence if it can be linked to `d`115elements of (current) degree `d-1`. Those `d` vertices of degree `d-1`116hence become vertices of degree `d`, and so `d` elements of degree117`d-1` are removed from the sequence while `d+1` elements of degree `d`118are added to it.119120.. MATH::1213, 2, 2, 2, 1 \xrightarrow{Extension} {\bf 3}, 3, (2+1), (2+1), (2+1), 1 = {\bf 3}, 3, 3, 3, 3, 1 \xrightarrow{Reduction} 3, 2, 2, 2, 1122123* Extension of a degree sequence that changes the value of the maximum124element :125126* In the general case, i.e. when the number of elements of value127`\Delta,\Delta-1` is small compared to `\Delta` (i.e. the maximum128element of a given degree sequence), reducing a sequence strictly129decreases the value of the maximum element. According to Havel and130Hakimi's characterization there is only **one** way to reduce a131sequence, but reversing this operation is more complicated than in the132previous cases. Indeed, the following extensions are perfectly valid133according to the reduction rule.134135.. MATH::1362,1,1,0,0\xrightarrow{Extension} {\bf 3}, (2+1), (1+1), (1+1), 0, 0 = 3, 3, 2, 2, 0, 0 \xrightarrow{Reduction} 2, 1, 1, 0, 0\\1372,1,1,0,0\xrightarrow{Extension} {\bf 3}, (2+1), (1+1), 1, (0+1), 0 = 3, 3, 2, 1, 1, 0 \xrightarrow{Reduction} 2, 1, 1, 0, 0\\1382,1,1,0,0\xrightarrow{Extension} {\bf 3}, (2+1), 1, 1, (0+1), (0+1) = 3, 3, 1, 1, 1, 1 \xrightarrow{Reduction} 2, 1, 1, 0, 0\\139...140141In order to extend a current degree sequence while strictly increasing142its maximum degree, it is equivalent to pick a set `I` of elements of143the degree sequence with `|I|>\Delta` in such a way that the144`(d_i+1)_{i\in I}` are the `|I|` maximum elements of the sequence145`(d_i+\genfrac{}{}{0pt}{}{1\text{ if }i\in I}{0\text{ if }i\not \in146I})_{1\leq i \leq n}`, and to add to this new sequence an element of147value `|I|`. The non-increasing sequence containing the elements `|I|`148and `(d_i+\genfrac{}{}{0pt}{}{1\text{ if }i\in I}{0\text{ if }i\not149\in I})_{1\leq i \leq n}` can be reduced to `(d_i)_{1\leq i \leq n}`150by Havel and Hakimi's rule.151152.. MATH::153... 1, 1, 2, {\bf 2}, {\bf 2}, 2, 2, 3, 3, \underline{3}, {\bf 3}, {\bf 3}, {\bf 4}, {\bf 6}, ... \xrightarrow{Extension} ... 1, 1, 2, 2, 2, 3, 3, \underline{3}, {\bf 3}, {\bf 3}, {\bf 4}, {\bf 4}, {\bf 5}, {\bf 7}, ...154155The number of possible sets `I` having this property (i.e. the number156of possible extensions of a sequence) is smaller than it157seems. Indeed, by definition, if `j\not \in I` then for all `i\in I`158the inequality `d_j\leq d_i+1` holds. Hence, each set `I` is entirely159determined by the largest element `d_k` of the sequence that it does160**not** contain (hence `I` contains `\{1,...,k-1\}`), and by the161cardinalities of `\{i\in I:d_i= d_k\}` and `\{i\in I:d_i= d_k-1\}`.162163.. MATH::164I = \{i \in I : d_i= d_k \} \cup \{i \in I : d_i= d_k-1 \} \cup \{i : d_i> d_k \}165166The number of possible extensions is hence at most cubic, and is167easily enumerated.168169About the implementation170~~~~~~~~~~~~~~~~~~~~~~~~171172In the actual implementation of the enumeration algorithm, the degree sequence173is stored differently for reasons of efficiency.174175Indeed, when enumerating all the degree sequences of length `n`, Sage first176allocates an array ``seq`` of `n+1` integers where ``seq[i]`` is the number of177elements of value ``i`` in the current sequence. Obviously, ``seq[n]=0`` holds178in permanence : it is useful to allocate a larger array than necessary to179simplify the code. The ``seq`` array is a global variable.180181The recursive function ``enum(depth, maximum)`` is the one building the list of182sequences. It builds the list of degree sequences of length `n` which *extend*183the sequence currently stored in ``seq[0]...seq[depth-1]``. When it is called,184``maximum`` must be set to the maximum value of an element in the partial185sequence ``seq[0]...seq[depth-1]``.186187If during its run the function ``enum`` heavily works on the content of the188``seq`` array, the value of ``seq`` is the **same** before and after the run of189``enum``.190191**Extending the current partial sequence**192193The two cases for which the maximum degree of the partial sequence does not194change are easy to detect. It is (sligthly) harder to enumerate all the sets `I`195corresponding to possible extensions of the partial sequence. As said196previously, to each set `I` one can associate an integer ``current_box`` such197that `I` contains all the `i` satisfying `d_i>current\_box`. The variable198``taken`` represents the number of all such elements `i`, so that when199enumerating all possible sets `I` in the algorithm we have the equality200201.. MATH::202I = \text{taken }+\text{ number of elements of value }current\_box+ \text{ number of elements of value }current\_box-1203204References205~~~~~~~~~~206207.. [RCES] Alley CATs in search of good homes208Ruskey, R. Cohen, P. Eades, A. Scott209Congressus numerantium, 1994210Pages 97--110211212213Author214~~~~~~215216Nathann Cohen217218Tests219~~~~~220221The sequences produced by random graphs *are* degree sequences::222223sage: n = 30224sage: DS = DegreeSequences(30)225sage: for i in range(10):226... g = graphs.RandomGNP(n,.2)227... if not g.degree_sequence() in DS:228... print "Something is very wrong !"229230Checking that we indeed enumerate *all* the degree sequences for `n=5`::231232sage: ds1 = Set([tuple(g.degree_sequence()) for g in graphs(5)])233sage: ds2 = Set(map(tuple,list(DegreeSequences(5))))234sage: ds1 == ds2235True236237Checking the consistency of enumeration and test::238239sage: DS = DegreeSequences(6)240sage: all(seq in DS for seq in DS)241True242243.. WARNING::244245For the moment, iterating over all degree sequences involves building the246list of them first, then iterate on this list. This is obviously bad, as it247requires uselessly a **lot** of memory for large values of `n`.248249As soon as the ``yield`` keyword is available in Cython this should be250changed. Updating the code does not require more than a couple of minutes.251252"""253254##############################################################################255# Copyright (C) 2011 Nathann Cohen <[email protected]>256# Distributed under the terms of the GNU General Public License (GPL)257# The full text of the GPL is available at:258# http://www.gnu.org/licenses/259##############################################################################260261from sage.libs.gmp.all cimport mpz_t262from sage.libs.gmp.all cimport *263from sage.rings.integer cimport Integer264include 'sage/ext/stdsage.pxi'265include 'sage/ext/cdefs.pxi'266include "sage/ext/interrupt.pxi"267268269cdef unsigned char * seq270cdef list sequences271272class DegreeSequences:273274def __init__(self, n):275r"""276Degree Sequences277278An instance of this class represents the degree sequences of graphs on a279given number `n` of vertices. It can be used to list and count them, as280well as to test whether a sequence is a degree sequence. For more281information, please refer to the documentation of the282:mod:`DegreeSequence<sage.combinat.degree_sequences>` module.283284EXAMPLE::285286sage: DegreeSequences(8)287Degree sequences on 8 elements288sage: [3,3,2,2,2,2,2,2] in DegreeSequences(8)289True290291"""292self._n = n293294def __contains__(self, seq):295"""296Checks whether a given integer sequence is the degree sequence297of a graph on `n` elements298299EXAMPLE::300301sage: [3,3,2,2,2,2,2,2] in DegreeSequences(8)302True303304TESTS:305306:trac:`15503`::307308sage: [2,2,2,2,1,1,1] in DegreeSequences(7)309False310"""311cdef int n = self._n312if len(seq)!=n:313return False314315# Is the sum even ?316if sum(seq)%2 == 1:317return False318319# Partial represents the left side of Erdos and Gallai's inequality,320# i.e. the sum of the i first integers.321cdef int partial = 0322cdef int i,d,dd, right323324# Temporary variable to ensure that the sequence is indeed325# non-increasing326cdef int prev = n-1327328for i,d in enumerate(seq):329330# Non-increasing ?331if d > prev:332return False333else:334prev = d335336# Updating the partial sum337partial += d338339# Evaluating the right hand side340right = i*(i+1)341for dd in seq[i+1:]:342right += min(dd,i+1)343344# Comparing the two345if partial > right:346return False347348return True349350def __repr__(self):351"""352Representing the element353354TEST::355356sage: DegreeSequences(6)357Degree sequences on 6 elements358"""359return "Degree sequences on "+str(self._n)+" elements"360361def __iter__(self):362"""363Iterate over all the degree sequences.364365TODO: THIS SHOULD BE UPDATED AS SOON AS THE YIELD KEYWORD APPEARS IN366CYTHON. See comment in the class' documentation.367368EXAMPLE::369370sage: DS = DegreeSequences(6)371sage: all(seq in DS for seq in DS)372True373"""374375init(self._n)376return iter(sequences)377378def __dealloc__():379"""380Freeing the memory381"""382if seq != NULL:383sage_free(seq)384385cdef init(int n):386"""387Initializes the memory and starts the enumeration algorithm.388"""389global seq390global N391global sequences392393if n == 0:394return [[]]395elif n == 1:396return [[0]]397398sig_on()399seq = <unsigned char *> sage_malloc((n+1)*sizeof(unsigned char))400memset(seq,0,(n+1)*sizeof(unsigned char))401sig_off()402403# We begin with one vertex of degree 0404seq[0] = 1405406N = n407sequences = []408enum(1,0)409sage_free(seq)410return sequences411412cdef inline add_seq():413"""414This function is called whenever a sequence is found.415416Build the degree sequence corresponding to the current state of the417algorithm and adds it to the sequences list.418"""419global sequences420global N421global seq422423cdef list s = []424cdef int i, j425426for N > i >= 0:427for 0<= j < seq[i]:428s.append(i)429430sequences.append(s)431432cdef void enum(int k, int M):433"""434Main function. For an explanation of the algorithm please refer to the435class' documentation.436437INPUT:438439* ``k`` -- depth of the partial degree sequence440* ``M`` -- value of a maximum element in the partial degree sequence441"""442cdef int i,j443global seq444cdef int taken = 0445cdef int current_box446cdef int n_current_box447cdef int n_previous_box448cdef int new_vertex449450# Have we found a new degree sequence ? End of recursion !451if k == N:452add_seq()453return454455sig_on()456457#############################################458# Creating vertices of Vertices of degree M #459#############################################460461# If 0 is the current maximum degree, we can always extend the degree462# sequence with another 0463if M == 0:464465seq[0] += 1466enum(k+1, M)467seq[0] -= 1468469# We need not automatically increase the degree at each step. In this case,470# we have no other choice but to link the new vertex of degree M to vertices471# of degree M-1, which will become vertices of degree M too.472elif seq[M-1] >= M:473474seq[M] += M+1475seq[M-1] -= M476477enum(k+1, M)478479seq[M] -= M+1480seq[M-1] += M481482###############################################483# Creating vertices of Vertices of degree > M #484###############################################485486for M >= current_box > 0:487488# If there is not enough vertices in the boxes available489if taken + (seq[current_box] - 1) + seq[current_box-1] <= M:490taken += seq[current_box]491seq[current_box+1] += seq[current_box]492seq[current_box] = 0493continue494495# The degree of the new vertex will be taken + i + j where :496#497# * i is the number of vertices taken in the *current* box498# * j the number of vertices taken in the *previous* one499500n_current_box = seq[current_box]501n_previous_box = seq[current_box-1]502503# Note to self, and others :504#505# In the following lines, there are many incrementation/decrementation506# that *may* be replaced by only +1 and -1 and save some507# instructions. This would involve adding several "if", and I feared it508# would make the code even uglier. If you are willing to give it a try,509# **please check the results** ! It is trickier that it seems ! Even510# changing the lower bounds in the for loops would require tests511# afterwards.512513for max(0,((M+1)-n_previous_box-taken)) <= i < n_current_box:514seq[current_box] -= i515seq[current_box+1] += i516517for max(0,((M+1)-taken-i)) <= j <= n_previous_box:518seq[current_box-1] -= j519seq[current_box] += j520521new_vertex = taken + i + j522seq[new_vertex] += 1523enum(k+1,new_vertex)524seq[new_vertex] -= 1525526seq[current_box-1] += j527seq[current_box] -= j528529seq[current_box] += i530seq[current_box+1] -= i531532taken += n_current_box533seq[current_box] = 0534seq[current_box+1] += n_current_box535536# Corner case537#538# Now current_box = 0. All the vertices of nonzero degree are taken, we just539# want to know how many vertices of degree 0 will be neighbors of the new540# vertex.541for max(0,((M+1)-taken)) <= i <= seq[0]:542543seq[1] += i544seq[0] -= i545seq[taken+i] += 1546547enum(k+1, taken+i)548549seq[taken+i] -= 1550seq[1] -= i551seq[0] += i552553# Shift everything back to normal ! ( cell N is always equal to 0)554for 1 <= i < N:555seq[i] = seq[i+1]556557sig_off()558559560