Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/combinat/degree_sequences.pyx
8817 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* `\sum_i d_i` is even and `\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 'sage/ext/stdsage.pxi'
266
include 'sage/ext/cdefs.pxi'
267
include "sage/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
TESTS:
306
307
:trac:`15503`::
308
309
sage: [2,2,2,2,1,1,1] in DegreeSequences(7)
310
False
311
"""
312
cdef int n = self._n
313
if len(seq)!=n:
314
return False
315
316
# Is the sum even ?
317
if sum(seq)%2 == 1:
318
return False
319
320
# Partial represents the left side of Erdos and Gallai's inequality,
321
# i.e. the sum of the i first integers.
322
cdef int partial = 0
323
cdef int i,d,dd, right
324
325
# Temporary variable to ensure that the sequence is indeed
326
# non-increasing
327
cdef int prev = n-1
328
329
for i,d in enumerate(seq):
330
331
# Non-increasing ?
332
if d > prev:
333
return False
334
else:
335
prev = d
336
337
# Updating the partial sum
338
partial += d
339
340
# Evaluating the right hand side
341
right = i*(i+1)
342
for dd in seq[i+1:]:
343
right += min(dd,i+1)
344
345
# Comparing the two
346
if partial > right:
347
return False
348
349
return True
350
351
def __repr__(self):
352
"""
353
Representing the element
354
355
TEST::
356
357
sage: DegreeSequences(6)
358
Degree sequences on 6 elements
359
"""
360
return "Degree sequences on "+str(self._n)+" elements"
361
362
def __iter__(self):
363
"""
364
Iterate over all the degree sequences.
365
366
TODO: THIS SHOULD BE UPDATED AS SOON AS THE YIELD KEYWORD APPEARS IN
367
CYTHON. See comment in the class' documentation.
368
369
EXAMPLE::
370
371
sage: DS = DegreeSequences(6)
372
sage: all(seq in DS for seq in DS)
373
True
374
"""
375
376
init(self._n)
377
return iter(sequences)
378
379
def __dealloc__():
380
"""
381
Freeing the memory
382
"""
383
if seq != NULL:
384
sage_free(seq)
385
386
cdef init(int n):
387
"""
388
Initializes the memory and starts the enumeration algorithm.
389
"""
390
global seq
391
global N
392
global sequences
393
394
if n == 0:
395
return [[]]
396
elif n == 1:
397
return [[0]]
398
399
sig_on()
400
seq = <unsigned char *> sage_malloc((n+1)*sizeof(unsigned char))
401
memset(seq,0,(n+1)*sizeof(unsigned char))
402
sig_off()
403
404
# We begin with one vertex of degree 0
405
seq[0] = 1
406
407
N = n
408
sequences = []
409
enum(1,0)
410
sage_free(seq)
411
return sequences
412
413
cdef inline add_seq():
414
"""
415
This function is called whenever a sequence is found.
416
417
Build the degree sequence corresponding to the current state of the
418
algorithm and adds it to the sequences list.
419
"""
420
global sequences
421
global N
422
global seq
423
424
cdef list s = []
425
cdef int i, j
426
427
for N > i >= 0:
428
for 0<= j < seq[i]:
429
s.append(i)
430
431
sequences.append(s)
432
433
cdef void enum(int k, int M):
434
"""
435
Main function. For an explanation of the algorithm please refer to the
436
class' documentation.
437
438
INPUT:
439
440
* ``k`` -- depth of the partial degree sequence
441
* ``M`` -- value of a maximum element in the partial degree sequence
442
"""
443
cdef int i,j
444
global seq
445
cdef int taken = 0
446
cdef int current_box
447
cdef int n_current_box
448
cdef int n_previous_box
449
cdef int new_vertex
450
451
# Have we found a new degree sequence ? End of recursion !
452
if k == N:
453
add_seq()
454
return
455
456
sig_on()
457
458
#############################################
459
# Creating vertices of Vertices of degree M #
460
#############################################
461
462
# If 0 is the current maximum degree, we can always extend the degree
463
# sequence with another 0
464
if M == 0:
465
466
seq[0] += 1
467
enum(k+1, M)
468
seq[0] -= 1
469
470
# We need not automatically increase the degree at each step. In this case,
471
# we have no other choice but to link the new vertex of degree M to vertices
472
# of degree M-1, which will become vertices of degree M too.
473
elif seq[M-1] >= M:
474
475
seq[M] += M+1
476
seq[M-1] -= M
477
478
enum(k+1, M)
479
480
seq[M] -= M+1
481
seq[M-1] += M
482
483
###############################################
484
# Creating vertices of Vertices of degree > M #
485
###############################################
486
487
for M >= current_box > 0:
488
489
# If there is not enough vertices in the boxes available
490
if taken + (seq[current_box] - 1) + seq[current_box-1] <= M:
491
taken += seq[current_box]
492
seq[current_box+1] += seq[current_box]
493
seq[current_box] = 0
494
continue
495
496
# The degree of the new vertex will be taken + i + j where :
497
#
498
# * i is the number of vertices taken in the *current* box
499
# * j the number of vertices taken in the *previous* one
500
501
n_current_box = seq[current_box]
502
n_previous_box = seq[current_box-1]
503
504
# Note to self, and others :
505
#
506
# In the following lines, there are many incrementation/decrementation
507
# that *may* be replaced by only +1 and -1 and save some
508
# instructions. This would involve adding several "if", and I feared it
509
# would make the code even uglier. If you are willing to give it a try,
510
# **please check the results** ! It is trickier that it seems ! Even
511
# changing the lower bounds in the for loops would require tests
512
# afterwards.
513
514
for max(0,((M+1)-n_previous_box-taken)) <= i < n_current_box:
515
seq[current_box] -= i
516
seq[current_box+1] += i
517
518
for max(0,((M+1)-taken-i)) <= j <= n_previous_box:
519
seq[current_box-1] -= j
520
seq[current_box] += j
521
522
new_vertex = taken + i + j
523
seq[new_vertex] += 1
524
enum(k+1,new_vertex)
525
seq[new_vertex] -= 1
526
527
seq[current_box-1] += j
528
seq[current_box] -= j
529
530
seq[current_box] += i
531
seq[current_box+1] -= i
532
533
taken += n_current_box
534
seq[current_box] = 0
535
seq[current_box+1] += n_current_box
536
537
# Corner case
538
#
539
# Now current_box = 0. All the vertices of nonzero degree are taken, we just
540
# want to know how many vertices of degree 0 will be neighbors of the new
541
# vertex.
542
for max(0,((M+1)-taken)) <= i <= seq[0]:
543
544
seq[1] += i
545
seq[0] -= i
546
seq[taken+i] += 1
547
548
enum(k+1, taken+i)
549
550
seq[taken+i] -= 1
551
seq[1] -= i
552
seq[0] += i
553
554
# Shift everything back to normal ! ( cell N is always equal to 0)
555
for 1 <= i < N:
556
seq[i] = seq[i+1]
557
558
sig_off()
559
560