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