Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/combinat/degree_sequences.pyx
4058 views
1
r"""
2
Degree sequences
3
4
The present module implements the ``DegreeSequences`` class, whose instances
5
represent the integer sequences of length `n`::
6
7
sage: DegreeSequences(6)
8
Degree sequences on 6 elements
9
10
With the object ``DegreeSequences(n)``, one can :
11
12
* Check whether a sequence is indeed a degree sequence ::
13
14
sage: DS = DegreeSequences(5)
15
sage: [4, 3, 3, 3, 3] in DS
16
True
17
sage: [4, 4, 0, 0, 0] in DS
18
False
19
20
* List all the possible degree sequences of length `n`::
21
22
sage: for seq in DegreeSequences(4):
23
... print seq
24
[0, 0, 0, 0]
25
[1, 1, 0, 0]
26
[2, 1, 1, 0]
27
[3, 1, 1, 1]
28
[1, 1, 1, 1]
29
[2, 2, 1, 1]
30
[2, 2, 2, 0]
31
[3, 2, 2, 1]
32
[2, 2, 2, 2]
33
[3, 3, 2, 2]
34
[3, 3, 3, 3]
35
36
.. NOTE::
37
38
Given a degree sequence, one can obtain a graph realizing it by using
39
:meth:`sage.graphs.graph_generators.graphs.DegreeSequence`. For instance ::
40
41
sage: ds = [3, 3, 2, 2, 2, 2, 2, 1, 1, 0]
42
sage: g = graphs.DegreeSequence(ds)
43
sage: g.degree_sequence()
44
[3, 3, 2, 2, 2, 2, 2, 1, 1, 0]
45
46
Definitions
47
~~~~~~~~~~~
48
49
A sequence of integers `d_1,...,d_n` is said to be a *degree sequence* (or
50
*graphic* sequence) if there exists a graph in which vertex `i` is of degree
51
`d_i`. It is often required to be *non-increasing*, i.e. that `d_1 \geq ... \geq
52
d_n`.
53
54
An integer sequence need not necessarily be a degree sequence. Indeed, in a
55
degree sequence of length `n` no integer can be larger than `n-1` -- the degree
56
of a vertex is at most `n-1` -- and the sum of them is at most `n(n-1)`.
57
58
Degree sequences are completely characterized by a result from Erdos and Gallai:
59
60
**Erdos and Gallai:** *The sequence of integers* `d_1\geq ... \geq d_n` *is a
61
degree sequence if and only if* `\forall i`
62
63
.. MATH::
64
\sum_{j\leq i}d_j \leq j(j-1) + \sum_{j>i}\min(d_j,i)
65
66
Alternatively, a degree sequence can be defined recursively :
67
68
**Havel and Hakimi:** *The sequence of integers* `d_1\geq ... \geq d_n` *is a
69
degree sequence if and only if* `d_2-1,...,d_{d_1+1}-1, d_{d_1+2}, ...,d_n` *is
70
also a degree sequence.*
71
72
Or equivalently :
73
74
**Havel and Hakimi (bis):** *If there is a realization of an integer sequence as
75
a graph (i.e. if the sequence is a degree sequence), then it can be realized in
76
such a way that the vertex of maximum degree* `\Delta` *is adjacent to the*
77
`\Delta` *vertices of highest degree (except itself, of course).*
78
79
80
Algorithms
81
~~~~~~~~~~
82
83
**Checking whether a given sequence is a degree sequence**
84
85
This is tested using Erdos and Gallai's criterion. It is also checked that the
86
given sequence is non-increasing and has length `n`.
87
88
**Iterating through the sequences of length** `n`
89
90
From Havel and Hakimi's recursive definition of a degree sequence, one can build
91
an enumeration algorithm as done in [RCES]_. It consists in trying to **extend**
92
a current degree sequence on `n` elements into a degree sequence on `n+1`
93
elements by adding a vertex of degree larger than those already present in the
94
sequence. This can be seen as **reversing** the reduction operation described in
95
Havel and Hakimi's characterization. This operation can appear in several
96
different ways :
97
98
* Extensions of a degree sequence that do **not** change the value of the
99
maximum element
100
101
* If the maximum element of a given degree sequence is `0`, then one can
102
remove it to reduce the sequence, following Havel and Hakimi's
103
rule. Conversely, if the maximum element of the (current) sequence is
104
`0`, then one can always extend it by adding a new element of degree
105
`0` to the sequence.
106
107
.. MATH::
108
0, 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, 0
109
110
* If there are at least `\Delta+1` elements of (maximum) degree `\Delta`
111
in a given degree sequence, then one can reduce it by removing a
112
vertex of degree `\Delta` and decreasing the values of `\Delta`
113
elements of value `\Delta` to `\Delta-1`. Conversely, if the maximum
114
element of the (current) sequence is `d>0`, then one can add a new
115
element of degree `d` to the sequence if it can be linked to `d`
116
elements of (current) degree `d-1`. Those `d` vertices of degree `d-1`
117
hence become vertices of degree `d`, and so `d` elements of degree
118
`d-1` are removed from the sequence while `d+1` elements of degree `d`
119
are added to it.
120
121
.. MATH::
122
3, 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, 1
123
124
* Extension of a degree sequence that changes the value of the maximum
125
element :
126
127
* In the general case, i.e. when the number of elements of value
128
`\Delta,\Delta-1` is small compared to `\Delta` (i.e. the maximum
129
element of a given degree sequence), reducing a sequence strictly
130
decreases the value of the maximum element. According to Havel and
131
Hakimi's characterization there is only **one** way to reduce a
132
sequence, but reversing this operation is more complicated than in the
133
previous cases. Indeed, the following extensions are perfectly valid
134
according to the reduction rule.
135
136
.. MATH::
137
2,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\\
138
2,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\\
139
2,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\\
140
...
141
142
In order to extend a current degree sequence while strictly increasing
143
its maximum degree, it is equivalent to pick a set `I` of elements of
144
the degree sequence with `|I|>\Delta` in such a way that the
145
`(d_i+1)_{i\in I}` are the `|I|` maximum elements of the sequence
146
`(d_i+\genfrac{}{}{0pt}{}{1\text{ if }i\in I}{0\text{ if }i\not \in
147
I})_{1\leq i \leq n}`, and to add to this new sequence an element of
148
value `|I|`. The non-increasing sequence containing the elements `|I|`
149
and `(d_i+\genfrac{}{}{0pt}{}{1\text{ if }i\in I}{0\text{ if }i\not
150
\in I})_{1\leq i \leq n}` can be reduced to `(d_i)_{1\leq i \leq n}`
151
by Havel and Hakimi's rule.
152
153
.. MATH::
154
... 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}, ...
155
156
The number of possible sets `I` having this property (i.e. the number
157
of possible extensions of a sequence) is smaller than it
158
seems. Indeed, by definition, if `j\not \in I` then for all `i\in I`
159
the inequality `d_j\leq d_i+1` holds. Hence, each set `I` is entirely
160
determined by the largest element `d_k` of the sequence that it does
161
**not** contain (hence `I` contains `\{1,...,k-1\}`), and by the
162
cardinalities of `\{i\in I:d_i= d_k\}` and `\{i\in I:d_i= d_k-1\}`.
163
164
.. MATH::
165
I = \{i \in I : d_i= d_k \} \cup \{i \in I : d_i= d_k-1 \} \cup \{i : d_i> d_k \}
166
167
The number of possible extensions is hence at most cubic, and is
168
easily enumerated.
169
170
About the implementation
171
~~~~~~~~~~~~~~~~~~~~~~~~
172
173
In the actual implementation of the enumeration algorithm, the degree sequence
174
is stored differently for reasons of efficiency.
175
176
Indeed, when enumerating all the degree sequences of length `n`, Sage first
177
allocates an array ``seq`` of `n+1` integers where ``seq[i]`` is the number of
178
elements of value ``i`` in the current sequence. Obviously, ``seq[n]=0`` holds
179
in permanence : it is useful to allocate a larger array than necessary to
180
simplify the code. The ``seq`` array is a global variable.
181
182
The recursive function ``enum(depth, maximum)`` is the one building the list of
183
sequences. It builds the list of degree sequences of length `n` which *extend*
184
the sequence currently stored in ``seq[0]...seq[depth-1]``. When it is called,
185
``maximum`` must be set to the maximum value of an element in the partial
186
sequence ``seq[0]...seq[depth-1]``.
187
188
If during its run the function ``enum`` heavily works on the content of the
189
``seq`` array, the value of ``seq`` is the **same** before and after the run of
190
``enum``.
191
192
**Extending the current partial sequence**
193
194
The two cases for which the maximum degree of the partial sequence does not
195
change are easy to detect. It is (sligthly) harder to enumerate all the sets `I`
196
corresponding to possible extensions of the partial sequence. As said
197
previously, to each set `I` one can associate an integer ``current_box`` such
198
that `I` contains all the `i` satisfying `d_i>current\_box`. The variable
199
``taken`` represents the number of all such elements `i`, so that when
200
enumerating all possible sets `I` in the algorithm we have the equality
201
202
.. MATH::
203
I = \text{taken }+\text{ number of elements of value }current\_box+ \text{ number of elements of value }current\_box-1
204
205
References
206
~~~~~~~~~~
207
208
.. [RCES] Alley CATs in search of good homes
209
Ruskey, R. Cohen, P. Eades, A. Scott
210
Congressus numerantium, 1994
211
Pages 97--110
212
213
214
Author
215
~~~~~~
216
217
Nathann Cohen
218
219
Tests
220
~~~~~
221
222
The sequences produced by random graphs *are* degree sequences::
223
224
sage: n = 30
225
sage: DS = DegreeSequences(30)
226
sage: for i in range(10):
227
... g = graphs.RandomGNP(n,.2)
228
... if not g.degree_sequence() in DS:
229
... print "Something is very wrong !"
230
231
Checking that we indeed enumerate *all* the degree sequences for `n=5`::
232
233
sage: ds1 = Set([tuple(g.degree_sequence()) for g in graphs(5)])
234
sage: ds2 = Set(map(tuple,list(DegreeSequences(5))))
235
sage: ds1 == ds2
236
True
237
238
Checking the consistency of enumeration and test::
239
240
sage: DS = DegreeSequences(6)
241
sage: all(seq in DS for seq in DS)
242
True
243
244
.. WARNING::
245
246
For the moment, iterating over all degree sequences involves building the
247
list of them first, then iterate on this list. This is obviously bad, as it
248
requires uselessly a **lot** of memory for large values of `n`.
249
250
As soon as the ``yield`` keyword is available in Cython this should be
251
changed. Updating the code does not require more than a couple of minutes.
252
253
"""
254
255
##############################################################################
256
# Copyright (C) 2011 Nathann Cohen <[email protected]>
257
# Distributed under the terms of the GNU General Public License (GPL)
258
# The full text of the GPL is available at:
259
# http://www.gnu.org/licenses/
260
##############################################################################
261
262
from sage.libs.gmp.all cimport mpz_t
263
from sage.libs.gmp.all cimport *
264
from sage.rings.integer cimport Integer
265
include '../../../../devel/sage/sage/ext/stdsage.pxi'
266
include '../ext/cdefs.pxi'
267
include "../ext/interrupt.pxi"
268
269
270
cdef unsigned char * seq
271
cdef list sequences
272
273
class DegreeSequences:
274
275
def __init__(self, n):
276
r"""
277
Degree Sequences
278
279
An instance of this class represents the degree sequences of graphs on a
280
given number `n` of vertices. It can be used to list and count them, as
281
well as to test whether a sequence is a degree sequence. For more
282
information, please refer to the documentation of the
283
:mod:`DegreeSequence<sage.combinat.degree_sequences>` module.
284
285
EXAMPLE::
286
287
sage: DegreeSequences(8)
288
Degree sequences on 8 elements
289
sage: [3,3,2,2,2,2,2,2] in DegreeSequences(8)
290
True
291
292
"""
293
self._n = n
294
295
def __contains__(self, seq):
296
"""
297
Checks whether a given integer sequence is the degree sequence
298
of a graph on `n` elements
299
300
EXAMPLE::
301
302
sage: [3,3,2,2,2,2,2,2] in DegreeSequences(8)
303
True
304
"""
305
cdef int n = self._n
306
if len(seq)!=n:
307
return False
308
309
cdef int S = sum(seq)
310
311
# Partial represents the left side of Erdos and Gallai's inequality,
312
# i.e. the sum of the i first integers.
313
cdef int partial = 0
314
cdef int i,d,dd, right
315
316
# Temporary variable to ensure that the sequence is indeed
317
# non-increasing
318
cdef int prev = n-1
319
320
for i,d in enumerate(seq):
321
322
# Non-increasing ?
323
if d > prev:
324
return False
325
else:
326
prev = d
327
328
# Updating the partial sum
329
partial += d
330
331
# Evaluating the right hand side
332
right = i*(i+1)
333
for dd in seq[i+1:]:
334
right += min(dd,i+1)
335
336
# Comparing the two
337
if partial > right:
338
return False
339
340
return True
341
342
def __repr__(self):
343
"""
344
Representing the element
345
346
TEST::
347
348
sage: DegreeSequences(6)
349
Degree sequences on 6 elements
350
"""
351
return "Degree sequences on "+str(self._n)+" elements"
352
353
def __iter__(self):
354
"""
355
Iterate over all the degree sequences.
356
357
TODO: THIS SHOULD BE UPDATED AS SOON AS THE YIELD KEYWORD APPEARS IN
358
CYTHON. See comment in the class' documentation.
359
360
EXAMPLE::
361
362
sage: DS = DegreeSequences(6)
363
sage: all(seq in DS for seq in DS)
364
True
365
"""
366
367
init(self._n)
368
return iter(sequences)
369
370
def __dealloc__():
371
"""
372
Freeing the memory
373
"""
374
if seq != NULL:
375
free(seq)
376
377
cdef init(int n):
378
"""
379
Initializes the memory and starts the enumeration algorithm.
380
"""
381
global seq
382
global N
383
global sequences
384
385
if n == 0:
386
return [[]]
387
elif n == 1:
388
return [[0]]
389
390
seq = <unsigned char *> malloc((n+1)*sizeof(unsigned char))
391
memset(seq,0,(n+1)*sizeof(unsigned char))
392
393
# We begin with one vertex of degree 0
394
seq[0] = 1
395
396
N = n
397
sequences = []
398
enum(1,0)
399
free(seq)
400
return sequences
401
402
cdef inline add_seq():
403
"""
404
This function is called whenever a sequence is found.
405
406
Build the degree sequence corresponding to the current state of the
407
algorithm and adds it to the sequences list.
408
"""
409
global sequences
410
global N
411
global seq
412
413
cdef list s = []
414
cdef int i, j
415
416
for N > i >= 0:
417
for 0<= j < seq[i]:
418
s.append(i)
419
420
sequences.append(s)
421
422
cdef void enum(int k, int M):
423
"""
424
Main function. For an explanation of the algorithm please refer to the
425
class' documentation.
426
427
INPUT:
428
429
* ``k`` -- depth of the partial degree sequence
430
* ``M`` -- value of a maximum element in the partial degree sequence
431
"""
432
cdef int i,j
433
global seq
434
cdef int taken = 0
435
cdef int current_box
436
cdef int n_current_box
437
cdef int n_previous_box
438
cdef int new_vertex
439
440
# Have we found a new degree sequence ? End of recursion !
441
if k == N:
442
add_seq()
443
return
444
445
_sig_on
446
447
#############################################
448
# Creating vertices of Vertices of degree M #
449
#############################################
450
451
# If 0 is the current maximum degree, we can always extend the degree
452
# sequence with another 0
453
if M == 0:
454
455
seq[0] += 1
456
enum(k+1, M)
457
seq[0] -= 1
458
459
# We need not automatically increase the degree at each step. In this case,
460
# we have no other choice but to link the new vertex of degree M to vertices
461
# of degree M-1, which will become vertices of degree M too.
462
elif seq[M-1] >= M:
463
464
seq[M] += M+1
465
seq[M-1] -= M
466
467
enum(k+1, M)
468
469
seq[M] -= M+1
470
seq[M-1] += M
471
472
###############################################
473
# Creating vertices of Vertices of degree > M #
474
###############################################
475
476
for M >= current_box > 0:
477
478
# If there is not enough vertices in the boxes available
479
if taken + (seq[current_box] - 1) + seq[current_box-1] <= M:
480
taken += seq[current_box]
481
seq[current_box+1] += seq[current_box]
482
seq[current_box] = 0
483
continue
484
485
# The degree of the new vertex will be taken + i + j where :
486
#
487
# * i is the number of vertices taken in the *current* box
488
# * j the number of vertices taken in the *previous* one
489
490
n_current_box = seq[current_box]
491
n_previous_box = seq[current_box-1]
492
493
# Note to self, and others :
494
#
495
# In the following lines, there are many incrementation/decrementation
496
# that *may* be replaced by only +1 and -1 and save some
497
# instructions. This would involve adding several "if", and I feared it
498
# would make the code even uglier. If you are willing to give it a try,
499
# **please check the results** ! It is trickier that it seems ! Even
500
# changing the lower bounds in the for loops would require tests
501
# afterwards.
502
503
for max(0,((M+1)-n_previous_box-taken)) <= i < n_current_box:
504
seq[current_box] -= i
505
seq[current_box+1] += i
506
507
for max(0,((M+1)-taken-i)) <= j <= n_previous_box:
508
seq[current_box-1] -= j
509
seq[current_box] += j
510
511
new_vertex = taken + i + j
512
seq[new_vertex] += 1
513
enum(k+1,new_vertex)
514
seq[new_vertex] -= 1
515
516
seq[current_box-1] += j
517
seq[current_box] -= j
518
519
seq[current_box] += i
520
seq[current_box+1] -= i
521
522
taken += n_current_box
523
seq[current_box] = 0
524
seq[current_box+1] += n_current_box
525
526
# Corner case
527
#
528
# Now current_box = 0. All the vertices of nonzero degree are taken, we just
529
# want to know how many vertices of degree 0 will be neighbors of the new
530
# vertex.
531
for max(0,((M+1)-taken)) <= i <= seq[0]:
532
533
seq[1] += i
534
seq[0] -= i
535
seq[taken+i] += 1
536
537
enum(k+1, taken+i)
538
539
seq[taken+i] -= 1
540
seq[1] -= i
541
seq[0] += i
542
543
# Shift everything back to normal ! ( cell N is always equal to 0)
544
for 1 <= i < N:
545
seq[i] = seq[i+1]
546
547
_sig_off
548
549