Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/combinat/integer_vector.py
8815 views
1
"""
2
(Non-negative) Integer vectors
3
4
AUTHORS:
5
6
* Mike Hanson (2007) - original module
7
* Nathann Cohen, David Joyner (2009-2010) - Gale-Ryser stuff
8
* Nathann Cohen, David Joyner (2011) - Gale-Ryser bugfix
9
* Travis Scrimshaw (2012-05-12) - Updated doc-strings to tell the user of
10
that the class's name is a misnomer (that they only contains non-negative
11
entries).
12
* Federico Poloni (2013) - specialized rank()
13
"""
14
#*****************************************************************************
15
# Copyright (C) 2007 Mike Hansen <[email protected]>,
16
# Copyright (C) 2012 Travis Scrimshaw <[email protected]>
17
#
18
# Distributed under the terms of the GNU General Public License (GPL)
19
#
20
# This code is distributed in the hope that it will be useful,
21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23
# General Public License for more details.
24
#
25
# The full text of the GPL is available at:
26
#
27
# http://www.gnu.org/licenses/
28
#*****************************************************************************
29
30
from combinat import CombinatorialClass
31
from __builtin__ import list as builtinlist
32
from sage.rings.integer import Integer
33
from sage.rings.arith import binomial
34
import misc
35
from sage.rings.infinity import PlusInfinity
36
import integer_list
37
import cartesian_product
38
import functools
39
40
41
def is_gale_ryser(r,s):
42
r"""
43
Tests whether the given sequences satisfy the condition
44
of the Gale-Ryser theorem.
45
46
Given a binary matrix `B` of dimension `n\times m`, the
47
vector of row sums is defined as the vector whose
48
`i^{\mbox{th}}` component is equal to the sum of the `i^{\mbox{th}}`
49
row in `A`. The vector of column sums is defined similarly.
50
51
If, given a binary matrix, these two vectors are easy to compute,
52
the Gale-Ryser theorem lets us decide whether, given two
53
non-negative vectors `r,s`, there exists a binary matrix
54
whose row/colum sums vectors are `r` and `s`.
55
56
This functions answers accordingly.
57
58
INPUT:
59
60
- ``r``, ``s`` -- lists of non-negative integers.
61
62
ALGORITHM:
63
64
Without loss of generality, we can assume that:
65
66
- The two given sequences do not contain any `0` ( which would
67
correspond to an empty column/row )
68
69
- The two given sequences are ordered in decreasing order
70
(reordering the sequence of row (resp. column) sums amounts to
71
reordering the rows (resp. columns) themselves in the matrix,
72
which does not alter the columns (resp. rows) sums.
73
74
We can then assume that `r` and `s` are partitions
75
(see the corresponding class ``Partition``)
76
77
If `r^*` denote the conjugate of `r`, the Gale-Ryser theorem
78
asserts that a binary Matrix satisfying the constraints exists
79
if and only if `s\preceq r^*`, where `\preceq` denotes
80
the domination order on partitions.
81
82
EXAMPLES::
83
84
sage: from sage.combinat.integer_vector import is_gale_ryser
85
sage: is_gale_ryser([4,2,2],[3,3,1,1])
86
True
87
sage: is_gale_ryser([4,2,1,1],[3,3,1,1])
88
True
89
sage: is_gale_ryser([3,2,1,1],[3,3,1,1])
90
False
91
92
REMARK: In the literature, what we are calling a
93
Gale-Ryser sequence sometimes goes by the (rather
94
generic-sounding) term ''realizable sequence''.
95
"""
96
97
# The sequences only contan non-negative integers
98
if [x for x in r if x<0] or [x for x in s if x<0]:
99
return False
100
101
# builds the corresponding partitions, i.e.
102
# removes the 0 and sorts the sequences
103
from sage.combinat.partition import Partition
104
r2 = Partition(sorted([x for x in r if x>0], reverse=True))
105
s2 = Partition(sorted([x for x in s if x>0], reverse=True))
106
107
# If the two sequences only contained zeroes
108
if len(r2) == 0 and len(s2) == 0:
109
return True
110
111
rstar = Partition(r2).conjugate()
112
113
# same number of 1s domination
114
return len(rstar) <= len(s2) and sum(r2) == sum(s2) and rstar.dominates(s)
115
116
def _slider01(A, t, k, p1, p2, fixedcols=[]):
117
r"""
118
Assumes `A` is a `(0,1)`-matrix. For each of the
119
`t` rows with highest row sums, this function
120
returns a matrix `B` which is the same as `A` except that it
121
has slid `t` of the `1` in each of these rows of `A`
122
over towards the `k`-th column. Care must be taken when the
123
last leading 1 is in column >=k. It avoids those in columns
124
listed in fixedcols.
125
126
This is a 'private' function for use in gale_ryser_theorem.
127
128
INPUT:
129
130
- ``A`` -- an `m\times n` (0,1) matrix
131
- ``t``, ``k`` -- integers satisfying `0 < t < m`, `0 < k < n`
132
- ``fixedcols`` -- those columns (if any) whose entries
133
aren't permitted to slide
134
135
OUTPUT:
136
137
An `m\times n` (0,1) matrix, which is the same as `A` except
138
that it has exactly one `1` in `A` slid over to the `k`-th
139
column.
140
141
EXAMPLES::
142
143
sage: from sage.combinat.integer_vector import _slider01
144
sage: A = matrix([[1,1,1,0],[1,1,1,0],[1,0,0,0],[1,0,0,0]])
145
sage: A
146
[1 1 1 0]
147
[1 1 1 0]
148
[1 0 0 0]
149
[1 0 0 0]
150
sage: _slider01(A, 1, 3, [3,3,1,1], [3,3,1,1])
151
[1 1 0 1]
152
[1 1 1 0]
153
[1 0 0 0]
154
[1 0 0 0]
155
sage: _slider01(A, 3, 3, [3,3,1,1], [3,3,1,1])
156
[1 1 0 1]
157
[1 0 1 1]
158
[0 0 0 1]
159
[1 0 0 0]
160
161
"""
162
# we assume that the rows of A are arranged so that
163
# there row sums are decreasing as you go from the
164
# top row to the bottom row
165
import copy
166
from sage.matrix.constructor import matrix
167
m = len(A.rows())
168
rs = [sum(x) for x in A.rows()]
169
n = len(A.columns())
170
cs = [sum(x) for x in A.columns()]
171
B = [copy.deepcopy(list(A.row(j))) for j in range(m)]
172
c = 0 # initializing counter
173
for ii in range(m):
174
rw = copy.deepcopy(B[ii]) # to make mutable
175
# now we want to move the rightmost left 1 to the k-th column
176
fixedcols = [l for l in range(n) if p2[l]==sum(matrix(B).column(l))]
177
JJ = range(n)
178
JJ.reverse()
179
for jj in JJ:
180
if t==sum(matrix(B).column(k)):
181
break
182
if jj<k and rw[jj]==1 and rw[k]==0 and not(jj in fixedcols):
183
# fixedcols check: only change B if the col sums get "better"
184
rw[jj] = 0
185
rw[k] = 1
186
B[ii] = rw
187
c = c+1
188
if c>=t: next
189
j=n-1
190
else:
191
next
192
if c>=t:
193
break
194
return matrix(B)
195
196
def gale_ryser_theorem(p1, p2, algorithm="gale"):
197
r"""
198
Returns the binary matrix given by the Gale-Ryser theorem.
199
200
The Gale Ryser theorem asserts that if `p_1,p_2` are two
201
partitions of `n` of respective lengths `k_1,k_2`, then there is
202
a binary `k_1\times k_2` matrix `M` such that `p_1` is the vector
203
of row sums and `p_2` is the vector of column sums of `M`, if
204
and only if the conjugate of `p_2` dominates `p_1`.
205
206
INPUT:
207
208
- ``p1, p2``-- list of integers representing the vectors
209
of row/column sums
210
211
- ``algorithm`` -- two possible string values :
212
213
- ``"ryser"`` implements the construction due
214
to Ryser [Ryser63]_.
215
216
- ``"gale"`` (default) implements the construction due to Gale [Gale57]_.
217
218
OUTPUT:
219
220
- A binary matrix if it exists, ``None`` otherwise.
221
222
Gale's Algorithm:
223
224
(Gale [Gale57]_): A matrix satisfying the constraints of its
225
sums can be defined as the solution of the following
226
Linear Program, which Sage knows how to solve.
227
228
.. MATH::
229
230
\forall i&\sum_{j=1}^{k_2} b_{i,j}=p_{1,j}\\
231
\forall i&\sum_{j=1}^{k_1} b_{j,i}=p_{2,j}\\
232
&b_{i,j}\mbox{ is a binary variable}
233
234
Ryser's Algorithm:
235
236
(Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`,
237
due to Ryser, is described as follows. The
238
construction works if and only if have `s\preceq r^*`.
239
240
* Construct the `m\times n` matrix `B` from `r` by defining
241
the `i`-th row of `B` to be the vector whose first `r_i`
242
entries are `1`, and the remainder are 0's, `1\leq i\leq
243
m`. This maximal matrix `B` with row sum `r` and ones left
244
justified has column sum `r^{*}`.
245
246
* Shift the last `1` in certain rows of `B` to column `n` in
247
order to achieve the sum `s_n`. Call this `B` again.
248
249
* The `1`'s in column n are to appear in those
250
rows in which `A` has the largest row sums, giving
251
preference to the bottom-most positions in case of ties.
252
* Note: When this step automatically "fixes" other columns,
253
one must skip ahead to the first column index
254
with a wrong sum in the step below.
255
256
* Proceed inductively to construct columns `n-1`, ..., `2`, `1`.
257
258
* Set `A = B`. Return `A`.
259
260
EXAMPLES:
261
262
Computing the matrix for `p_1=p_2=2+2+1` ::
263
264
sage: from sage.combinat.integer_vector import gale_ryser_theorem
265
sage: p1 = [2,2,1]
266
sage: p2 = [2,2,1]
267
sage: print gale_ryser_theorem(p1, p2) # not tested
268
[1 1 0]
269
[1 0 1]
270
[0 1 0]
271
sage: A = gale_ryser_theorem(p1, p2)
272
sage: rs = [sum(x) for x in A.rows()]
273
sage: cs = [sum(x) for x in A.columns()]
274
sage: p1 == rs; p2 == cs
275
True
276
True
277
278
Or for a non-square matrix with `p_1=3+3+2+1` and `p_2=3+2+2+1+1`, using Ryser's algorithm ::
279
280
sage: from sage.combinat.integer_vector import gale_ryser_theorem
281
sage: p1 = [3,3,1,1]
282
sage: p2 = [3,3,1,1]
283
sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
284
[1 1 0 1]
285
[1 1 1 0]
286
[0 1 0 0]
287
[1 0 0 0]
288
sage: p1 = [4,2,2]
289
sage: p2 = [3,3,1,1]
290
sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
291
[1 1 1 1]
292
[1 1 0 0]
293
[1 1 0 0]
294
sage: p1 = [4,2,2,0]
295
sage: p2 = [3,3,1,1,0,0]
296
sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
297
[1 1 1 1 0 0]
298
[1 1 0 0 0 0]
299
[1 1 0 0 0 0]
300
[0 0 0 0 0 0]
301
sage: p1 = [3,3,2,1]
302
sage: p2 = [3,2,2,1,1]
303
sage: print gale_ryser_theorem(p1, p2, algorithm="gale") # not tested
304
[1 1 1 0 0]
305
[1 1 0 0 1]
306
[1 0 1 0 0]
307
[0 0 0 1 0]
308
309
With `0` in the sequences, and with unordered inputs ::
310
311
sage: from sage.combinat.integer_vector import gale_ryser_theorem
312
sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser")
313
[1 0 1 1 0]
314
[1 1 1 0 0]
315
[0 0 0 0 0]
316
[0 0 1 0 0]
317
[1 0 0 0 0]
318
[0 0 0 0 0]
319
sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0]
320
sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
321
[1 1 1 0]
322
[0 0 1 0]
323
[0 1 0 0]
324
[1 0 0 0]
325
[1 0 0 0]
326
327
TESTS:
328
329
This test created a random bipartite graph on `n+m` vertices. Its
330
adjacency matrix is binary, and it is used to create some
331
"random-looking" sequences which correspond to an existing matrix. The
332
``gale_ryser_theorem`` is then called on these sequences, and the output
333
checked for correction.::
334
335
sage: def test_algorithm(algorithm, low = 10, high = 50):
336
... n,m = randint(low,high), randint(low,high)
337
... g = graphs.RandomBipartite(n, m, .3)
338
... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True)
339
... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True)
340
... m = gale_ryser_theorem(s1, s2, algorithm = algorithm)
341
... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True)
342
... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True)
343
... if ((ss1 == s1) and (ss2 == s2)):
344
... return True
345
... return False
346
347
sage: for algorithm in ["gale", "ryser"]: # long time
348
... for i in range(50): # long time
349
... if not test_algorithm(algorithm, 3, 10): # long time
350
... print "Something wrong with algorithm ", algorithm # long time
351
... break # long time
352
353
Null matrix::
354
355
sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="gale")
356
[0 0 0 0]
357
[0 0 0 0]
358
[0 0 0 0]
359
sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="ryser")
360
[0 0 0 0]
361
[0 0 0 0]
362
[0 0 0 0]
363
364
REFERENCES:
365
366
.. [Ryser63] H. J. Ryser, Combinatorial Mathematics,
367
Carus Monographs, MAA, 1963.
368
.. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math.
369
7(1957)1073-1082.
370
"""
371
from sage.combinat.partition import Partition
372
from sage.matrix.constructor import matrix
373
374
if not(is_gale_ryser(p1,p2)):
375
return False
376
377
if algorithm=="ryser": # ryser's algorithm
378
from sage.combinat.permutation import Permutation
379
380
# Sorts the sequences if they are not, and remembers the permutation
381
# applied
382
tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1])
383
r = [x[1] for x in tmp if x[1]>0]
384
r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()]
385
m = len(r)
386
387
tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1])
388
s = [x[1] for x in tmp if x[1]>0]
389
s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()]
390
n = len(s)
391
392
A0 = matrix([[1]*r[j]+[0]*(n-r[j]) for j in range(m)])
393
394
for k in range(1,n+1):
395
goodcols = [i for i in range(n) if s[i]==sum(A0.column(i))]
396
if sum(A0.column(n-k)) != s[n-k]:
397
A0 = _slider01(A0,s[n-k],n-k, p1, p2, goodcols)
398
399
# If we need to add empty rows/columns
400
if len(p1)!=m:
401
A0 = A0.stack(matrix([[0]*n]*(len(p1)-m)))
402
403
if len(p2)!=n:
404
A0 = A0.transpose().stack(matrix([[0]*len(p1)]*(len(p2)-n))).transpose()
405
406
# Applying the permutations to get a matrix satisfying the
407
# order given by the input
408
A0 = A0.matrix_from_rows_and_columns(r_permutation, s_permutation)
409
return A0
410
411
elif algorithm == "gale":
412
from sage.numerical.mip import MixedIntegerLinearProgram
413
k1, k2=len(p1), len(p2)
414
p = MixedIntegerLinearProgram()
415
b = p.new_variable(dim=2)
416
for (i,c) in enumerate(p1):
417
p.add_constraint(p.sum([b[i][j] for j in xrange(k2)]),min=c,max=c)
418
for (i,c) in enumerate(p2):
419
p.add_constraint(p.sum([b[j][i] for j in xrange(k1)]),min=c,max=c)
420
p.set_objective(None)
421
p.set_binary(b)
422
p.solve()
423
b = p.get_values(b)
424
M = [[0]*k2 for i in xrange(k1)]
425
for i in xrange(k1):
426
for j in xrange(k2):
427
M[i][j] = int(b[i][j])
428
return matrix(M)
429
430
else:
431
raise ValueError("The only two algorithms available are \"gale\" and \"ryser\"")
432
433
def _default_function(l, default, i):
434
"""
435
EXAMPLES::
436
437
sage: from sage.combinat.integer_vector import _default_function
438
sage: import functools
439
sage: f = functools.partial(_default_function, [1,2,3], 99)
440
sage: f(0)
441
99
442
sage: f(1)
443
1
444
sage: f(2)
445
2
446
sage: f(3)
447
3
448
sage: f(4)
449
99
450
"""
451
try:
452
if i <= 0:
453
return default
454
return l[i-1]
455
except IndexError:
456
return default
457
458
infinity = PlusInfinity()
459
def list2func(l, default=None):
460
"""
461
Given a list l, return a function that takes in a value i and
462
return l[i-1]. If default is not None, then the function will
463
return the default value for out of range i's.
464
465
EXAMPLES::
466
467
sage: f = sage.combinat.integer_vector.list2func([1,2,3])
468
sage: f(1)
469
1
470
sage: f(2)
471
2
472
sage: f(3)
473
3
474
sage: f(4)
475
Traceback (most recent call last):
476
...
477
IndexError: list index out of range
478
479
::
480
481
sage: f = sage.combinat.integer_vector.list2func([1,2,3], 0)
482
sage: f(3)
483
3
484
sage: f(4)
485
0
486
"""
487
if default is None:
488
return lambda i: l[i-1]
489
else:
490
return functools.partial(_default_function, l, default)
491
492
493
def constant_func(i):
494
"""
495
Returns the constant function i.
496
497
EXAMPLES::
498
499
sage: f = sage.combinat.integer_vector.constant_func(3)
500
sage: f(-1)
501
3
502
sage: f('asf')
503
3
504
"""
505
return lambda x: i
506
507
def IntegerVectors(n=None, k=None, **kwargs):
508
"""
509
Returns the combinatorial class of (non-negative) integer vectors.
510
511
NOTE - These integer vectors are non-negative.
512
513
EXAMPLES: If n is not specified, it returns the class of all
514
integer vectors.
515
516
::
517
518
sage: IntegerVectors()
519
Integer vectors
520
sage: [] in IntegerVectors()
521
True
522
sage: [1,2,1] in IntegerVectors()
523
True
524
sage: [1, 0, 0] in IntegerVectors()
525
True
526
527
Entries are non-negative.
528
529
::
530
531
sage: [-1, 2] in IntegerVectors()
532
False
533
534
If n is specified, then it returns the class of all integer vectors
535
which sum to n.
536
537
::
538
539
sage: IV3 = IntegerVectors(3); IV3
540
Integer vectors that sum to 3
541
542
Note that trailing zeros are ignored so that [3, 0] does not show
543
up in the following list (since [3] does)
544
545
::
546
547
sage: IntegerVectors(3, max_length=2).list()
548
[[3], [2, 1], [1, 2], [0, 3]]
549
550
If n and k are both specified, then it returns the class of integer
551
vectors that sum to n and are of length k.
552
553
::
554
555
sage: IV53 = IntegerVectors(5,3); IV53
556
Integer vectors of length 3 that sum to 5
557
sage: IV53.cardinality()
558
21
559
sage: IV53.first()
560
[5, 0, 0]
561
sage: IV53.last()
562
[0, 0, 5]
563
sage: IV53.random_element()
564
[4, 0, 1]
565
"""
566
if n is None:
567
return IntegerVectors_all()
568
elif k is None:
569
return IntegerVectors_nconstraints(n,kwargs)
570
else:
571
if isinstance(k, builtinlist):
572
return IntegerVectors_nnondescents(n,k)
573
else:
574
if len(kwargs) == 0:
575
return IntegerVectors_nk(n,k)
576
else:
577
return IntegerVectors_nkconstraints(n,k,kwargs)
578
579
580
class IntegerVectors_all(CombinatorialClass):
581
def __repr__(self):
582
"""
583
EXAMPLES::
584
585
sage: IntegerVectors()
586
Integer vectors
587
"""
588
return "Integer vectors"
589
590
def __contains__(self, x):
591
"""
592
EXAMPLES::
593
594
sage: [] in IntegerVectors()
595
True
596
sage: [3,2,2,1] in IntegerVectors()
597
True
598
"""
599
if not isinstance(x, builtinlist):
600
return False
601
for i in x:
602
if not isinstance(i, (int, Integer)):
603
return False
604
if i < 0:
605
return False
606
607
return True
608
609
def list(self):
610
"""
611
EXAMPLES::
612
613
sage: IntegerVectors().list()
614
Traceback (most recent call last):
615
...
616
NotImplementedError: infinite list
617
"""
618
raise NotImplementedError, "infinite list" # can't use InfiniteAbstractCombinatorialClass
619
620
def cardinality(self):
621
"""
622
EXAMPLES::
623
624
sage: IntegerVectors().cardinality()
625
+Infinity
626
"""
627
return infinity
628
629
630
class IntegerVectors_nk(CombinatorialClass):
631
def __init__(self, n, k):
632
"""
633
TESTS::
634
635
sage: IV = IntegerVectors(2,3)
636
sage: IV == loads(dumps(IV))
637
True
638
639
AUTHORS:
640
641
- Martin Albrecht
642
643
- Mike Hansen
644
"""
645
self.n = n
646
self.k = k
647
648
649
def _list_rec(self, n, k):
650
"""
651
Return a list of a exponent tuples of length `size` such
652
that the degree of the associated monomial is `D`.
653
654
INPUT:
655
656
657
- ``n`` - degree (must be 0)
658
659
- ``k`` - length of exponent tuples (must be 0)
660
661
662
EXAMPLES::
663
664
sage: IV = IntegerVectors(2,3)
665
sage: IV._list_rec(2,3)
666
[(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)]
667
"""
668
res = []
669
670
if k == 1:
671
return [ (n, ) ]
672
673
for nbar in range(n+1):
674
n_diff = n-nbar
675
for rest in self._list_rec( nbar , k-1):
676
res.append((n_diff,)+rest)
677
return res
678
679
def list(self):
680
"""
681
EXAMPLE::
682
683
sage: IV = IntegerVectors(2,3)
684
sage: IV.list()
685
[[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]]
686
sage: IntegerVectors(3, 0).list()
687
[]
688
sage: IntegerVectors(3, 1).list()
689
[[3]]
690
sage: IntegerVectors(0, 1).list()
691
[[0]]
692
sage: IntegerVectors(0, 2).list()
693
[[0, 0]]
694
sage: IntegerVectors(2, 2).list()
695
[[2, 0], [1, 1], [0, 2]]
696
"""
697
if self.n < 0:
698
return []
699
700
if self.k == 0:
701
if self.n == 0:
702
return [[]]
703
else:
704
return []
705
elif self.k == 1:
706
return [[self.n]]
707
708
res = self._list_rec(self.n, self.k)
709
return map(list, res)
710
711
712
def __iter__(self):
713
"""
714
EXAMPLE::
715
716
sage: IV = IntegerVectors(2,3)
717
sage: list(IV)
718
[[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]]
719
sage: list(IntegerVectors(3, 0))
720
[]
721
sage: list(IntegerVectors(3, 1))
722
[[3]]
723
sage: list(IntegerVectors(0, 1))
724
[[0]]
725
sage: list(IntegerVectors(0, 2))
726
[[0, 0]]
727
sage: list(IntegerVectors(2, 2))
728
[[2, 0], [1, 1], [0, 2]]
729
sage: IntegerVectors(0,0).list()
730
[[]]
731
sage: IntegerVectors(1,0).list()
732
[]
733
sage: IntegerVectors(0,1).list()
734
[[0]]
735
sage: IntegerVectors(2,2).list()
736
[[2, 0], [1, 1], [0, 2]]
737
sage: IntegerVectors(-1,0).list()
738
[]
739
sage: IntegerVectors(-1,2).list()
740
[]
741
"""
742
if self.n < 0:
743
return
744
745
if self.k == 0:
746
if self.n == 0:
747
yield []
748
return
749
elif self.k == 1:
750
yield [self.n]
751
return
752
753
for nbar in range(self.n+1):
754
n = self.n-nbar
755
for rest in IntegerVectors_nk(nbar , self.k-1):
756
yield [n] + rest
757
758
def __repr__(self):
759
"""
760
TESTS::
761
762
sage: IV = IntegerVectors(2,3)
763
sage: repr(IV)
764
'Integer vectors of length 3 that sum to 2'
765
"""
766
return "Integer vectors of length %s that sum to %s"%(self.k, self.n)
767
768
def __contains__(self, x):
769
"""
770
TESTS::
771
772
sage: IV = IntegerVectors(2,3)
773
sage: all([i in IV for i in IV])
774
True
775
sage: [0,1,2] in IV
776
False
777
sage: [2.0, 0, 0] in IV
778
False
779
sage: [0,1,0,1] in IV
780
False
781
sage: [0,1,1] in IV
782
True
783
sage: [-1,2,1] in IV
784
False
785
"""
786
if x not in IntegerVectors():
787
return False
788
789
if sum(x) != self.n:
790
return False
791
792
if len(x) != self.k:
793
return False
794
795
if len(x) > 0 and min(x) < 0:
796
return False
797
798
return True
799
800
def rank(self, x):
801
"""
802
Returns the position of a given element.
803
804
INPUT:
805
806
- ``x`` - a list with ``sum(x) == n`` and ``len(x) == k``
807
808
TESTS::
809
810
sage: IV = IntegerVectors(4,5)
811
sage: range(IV.cardinality()) == [IV.rank(x) for x in IV]
812
True
813
"""
814
815
if x not in self:
816
raise ValueError, "argument is not a member of IntegerVectors(%d,%d)" % (self.n, self.k)
817
818
n = self.n
819
k = self.k
820
821
r = 0
822
for i in range(k-1):
823
k -= 1
824
n -= x[i]
825
r += binomial(k+n-1,k)
826
827
return r
828
829
class IntegerVectors_nkconstraints(CombinatorialClass):
830
def __init__(self, n, k, constraints):
831
"""
832
EXAMPLES::
833
834
sage: IV = IntegerVectors(2,3,min_slope=0)
835
sage: IV == loads(dumps(IV))
836
True
837
"""
838
self.n = n
839
self.k = k
840
self.constraints = constraints
841
842
def __repr__(self):
843
"""
844
EXAMPLES::
845
846
sage: IntegerVectors(2,3,min_slope=0).__repr__()
847
'Integer vectors of length 3 that sum to 2 with constraints: min_slope=0'
848
"""
849
return "Integer vectors of length %s that sum to %s with constraints: %s"%(self.k, self.n, ", ".join( ["%s=%s"%(key, self.constraints[key]) for key in sorted(self.constraints.keys())] ))
850
851
852
def __contains__(self, x):
853
"""
854
TESTS::
855
856
sage: [0] in IntegerVectors(0)
857
True
858
sage: [0] in IntegerVectors(0, 1)
859
True
860
sage: [] in IntegerVectors(0, 0)
861
True
862
sage: [] in IntegerVectors(0, 1)
863
False
864
sage: [] in IntegerVectors(1, 0)
865
False
866
sage: [3] in IntegerVectors(3)
867
True
868
sage: [3] in IntegerVectors(2,1)
869
False
870
sage: [3] in IntegerVectors(2)
871
False
872
sage: [3] in IntegerVectors(3,1)
873
True
874
sage: [3,2,2,1] in IntegerVectors(9)
875
False
876
sage: [3,2,2,1] in IntegerVectors(9,5)
877
False
878
sage: [3,2,2,1] in IntegerVectors(8)
879
True
880
sage: [3,2,2,1] in IntegerVectors(8,5)
881
False
882
sage: [3,2,2,1] in IntegerVectors(8,4)
883
True
884
sage: [3,2,2,1] in IntegerVectors(8,4, min_part = 1)
885
True
886
sage: [3,2,2,1] in IntegerVectors(8,4, min_part = 2)
887
False
888
"""
889
if x not in IntegerVectors():
890
return False
891
892
if sum(x) != self.n:
893
return False
894
895
if len(x) != self.k:
896
return False
897
898
if self.constraints:
899
if not misc.check_integer_list_constraints(x, singleton=True, **self.constraints):
900
return False
901
902
return True
903
904
def cardinality(self):
905
"""
906
EXAMPLES::
907
908
sage: IntegerVectors(3,3, min_part=1).cardinality()
909
1
910
sage: IntegerVectors(5,3, min_part=1).cardinality()
911
6
912
sage: IntegerVectors(13, 4, min_part=2, max_part=4).cardinality()
913
16
914
"""
915
if not self.constraints:
916
if self.n >= 0:
917
return binomial(self.n+self.k-1,self.n)
918
else:
919
return 0
920
else:
921
if len(self.constraints) == 1 and 'max_part' in self.constraints and self.constraints['max_part'] != infinity:
922
m = self.constraints['max_part']
923
if m >= self.n:
924
return binomial(self.n+self.k-1,self.n)
925
else: #do by inclusion / exclusion on the number
926
#i of parts greater than m
927
return sum( [(-1)**i * binomial(self.n+self.k-1-i*(m+1), self.k-1)*binomial(self.k,i) for i in range(0, self.n/(m+1)+1)])
928
else:
929
return len(self.list())
930
931
932
def _parameters(self):
933
"""
934
Returns a tuple (min_length, max_length, floor, ceiling,
935
min_slope, max_slope) for the parameters of self.
936
937
EXAMPLES::
938
939
sage: IV = IntegerVectors(2,3,min_slope=0)
940
sage: min_length, max_length, floor, ceiling, min_slope, max_slope = IV._parameters()
941
sage: min_length
942
3
943
sage: max_length
944
3
945
sage: [floor(i) for i in range(1,10)]
946
[0, 0, 0, 0, 0, 0, 0, 0, 0]
947
sage: [ceiling(i) for i in range(1,5)]
948
[+Infinity, +Infinity, +Infinity, +Infinity]
949
sage: min_slope
950
0
951
sage: max_slope
952
+Infinity
953
954
sage: IV = IntegerVectors(3,10,inner=[4,1,3], min_part = 2)
955
sage: min_length, max_length, floor, ceiling, min_slope, max_slope = IV._parameters()
956
sage: floor(1), floor(2), floor(3)
957
(4, 2, 3)
958
959
sage: IV = IntegerVectors(3, 10, outer=[4,1,3], max_part = 3)
960
sage: min_length, max_length, floor, ceiling, min_slope, max_slope = IV._parameters()
961
sage: ceiling(1), ceiling(2), ceiling(3)
962
(3, 1, 3)
963
"""
964
constraints = self.constraints
965
#n, min_length, max_length, floor, ceiling, min_slope, max_slope
966
if self.k == -1:
967
min_length = constraints.get('min_length', 0)
968
max_length = constraints.get('max_length', infinity)
969
else:
970
min_length = self.k
971
max_length = self.k
972
973
min_part = constraints.get('min_part', 0)
974
max_part = constraints.get('max_part', infinity)
975
min_slope = constraints.get('min_slope', -infinity)
976
max_slope = constraints.get('max_slope', infinity)
977
if 'outer' in self.constraints:
978
ceiling = list2func( map(lambda i: min(max_part, i), self.constraints['outer']), default=max_part )
979
else:
980
ceiling = constant_func(max_part)
981
982
if 'inner' in self.constraints:
983
floor = list2func( map(lambda i: max(min_part, i), self.constraints['inner']), default=min_part )
984
else:
985
floor = constant_func(min_part)
986
987
return (min_length, max_length, floor, ceiling, min_slope, max_slope)
988
989
990
def first(self):
991
"""
992
EXAMPLES::
993
994
sage: IntegerVectors(2,3,min_slope=0).first()
995
[0, 1, 1]
996
"""
997
return integer_list.first(self.n, *self._parameters())
998
999
def next(self, x):
1000
"""
1001
EXAMPLES::
1002
1003
sage: IntegerVectors(2,3,min_slope=0).last()
1004
[0, 0, 2]
1005
"""
1006
return integer_list.next(x, *self._parameters())
1007
1008
def __iter__(self):
1009
"""
1010
EXAMPLES::
1011
1012
sage: IntegerVectors(-1, 0, min_part = 1).list()
1013
[]
1014
sage: IntegerVectors(-1, 2, min_part = 1).list()
1015
[]
1016
sage: IntegerVectors(0, 0, min_part=1).list()
1017
[[]]
1018
sage: IntegerVectors(3, 0, min_part=1).list()
1019
[]
1020
sage: IntegerVectors(0, 1, min_part=1).list()
1021
[]
1022
sage: IntegerVectors(2, 2, min_part=1).list()
1023
[[1, 1]]
1024
sage: IntegerVectors(2, 3, min_part=1).list()
1025
[]
1026
sage: IntegerVectors(4, 2, min_part=1).list()
1027
[[3, 1], [2, 2], [1, 3]]
1028
1029
::
1030
1031
sage: IntegerVectors(0, 3, outer=[0,0,0]).list()
1032
[[0, 0, 0]]
1033
sage: IntegerVectors(1, 3, outer=[0,0,0]).list()
1034
[]
1035
sage: IntegerVectors(2, 3, outer=[0,2,0]).list()
1036
[[0, 2, 0]]
1037
sage: IntegerVectors(2, 3, outer=[1,2,1]).list()
1038
[[1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1]]
1039
sage: IntegerVectors(2, 3, outer=[1,1,1]).list()
1040
[[1, 1, 0], [1, 0, 1], [0, 1, 1]]
1041
sage: IntegerVectors(2, 5, outer=[1,1,1,1,1]).list()
1042
[[1, 1, 0, 0, 0],
1043
[1, 0, 1, 0, 0],
1044
[1, 0, 0, 1, 0],
1045
[1, 0, 0, 0, 1],
1046
[0, 1, 1, 0, 0],
1047
[0, 1, 0, 1, 0],
1048
[0, 1, 0, 0, 1],
1049
[0, 0, 1, 1, 0],
1050
[0, 0, 1, 0, 1],
1051
[0, 0, 0, 1, 1]]
1052
1053
::
1054
1055
sage: iv = [ IntegerVectors(n,k) for n in range(-2, 7) for k in range(7) ]
1056
sage: all(map(lambda x: x.cardinality() == len(x.list()), iv))
1057
True
1058
sage: essai = [[1,1,1], [2,5,6], [6,5,2]]
1059
sage: iv = [ IntegerVectors(x[0], x[1], max_part = x[2]-1) for x in essai ]
1060
sage: all(map(lambda x: x.cardinality() == len(x.list()), iv))
1061
True
1062
"""
1063
return integer_list.iterator(self.n, *self._parameters())
1064
1065
class IntegerVectors_nconstraints(IntegerVectors_nkconstraints):
1066
def __init__(self, n, constraints):
1067
"""
1068
TESTS::
1069
1070
sage: IV = IntegerVectors(3, max_length=2)
1071
sage: IV == loads(dumps(IV))
1072
True
1073
"""
1074
IntegerVectors_nkconstraints.__init__(self, n, -1, constraints)
1075
1076
def __repr__(self):
1077
"""
1078
EXAMPLES::
1079
1080
sage: repr(IntegerVectors(3))
1081
'Integer vectors that sum to 3'
1082
sage: repr(IntegerVectors(3, max_length=2))
1083
'Integer vectors that sum to 3 with constraints: max_length=2'
1084
"""
1085
if self.constraints:
1086
return "Integer vectors that sum to %s with constraints: %s"%(self.n,", ".join( ["%s=%s"%(key, self.constraints[key]) for key in sorted(self.constraints.keys())] ))
1087
else:
1088
return "Integer vectors that sum to %s"%(self.n,)
1089
1090
def __contains__(self, x):
1091
"""
1092
EXAMPLES::
1093
1094
sage: [0,3,0,1,2] in IntegerVectors(6)
1095
True
1096
sage: [0,3,0,1,2] in IntegerVectors(6, max_length=3)
1097
False
1098
"""
1099
if self.constraints:
1100
return x in IntegerVectors_all() and misc.check_integer_list_constraints(x, singleton=True, **self.constraints)
1101
else:
1102
return x in IntegerVectors_all() and sum(x) == self.n
1103
1104
def cardinality(self):
1105
"""
1106
EXAMPLES::
1107
1108
sage: IntegerVectors(3, max_length=2).cardinality()
1109
4
1110
sage: IntegerVectors(3).cardinality()
1111
+Infinity
1112
"""
1113
if 'max_length' not in self.constraints:
1114
return infinity
1115
else:
1116
return self._CombinatorialClass__cardinality_from_iterator()
1117
1118
def list(self):
1119
"""
1120
EXAMPLES::
1121
1122
sage: IntegerVectors(3, max_length=2).list()
1123
[[3], [2, 1], [1, 2], [0, 3]]
1124
sage: IntegerVectors(3).list()
1125
Traceback (most recent call last):
1126
...
1127
NotImplementedError: infinite list
1128
"""
1129
if 'max_length' not in self.constraints:
1130
raise NotImplementedError, "infinite list" # no list from infinite iter
1131
else:
1132
return list(self)
1133
1134
1135
class IntegerVectors_nnondescents(CombinatorialClass):
1136
r"""
1137
The combinatorial class of integer vectors v graded by two
1138
parameters:
1139
1140
- n: the sum of the parts of v
1141
1142
- comp: the non descents composition of v
1143
1144
In other words: the length of v equals c[1]+...+c[k], and v is
1145
decreasing in the consecutive blocs of length c[1], ..., c[k]
1146
1147
Those are the integer vectors of sum n which are lexicographically
1148
maximal (for the natural left->right reading) in their orbit by the
1149
young subgroup S_c_1 x x S_c_k. In particular, they form a set
1150
of orbit representative of integer vectors w.r.t. this young
1151
subgroup.
1152
"""
1153
def __init__(self, n, comp):
1154
"""
1155
EXAMPLES::
1156
1157
sage: IV = IntegerVectors(4, [2])
1158
sage: IV == loads(dumps(IV))
1159
True
1160
"""
1161
self.n = n
1162
self.comp = comp
1163
1164
def __repr__(self):
1165
"""
1166
EXAMPLES::
1167
1168
sage: IntegerVectors(4, [2]).__repr__()
1169
'Integer vectors of 4 with non-descents composition [2]'
1170
"""
1171
return "Integer vectors of %s with non-descents composition %s"%(self.n, self.comp)
1172
1173
def __iter__(self):
1174
"""
1175
TESTS::
1176
1177
sage: IntegerVectors(0, []).list()
1178
[[]]
1179
sage: IntegerVectors(5, []).list()
1180
[]
1181
sage: IntegerVectors(0, [1]).list()
1182
[[0]]
1183
sage: IntegerVectors(4, [1]).list()
1184
[[4]]
1185
sage: IntegerVectors(4, [2]).list()
1186
[[4, 0], [3, 1], [2, 2]]
1187
sage: IntegerVectors(4, [2,2]).list()
1188
[[4, 0, 0, 0],
1189
[3, 1, 0, 0],
1190
[2, 2, 0, 0],
1191
[3, 0, 1, 0],
1192
[2, 1, 1, 0],
1193
[2, 0, 2, 0],
1194
[2, 0, 1, 1],
1195
[1, 1, 2, 0],
1196
[1, 1, 1, 1],
1197
[1, 0, 3, 0],
1198
[1, 0, 2, 1],
1199
[0, 0, 4, 0],
1200
[0, 0, 3, 1],
1201
[0, 0, 2, 2]]
1202
sage: IntegerVectors(5, [1,1,1]).list()
1203
[[5, 0, 0],
1204
[4, 1, 0],
1205
[4, 0, 1],
1206
[3, 2, 0],
1207
[3, 1, 1],
1208
[3, 0, 2],
1209
[2, 3, 0],
1210
[2, 2, 1],
1211
[2, 1, 2],
1212
[2, 0, 3],
1213
[1, 4, 0],
1214
[1, 3, 1],
1215
[1, 2, 2],
1216
[1, 1, 3],
1217
[1, 0, 4],
1218
[0, 5, 0],
1219
[0, 4, 1],
1220
[0, 3, 2],
1221
[0, 2, 3],
1222
[0, 1, 4],
1223
[0, 0, 5]]
1224
sage: IntegerVectors(0, [2,3]).list()
1225
[[0, 0, 0, 0, 0]]
1226
"""
1227
for iv in IntegerVectors(self.n, len(self.comp)):
1228
blocks = [ IntegerVectors(iv[i], self.comp[i], max_slope=0).list() for i in range(len(self.comp))]
1229
for parts in cartesian_product.CartesianProduct(*blocks):
1230
res = []
1231
for part in parts:
1232
res += part
1233
yield res
1234
1235
1236
1237