Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/matroids/basis_matroid.pyx
8817 views
1
"""
2
Basis matroids
3
4
In a matroid, a basis is an inclusionwise maximal independent set.
5
The common cardinality of all bases is the rank of the matroid.
6
Matroids are uniquely determined by their set of bases.
7
8
This module defines the class
9
:class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`, which
10
internally represents a matroid as a set of bases. It is a subclass of
11
:mod:`BasisExchangeMatroid <sage.matroids.basis_exchange_matroid>`, and as
12
such it inherits all method from that class and from the class
13
:mod:`Matroid <sage.matroids.matroid>`. Additionally, it provides the
14
following methods:
15
16
- :meth:`is_distinguished() <sage.matroids.basis_matroid.BasisMatroid.is_distinguished>`
17
- :meth:`relabel() <sage.matroids.basis_matroid.BasisMatroid.relabel>`
18
19
Construction
20
============
21
22
A ``BasisMatroid`` can be created from another matroid, from a list of bases,
23
or from a list of nonbases. For a full description of allowed inputs, see
24
:class:`below <sage.matroids.basis_matroid.BasisMatroid>`. It is recommended
25
to use the :func:`Matroid() <sage.matroids.constructor.Matroid>` function for
26
easy construction of a ``BasisMatroid``. For direct access to the
27
``BasisMatroid`` constructor, run::
28
29
sage: from sage.matroids.advanced import *
30
31
See also :mod:`sage.matroids.advanced`.
32
33
EXAMPLES::
34
35
sage: from sage.matroids.advanced import *
36
sage: M1 = BasisMatroid(groundset='abcd', bases=['ab', 'ac', 'ad', 'bc', 'bd', 'cd'])
37
sage: M2 = Matroid(['ab', 'ac', 'ad', 'bc', 'bd', 'cd'])
38
sage: M1 == M2
39
True
40
41
Implementation
42
==============
43
44
The set of bases is compactly stored in a bitset which takes
45
`O(binomial(N, R))` bits of space, where `N` is the cardinality of the
46
groundset and `R` is the rank. ``BasisMatroid`` inherits the matroid oracle
47
from its parent class ``BasisExchangeMatroid``, by providing the elementary
48
functions for exploring the base exchange graph. In addition, ``BasisMatroid``
49
has methods for constructing minors, duals, single-element extensions, for
50
testing matroid isomorphism and minor inclusion.
51
52
AUTHORS:
53
54
- Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version
55
56
TESTS::
57
58
sage: F = matroids.named_matroids.Fano()
59
sage: M = Matroid(bases=F.bases())
60
sage: TestSuite(M).run()
61
62
Methods
63
=======
64
"""
65
#*****************************************************************************
66
# Copyright (C) 2013 Rudi Pendavingh <[email protected]>
67
# Copyright (C) 2013 Stefan van Zwam <[email protected]>
68
#
69
# Distributed under the terms of the GNU General Public License (GPL)
70
# as published by the Free Software Foundation; either version 2 of
71
# the License, or (at your option) any later version.
72
# http://www.gnu.org/licenses/
73
#*****************************************************************************
74
include 'sage/ext/stdsage.pxi'
75
include 'sage/misc/bitset.pxi'
76
from matroid cimport Matroid
77
from basis_exchange_matroid cimport BasisExchangeMatroid
78
from itertools import permutations
79
from sage.rings.arith import binomial
80
from set_system cimport SetSystem
81
from itertools import combinations
82
83
# class of general matroids, represented by their list of bases
84
85
cdef class BasisMatroid(BasisExchangeMatroid):
86
"""
87
Create general matroid, stored as a set of bases.
88
89
INPUT:
90
91
- ``M`` (optional) -- a matroid.
92
- ``groundset`` (optional) -- any iterable set.
93
- ``bases`` (optional) -- a set of subsets of ``groundset``.
94
- ``nonbases`` (optional) -- a set of subsets of ``groundset``.
95
- ``rank`` (optional) -- a natural number
96
97
EXAMPLES:
98
99
The empty matroid::
100
101
sage: from sage.matroids.advanced import *
102
sage: M = BasisMatroid()
103
sage: M.groundset()
104
frozenset([])
105
sage: M.full_rank()
106
0
107
108
Create a BasisMatroid instance out of any other matroid::
109
110
sage: from sage.matroids.advanced import *
111
sage: F = matroids.named_matroids.Fano()
112
sage: M = BasisMatroid(F)
113
sage: F.groundset() == M.groundset()
114
True
115
sage: len(set(F.bases()).difference(M.bases()))
116
0
117
118
It is possible to provide either bases or nonbases::
119
120
sage: from sage.matroids.advanced import *
121
sage: M1 = BasisMatroid(groundset='abc', bases=['ab', 'ac'] )
122
sage: M2 = BasisMatroid(groundset='abc', nonbases=['bc'])
123
sage: M1 == M2
124
True
125
126
Providing only groundset and rank creates a uniform matroid::
127
128
sage: from sage.matroids.advanced import *
129
sage: M1 = BasisMatroid(matroids.Uniform(2, 5))
130
sage: M2 = BasisMatroid(groundset=range(5), rank=2)
131
sage: M1 == M2
132
True
133
134
We do not check if the provided input forms an actual matroid::
135
136
sage: from sage.matroids.advanced import *
137
sage: M1 = BasisMatroid(groundset='abcd', bases=['ab', 'cd'])
138
sage: M1.full_rank()
139
2
140
sage: M1.is_valid()
141
False
142
143
"""
144
def __init__(self, M=None, groundset=None, bases=None, nonbases=None, rank=None):
145
"""
146
See class definition for full documentation.
147
148
EXAMPLES::
149
150
sage: from sage.matroids.advanced import *
151
sage: F = matroids.named_matroids.Fano()
152
sage: M = BasisMatroid(F)
153
sage: F.groundset() == M.groundset()
154
True
155
sage: len(set(F.bases()).difference(M.bases()))
156
0
157
"""
158
cdef SetSystem NB
159
cdef long i
160
161
if isinstance(M, BasisMatroid):
162
BasisExchangeMatroid.__init__(self, groundset=(<BasisMatroid>M)._E, rank=(<BasisMatroid>M)._matroid_rank)
163
bitset_init(self._bb, binom[(<BasisMatroid>M)._groundset_size][(<BasisMatroid>M)._matroid_rank])
164
bitset_copy(self._bb, (<BasisMatroid>M)._bb)
165
bitset_init(self._b, (<BasisMatroid>M)._bitset_size)
166
bitset_copy(self._b, (<BasisMatroid>M)._b)
167
bitset_copy(self._current_basis, (<BasisMatroid>M)._current_basis)
168
self._bcount = (<BasisMatroid>M)._bcount
169
return
170
171
if isinstance(M, BasisExchangeMatroid):
172
BasisExchangeMatroid.__init__(self, groundset=(<BasisExchangeMatroid>M)._E, rank=(<BasisExchangeMatroid>M)._matroid_rank)
173
binom_init(len(M), M.full_rank())
174
bc = binom[(<BasisExchangeMatroid>M)._groundset_size][(<BasisExchangeMatroid>M)._matroid_rank]
175
bitset_init(self._bb, bc)
176
bitset_set_first_n(self._bb, bc)
177
NB = M.nonbases()
178
for i in xrange(len(NB)):
179
bitset_discard(self._bb, set_to_index(NB._subsets[i]))
180
181
bitset_init(self._b, (<BasisExchangeMatroid>M)._bitset_size)
182
self.reset_current_basis()
183
self._bcount = bc - len(NB)
184
return
185
186
if M is not None:
187
rank = M.full_rank()
188
nonbases = M.nonbases()
189
groundset = sorted(M.groundset())
190
191
if groundset is None:
192
groundset = frozenset()
193
if rank is None:
194
if bases is not None:
195
rank = len(min(bases))
196
elif nonbases is not None:
197
rank = len(min(nonbases))
198
else:
199
rank = 0
200
201
BasisExchangeMatroid.__init__(self, groundset=groundset, rank=rank)
202
203
size = len(groundset)
204
binom_init(size, rank)
205
bitset_init(self._bb, binom[size][rank])
206
bitset_init(self._b, max(size, 1))
207
bitset_clear(self._bb)
208
209
if bases is not None:
210
if len(bases) == 0:
211
raise ValueError("set of bases must be nonempty.")
212
self._bcount = 0
213
for B in bases:
214
b = frozenset(B)
215
if len(b) != self._matroid_rank:
216
raise ValueError("basis has wrong cardinality.")
217
if not b.issubset(self._groundset):
218
raise ValueError("basis is not a subset of the groundset")
219
self.__pack(self._b, b)
220
i = set_to_index(self._b)
221
if not bitset_in(self._bb, i):
222
self._bcount += 1
223
bitset_add(self._bb, i)
224
else:
225
bitset_complement(self._bb, self._bb)
226
self._bcount = binom[size][rank]
227
if nonbases is not None:
228
for B in nonbases:
229
b = frozenset(B)
230
if len(b) != self._matroid_rank:
231
raise ValueError("nonbasis has wrong cardinalty.")
232
if not b.issubset(self._groundset):
233
raise ValueError("nonbasis is not a subset of the groundset")
234
self.__pack(self._b, b)
235
i = set_to_index(self._b)
236
if bitset_in(self._bb, i):
237
self._bcount -= 1
238
bitset_discard(self._bb, i)
239
240
self.reset_current_basis()
241
242
def __dealloc__(self):
243
bitset_free(self._b)
244
bitset_free(self._bb)
245
246
# Sage special functions
247
def _repr_(self):
248
"""
249
Return a string representation of ``self``.
250
251
EXAMPLES::
252
253
sage: from sage.matroids.advanced import *
254
sage: M = BasisMatroid(matroids.named_matroids.Fano())
255
sage: repr(M) # indirect doctest
256
'Matroid of rank 3 on 7 elements with 28 bases'
257
258
"""
259
return Matroid._repr_(self) + " with " + str(self.bases_count()) + " bases"
260
261
# support for parent BasisExchangeMatroid
262
263
cdef bint __is_exchange_pair(self, long x, long y): # test if current_basis-x + y is a basis
264
"""
265
Test if `B-e + f` is a basis of the current matroid.
266
267
Here ``B`` is the 'current' basis, i.e. the one returned by
268
``self.basis()``, and ``e=self._E[x]``, ``f=self._E[y]``.
269
270
INPUT:
271
272
- ``x`` -- an integer
273
- ``y`` -- an integer
274
275
OUTPUT:
276
277
``True`` if `B-e + f` is a basis, ``False`` otherwise. Here `e`, `f`
278
are the groundset elements which are internally named by the integers
279
``x``, ``y``.
280
281
NOTE: this is an internal function, supporting the parent class
282
BasisExchangeMatroid of BasisMatroid.
283
284
"""
285
bitset_copy(self._b, self._current_basis)
286
bitset_discard(self._b, x)
287
bitset_add(self._b, y)
288
return bitset_in(self._bb, set_to_index(self._b))
289
290
cdef reset_current_basis(self):
291
"""
292
Set the current basis to the (lexicographically) first basis of the
293
matroid.
294
"""
295
index_to_set(self._current_basis, bitset_first(self._bb), self._matroid_rank, self._groundset_size) # set current basis of parent BasisExchangeMatroid
296
297
# a function that is very efficient for this class
298
299
cpdef _is_basis(self, X):
300
"""
301
Test if input is a basis.
302
303
INPUT:
304
305
- ``X`` -- An object with Python's ``frozenset`` interface containing
306
a subset of ``self.groundset()``.
307
308
.. WARNING::
309
310
This method assumes that ``X`` has the right size to be a basis,
311
i.e. ``len(X) == self.full_rank()``. Otherwise its behavior is
312
undefined.
313
314
OUTPUT:
315
316
Boolean.
317
318
EXAMPLES::
319
320
sage: M = Matroid(bases=matroids.named_matroids.Vamos().bases())
321
sage: M._is_basis(set(['a', 'b', 'c', 'e']))
322
True
323
sage: M._is_basis(set(['a', 'b', 'c', 'd']))
324
False
325
"""
326
self.__pack(self._b, X)
327
return bitset_in(self._bb, set_to_index(self._b))
328
329
# dual and minors
330
331
cpdef dual(self):
332
"""
333
Return the dual of the matroid.
334
335
Let `M` be a matroid with ground set `E`. If `B` is the set of bases
336
of `M`, then the set `\{E - b : b \in B\}` is the set of bases of
337
another matroid, the *dual* of `M`.
338
339
OUTPUT:
340
341
The dual matroid.
342
343
EXAMPLES::
344
345
sage: M = Matroid(bases=matroids.named_matroids.Pappus().bases())
346
sage: M.dual()
347
Matroid of rank 6 on 9 elements with 75 bases
348
349
ALGORITHM:
350
351
A BasisMatroid on `n` elements and of rank `r` is stored as a
352
bitvector of length `n \choose r`. The `i`-th bit in this vector
353
indicates that the `i`-th `r`-set in the lexicographic enumeration of
354
`r`-subsets of the groundset is a basis. Reversing this bitvector
355
yields a bitvector that indicates whether the complement of an
356
`(n - r)`-set is a basis, i.e. gives the bitvector of the bases of the
357
dual.
358
359
"""
360
cdef long i, N
361
cdef BasisMatroid D
362
D = BasisMatroid(groundset=self._E, rank=self.full_corank())
363
N = binom[self._groundset_size][self._matroid_rank]
364
for i in xrange(N):
365
if not bitset_in(self._bb, i):
366
bitset_discard(D._bb, N - i - 1)
367
D.reset_current_basis()
368
D._reset_invariants()
369
D._bcount = self._bcount
370
return D
371
372
cpdef _minor(self, contractions, deletions):
373
"""
374
Return a minor.
375
376
INPUT:
377
378
- ``contractions`` -- An object with Python's ``frozenset`` interface
379
containing a subset of ``self.groundset()``.
380
- ``deletions`` -- An object with Python's ``frozenset`` interface
381
containing a subset of ``self.groundset()``.
382
383
.. NOTE::
384
385
This method does NOT do any checks. Besides the assumptions above,
386
we assume the following:
387
388
- ``contractions`` is independent
389
- ``deletions`` is coindependent
390
- ``contractions`` and ``deletions`` are disjoint.
391
392
OUTPUT:
393
394
A matroid.
395
396
EXAMPLES::
397
398
sage: from sage.matroids.advanced import *
399
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
400
sage: M._minor(contractions=set(['a']), deletions=set(['b', 'c']))
401
Matroid of rank 3 on 5 elements with 10 bases
402
403
"""
404
E = self.groundset() - (contractions | deletions)
405
mr = self.full_rank() - len(contractions)
406
NB = [frozenset(B) for B in combinations(E, mr) if not self._is_basis(contractions | frozenset(B))]
407
return BasisMatroid(groundset=E, nonbases=NB, rank=mr)
408
409
cpdef truncation(self):
410
r"""
411
Return a rank-1 truncation of the matroid.
412
413
Let `M` be a matroid of rank `r`. The *truncation* of `M` is the
414
matroid obtained by declaring all subsets of size `r` dependent. It
415
can be obtained by adding an element freely to the span of the matroid
416
and then contracting that element.
417
418
OUTPUT:
419
420
A matroid.
421
422
.. SEEALSO::
423
424
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`,
425
:meth:`M.contract() <sage.matroids.matroid.Matroid.contract>`
426
427
EXAMPLES::
428
429
sage: M = Matroid(bases=matroids.named_matroids.N2().bases())
430
sage: M.truncation()
431
Matroid of rank 5 on 12 elements with 702 bases
432
sage: M.f_vector()
433
[1, 12, 66, 190, 258, 99, 1]
434
sage: M.truncation().f_vector()
435
[1, 12, 66, 190, 258, 1]
436
437
"""
438
if self.full_rank() == 0:
439
return None
440
return BasisMatroid(groundset=self._E, nonbases=self.dependent_r_sets(self.full_rank() - 1), rank=self.full_rank() - 1)
441
442
cpdef _extension(self, e, H):
443
r"""
444
Extend the matroid by a new element.
445
446
The result is a matroid on ``self.groundset() + {element}``, where
447
``element`` is contained in exactly the hyperplanes of ``self``
448
specified by ``hyperplanes``.
449
450
INPUT:
451
452
- ``element`` -- a hashable object not in ``self.groundset()``.
453
- ``hyperplanes`` -- the set of hyperplanes of a linear subclass of
454
``self``.
455
456
OUTPUT:
457
458
A matroid.
459
460
EXAMPLES::
461
462
sage: from sage.matroids.advanced import *
463
sage: M = BasisMatroid(matroids.Uniform(3, 5))
464
sage: H = M.hyperplanes()
465
sage: M._extension('x', [H[0]])
466
Matroid of rank 3 on 6 elements with 19 bases
467
sage: M._extension('x', [H[0], H[1]])
468
Matroid of rank 3 on 6 elements with 18 bases
469
sage: M._extension('x', H)
470
Matroid of rank 3 on 6 elements with 10 bases
471
sage: len([M._extension('x', mc) for mc in M.linear_subclasses()])
472
32
473
"""
474
cdef bint found
475
cdef frozenset B
476
if self.full_rank() == 0:
477
return BasisMatroid(groundset=self._E + (e,), bases=[set()])
478
479
BB = self.bases()
480
BT = self.independent_r_sets(self.full_rank() - 1)
481
se = set([e])
482
BE = []
483
for B in BT:
484
found = False
485
for hyp in H:
486
if B.issubset(hyp):
487
found = True
488
break
489
if not found:
490
BE.append(B | se)
491
BE += BB
492
return BasisMatroid(groundset=self._E + (e,), bases=BE)
493
494
cpdef _with_coloop(self, e):
495
r"""
496
Return the matroid that arises by adding an element `e` to the
497
groundset, that is a coloop of the resulting matroid.
498
499
INPUT:
500
501
- ``e`` -- the label of the new element. Assumed to be outside the
502
current groundset.
503
504
OUTPUT:
505
506
The extension of this matroid by a coloop.
507
508
EXAMPLES::
509
510
sage: from sage.matroids.advanced import *
511
sage: M = BasisMatroid(matroids.named_matroids.Fano())
512
sage: M
513
Matroid of rank 3 on 7 elements with 28 bases
514
sage: M._with_coloop('x')
515
Matroid of rank 4 on 8 elements with 28 bases
516
517
"""
518
cdef frozenset se = frozenset([e])
519
return BasisMatroid(groundset=self._E + (e,), bases=[B | se for B in self.bases()])
520
521
cpdef relabel(self, l):
522
"""
523
Return an isomorphic matroid with relabeled groundset.
524
525
The output is obtained by relabeling each element ``e`` by ``l[e]``,
526
where ``l`` is a given injective map. If ``e not in l`` then the
527
identity map is assumed.
528
529
INPUT:
530
531
- ``l`` -- a python object such that `l[e]` is the new label of `e`.
532
533
OUTPUT:
534
535
A matroid.
536
537
.. TODO::
538
539
Write abstract ``RelabeledMatroid`` class, and add ``relabel()``
540
method to the main ``Matroid`` class, together with ``_relabel()``
541
method that can be replaced by subclasses. Use the code from
542
``is_isomorphism()`` in ``relabel()`` to deal with a variety of
543
input methods for the relabeling.
544
545
EXAMPLES::
546
547
sage: from sage.matroids.advanced import *
548
sage: M = BasisMatroid(matroids.named_matroids.Fano())
549
sage: sorted(M.groundset())
550
['a', 'b', 'c', 'd', 'e', 'f', 'g']
551
sage: N = M.relabel({'g':'x'})
552
sage: sorted(N.groundset())
553
['a', 'b', 'c', 'd', 'e', 'f', 'x']
554
555
"""
556
M = BasisMatroid(M=self)
557
M.__relabel(l)
558
return M
559
560
# enumeration
561
562
cpdef bases_count(self):
563
r"""
564
Return the number of bases of the matroid.
565
566
OUTPUT:
567
568
Integer.
569
570
EXAMPLES::
571
572
sage: M = Matroid(bases=matroids.named_matroids.Fano().bases())
573
sage: M
574
Matroid of rank 3 on 7 elements with 28 bases
575
sage: M.bases_count()
576
28
577
"""
578
if self._bcount is None:
579
self._bcount = bitset_len(self._bb)
580
return self._bcount
581
582
cpdef bases(self):
583
r"""
584
Return the list of bases of the matroid.
585
586
A *basis* is a maximal independent set.
587
588
OUTPUT:
589
590
An iterable containing all bases of the matroid.
591
592
EXAMPLES::
593
594
sage: M = Matroid(bases=matroids.named_matroids.Fano().bases())
595
sage: M
596
Matroid of rank 3 on 7 elements with 28 bases
597
sage: len(M.bases())
598
28
599
"""
600
cdef long r, n
601
r = self.full_rank()
602
n = len(self)
603
cdef SetSystem BB
604
BB = SetSystem(self._E, capacity=bitset_len(self._bb))
605
cdef long b
606
b = bitset_first(self._bb)
607
while b >= 0:
608
index_to_set(self._b, b, r, n)
609
BB._append(self._b)
610
b = bitset_next(self._bb, b + 1)
611
return BB
612
613
cpdef nonbases(self):
614
r"""
615
Return the list of nonbases of the matroid.
616
617
A *nonbasis* is a set with cardinality ``self.full_rank()`` that is
618
not a basis.
619
620
OUTPUT:
621
622
An iterable containing the nonbases of the matroid.
623
624
.. SEEALSO::
625
626
:meth:`Matroid.basis() <sage.matroids.matroid.Matroid.basis>`
627
628
EXAMPLES::
629
630
sage: M = Matroid(bases=matroids.named_matroids.Fano().bases())
631
sage: M
632
Matroid of rank 3 on 7 elements with 28 bases
633
sage: len(M.nonbases())
634
7
635
"""
636
if self._nonbases is not None:
637
return self._nonbases
638
cdef long r, n
639
r = self.full_rank()
640
n = len(self)
641
cdef bitset_t bb_comp
642
bitset_init(bb_comp, binom[self._groundset_size][self._matroid_rank])
643
bitset_complement(bb_comp, self._bb)
644
cdef SetSystem NB
645
NB = SetSystem(self._E, capacity=bitset_len(bb_comp))
646
cdef long b
647
b = bitset_first(bb_comp)
648
while b >= 0:
649
index_to_set(self._b, b, r, n)
650
NB._append(self._b)
651
b = bitset_next(bb_comp, b + 1)
652
bitset_free(bb_comp)
653
self._nonbases = NB
654
return NB
655
656
# isomorphism test
657
658
cpdef _bases_invariant(self):
659
"""
660
Return an isomorphism invariant based on the incidences of groundset
661
elements with bases.
662
663
INPUT:
664
665
- Nothing
666
667
OUTPUT:
668
669
An integer.
670
671
EXAMPLES::
672
673
sage: from sage.matroids.advanced import *
674
sage: M = BasisMatroid(matroids.named_matroids.Fano())
675
sage: N = BasisMatroid(matroids.named_matroids.Fano())
676
sage: M._bases_invariant() == N._bases_invariant()
677
True
678
"""
679
if self._bases_invariant_var is not None:
680
return self._bases_invariant_var
681
cdef long i, j
682
cdef bitset_t bb_comp
683
cdef list bc
684
cdef dict bi
685
bc = [0 for i in xrange(len(self))]
686
for i in xrange(binom[self._groundset_size][self._matroid_rank]):
687
if not bitset_in(self._bb, i):
688
index_to_set(self._b, i, self._matroid_rank, self._groundset_size)
689
j = bitset_first(self._b)
690
while j >= 0:
691
bc[j] += 1
692
j = bitset_next(self._b, j + 1)
693
bi = {}
694
for e in xrange(len(self)):
695
if bc[e] in bi:
696
bi[bc[e]].append(e)
697
else:
698
bi[bc[e]] = [e]
699
self._bases_invariant_var = hash(tuple([(c, len(bi[c])) for c in sorted(bi)]))
700
self._bases_partition_var = SetSystem(self._E, [[self._E[e] for e in bi[c]] for c in sorted(bi)])
701
return self._bases_invariant_var
702
703
cpdef _bases_partition(self):
704
"""
705
Return an ordered partition based on the incidences of groundset
706
elements with bases.
707
708
EXAMPLES::
709
710
sage: from sage.matroids.advanced import *
711
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
712
sage: [sorted(p) for p in M._bases_partition()]
713
[['c', 'd', 'g', 'h'], ['a', 'b', 'e', 'f']]
714
"""
715
self._bases_invariant()
716
return self._bases_partition_var
717
718
cpdef _bases_invariant2(self):
719
"""
720
Return an isomophism invariant of the matroid.
721
722
Compared to BasisMatroid._bases_invariant() this invariant
723
distinguishes more frequently between nonisomorphic matroids but
724
takes more time to compute.
725
See also :meth:`<BasisMatroid.basis_partition2>`.
726
727
OUTPUT:
728
729
an integer isomorphism invariant.
730
731
EXAMPLES::
732
733
sage: from sage.matroids.advanced import *
734
sage: M = BasisMatroid(matroids.named_matroids.Fano())
735
sage: N = BasisMatroid(matroids.named_matroids.NonFano())
736
sage: M._bases_invariant2() == N._bases_invariant2()
737
False
738
"""
739
if self._bases_invariant2_var is None:
740
CP = self.nonbases()._equitable_partition(self._bases_partition())
741
self._bases_partition2_var = CP[0]
742
self._bases_invariant2_var = CP[2]
743
return self._bases_invariant2_var
744
745
cpdef _bases_partition2(self):
746
"""
747
Return an equitable partition which refines
748
:meth:`<BasisMatroid._bases_partition2>`.
749
750
EXAMPLES::
751
752
sage: from sage.matroids.advanced import *
753
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
754
sage: [sorted(p) for p in M._bases_partition2()]
755
[['c', 'd', 'g', 'h'], ['a', 'b', 'e', 'f']]
756
"""
757
self._bases_invariant2()
758
return self._bases_partition2_var
759
760
cpdef _bases_invariant3(self):
761
"""
762
Return a number characteristic for the construction of
763
:meth:`<BasisMatroid._bases_partition3>`.
764
765
EXAMPLES::
766
767
sage: from sage.matroids.advanced import *
768
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
769
sage: N = BasisMatroid(matroids.named_matroids.Vamos())
770
sage: M._bases_invariant3() == N._bases_invariant3()
771
True
772
"""
773
if self._bases_invariant3_var is None:
774
CP = self.nonbases()._heuristic_partition(self._bases_partition2())
775
self._bases_partition3_var = CP[0]
776
self._bases_invariant3_var = CP[2]
777
return self._bases_invariant3_var
778
779
cpdef _bases_partition3(self):
780
"""
781
Return an ordered partition into singletons which refines an equitable
782
partition of the matroid.
783
784
The purpose of this partition is to heuristically find an isomorphism
785
between two matroids, by lining up their respective
786
heuristic_partitions.
787
788
EXAMPLES::
789
790
sage: from sage.matroids.advanced import *
791
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
792
sage: N = BasisMatroid(matroids.named_matroids.Vamos())
793
sage: PM = M._bases_partition3()
794
sage: PN = N._bases_partition3()
795
sage: morphism = {}
796
sage: for i in xrange(len(M)): morphism[min(PM[i])]=min(PN[i])
797
sage: M._is_isomorphism(N, morphism)
798
True
799
"""
800
self._bases_invariant3()
801
return self._bases_partition3_var
802
803
cdef _reset_invariants(self):
804
"""
805
Remove all precomputed invariants.
806
"""
807
self._bcount = None
808
self._nonbases = None
809
self._bases_invariant_var = None
810
self._bases_partition_var = None
811
self._bases_invariant2_var = None
812
self._bases_partition2_var = None
813
self._bases_invariant3_var = None
814
self._bases_partition3_var = None
815
self._flush()
816
817
cpdef bint is_distinguished(self, e):
818
"""
819
Return whether ``e`` is a 'distinguished' element of the groundset.
820
821
The set of distinguished elements is an isomorphism invariant. Each
822
matroid has at least one distinguished element. The typical
823
application of this method is the execution of an orderly algorithm
824
for generating all matroids up to isomorphism in a minor-closed class,
825
by successively enumerating the single-element extensions and
826
coextensions of the matroids generated so far.
827
828
INPUT:
829
830
- ``e`` -- an element of the ground set
831
832
OUTPUT:
833
834
Boolean.
835
836
.. SEEALSO::
837
838
:meth:`M.extensions() <sage.matroids.matroid.Matroid.extensions>`,
839
:meth:`M.linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`,
840
:mod:`sage.matroids.extension <sage.matroids.extension>`
841
842
EXAMPLES::
843
844
sage: from sage.matroids.advanced import *
845
sage: M = BasisMatroid(matroids.named_matroids.N1())
846
sage: sorted([e for e in M.groundset() if M.is_distinguished(e)])
847
['c', 'g', 'h', 'j']
848
849
"""
850
P = self._bases_partition()
851
p = P[0]
852
if e not in p:
853
return False
854
if len(p) == 1:
855
return True
856
857
SP = self._bases_partition2()
858
q = p
859
for q2 in SP:
860
if q2.issubset(p) and len(q2) < len(q):
861
q = q2
862
return e in q
863
864
cpdef _is_relaxation(self, other, morphism):
865
"""
866
Return if the application of a groundset morphism to this matroid
867
yields a relaxation of the given matroid.
868
869
M is a relaxation of N if the set of bases of M is a subset of the
870
bases of N.
871
872
INPUT:
873
874
- ``other`` -- a BasisMatroid
875
- ``morphism`` -- a dictionary with sends each element of the
876
groundset of this matroid to a distinct element of the groundset
877
of ``other``
878
879
OUTPUT:
880
881
``True`` if ``morphism[self]`` is a relaxation of ``other``;
882
``False`` otherwise.
883
884
EXAMPLES::
885
886
sage: from sage.matroids.advanced import *
887
sage: M = BasisMatroid(matroids.named_matroids.NonFano())
888
sage: N = BasisMatroid(matroids.named_matroids.Fano())
889
sage: m = {e:e for e in M.groundset()}
890
sage: M._is_relaxation(N, m)
891
True
892
sage: N._is_relaxation(M, m)
893
False
894
"""
895
cdef long i, j
896
cdef bitset_t b2
897
cdef bitset_t bb_comp
898
899
bitset_init(bb_comp, binom[self._groundset_size][self._matroid_rank])
900
bitset_complement(bb_comp, self._bb)
901
902
bitset_init(b2, max(len(self), 1))
903
morph = [(<BasisMatroid>other)._idx[morphism[self._E[i]]] for i in xrange(len(self))]
904
i = bitset_first(bb_comp)
905
while i != -1:
906
index_to_set(self._b, i, self._matroid_rank, self._groundset_size)
907
bitset_clear(b2)
908
j = bitset_first(self._b)
909
while j != -1:
910
bitset_add(b2, morph[j])
911
j = bitset_next(self._b, j + 1)
912
if bitset_in((<BasisMatroid>other)._bb, set_to_index(b2)):
913
return False
914
i = bitset_next(bb_comp, i + 1)
915
return True
916
917
cpdef _is_isomorphism(self, other, morphism):
918
"""
919
Version of is_isomorphism() that does no type checking.
920
921
INPUT:
922
923
- ``other`` -- A matroid instance.
924
- ``morphism`` -- a dictionary mapping the groundset of ``self`` to
925
the groundset of ``other``.
926
927
OUTPUT:
928
929
Boolean.
930
931
.. SEEALSO::
932
933
:meth:`<sage.matroids.matroid.Matroid.is_isomorphism>`.
934
935
EXAMPLES::
936
937
sage: from sage.matroids.advanced import *
938
sage: M = BasisMatroid(matroids.named_matroids.NonFano())
939
sage: N = BasisMatroid(matroids.named_matroids.Fano())
940
sage: m = {e:e for e in M.groundset()}
941
sage: M._is_relaxation(N, m)
942
True
943
sage: M._is_isomorphism(N, m)
944
False
945
"""
946
if not isinstance(other, BasisMatroid):
947
ot = BasisMatroid(other)
948
else:
949
ot = other
950
return self.bases_count() == (<BasisMatroid>ot).bases_count() and self._is_relaxation(ot, morphism)
951
952
cpdef _is_isomorphic(self, other):
953
"""
954
Return if this matroid is isomorphic to the given matroid.
955
956
INPUT:
957
958
- ``other`` -- a matroid.
959
960
OUTPUT:
961
962
Boolean.
963
964
.. NOTE::
965
966
Internal version that does no input checking.
967
968
EXAMPLES::
969
970
sage: from sage.matroids.advanced import *
971
sage: M = BasisMatroid(matroids.named_matroids.NonFano())
972
sage: N = BasisMatroid(matroids.named_matroids.Fano())
973
sage: M._is_isomorphic(N)
974
False
975
"""
976
if not isinstance(other, BasisMatroid):
977
return BasisExchangeMatroid._is_isomorphic(self, other)
978
if self is other:
979
return True
980
if len(self) != len(other):
981
return False
982
if self.full_rank() != other.full_rank():
983
return False
984
if self.full_rank() == 0:
985
return True
986
if self.bases_count() != other.bases_count():
987
return False
988
if self.full_rank() < 2 or self.full_corank() < 2:
989
return True # number of bases then determines matroid up to isomorphism
990
991
if self._bases_invariant() != other._bases_invariant():
992
return False
993
PS = self._bases_partition()
994
PO = other._bases_partition()
995
if len(PS) == len(self) and len(PO) == len(other):
996
morphism = {}
997
for i in xrange(len(self)):
998
morphism[min(PS[i])] = min(PO[i])
999
return self._is_relaxation(other, morphism)
1000
1001
if self._bases_invariant2() != other._bases_invariant2():
1002
return False
1003
PS = self._bases_partition2()
1004
PO = other._bases_partition2()
1005
if len(PS) == len(self) and len(PO) == len(other):
1006
morphism = {}
1007
for i in xrange(len(self)):
1008
morphism[min(PS[i])] = min(PO[i])
1009
return self._is_relaxation(other, morphism)
1010
1011
if self._bases_invariant3() == other._bases_invariant3():
1012
PHS = self._bases_partition3()
1013
PHO = other._bases_partition3()
1014
morphism = {}
1015
for i in xrange(len(self)):
1016
morphism[min(PHS[i])] = min(PHO[i])
1017
if self._is_relaxation(other, morphism):
1018
return True
1019
1020
return self.nonbases()._isomorphism(other.nonbases(), PS, PO) is not None
1021
1022
def __hash__(self):
1023
r"""
1024
Return an invariant of the matroid.
1025
1026
This function is called when matroids are added to a set. It is very
1027
desirable to override it so it can distinguish matroids on the same
1028
groundset, which is a very typical use case!
1029
1030
.. WARNING::
1031
1032
This method is linked to __richcmp__ (in Cython) and __cmp__ or
1033
__eq__/__ne__ (in Python). If you override one, you should (and in
1034
Cython: MUST) override the other!
1035
1036
EXAMPLES::
1037
1038
sage: from sage.matroids.advanced import *
1039
sage: M = BasisMatroid(matroids.named_matroids.Fano())
1040
sage: N = BasisMatroid(matroids.named_matroids.Fano().dual()).dual()
1041
sage: O = BasisMatroid(matroids.named_matroids.NonFano())
1042
sage: hash(M) == hash(N)
1043
True
1044
sage: hash(M) == hash(O)
1045
False
1046
"""
1047
return hash((self.groundset(), self.bases_count(), self._weak_invariant()))
1048
1049
def __richcmp__(left, right, int op):
1050
r"""
1051
Compare two matroids.
1052
1053
We take a very restricted view on equality: the objects need to be of
1054
the exact same type (so no subclassing) and the internal data need to
1055
be the same. For BasisMatroids, this means that the groundsets and the
1056
sets of bases of the two matroids are equal.
1057
1058
EXAMPLES::
1059
1060
sage: from sage.matroids.advanced import *
1061
sage: M = BasisMatroid(matroids.named_matroids.Pappus())
1062
sage: N = BasisMatroid(matroids.named_matroids.NonPappus())
1063
sage: M == N
1064
False
1065
"""
1066
if op in [0, 1, 4, 5]: # <, <=, >, >=
1067
return NotImplemented
1068
if not isinstance(left, BasisMatroid) or not isinstance(right, BasisMatroid):
1069
return NotImplemented
1070
if op == 2: # ==
1071
res = True
1072
if op == 3: # !=
1073
res = False
1074
# res gets inverted if matroids are deemed different.
1075
if left.equals(right):
1076
return res
1077
else:
1078
return not res
1079
1080
def __copy__(self):
1081
"""
1082
Create a shallow copy.
1083
1084
EXAMPLES::
1085
1086
sage: from sage.matroids.advanced import *
1087
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
1088
sage: N = copy(M) # indirect doctest
1089
sage: M == N
1090
True
1091
1092
"""
1093
N = BasisMatroid(M=self)
1094
N.rename(getattr(self, '__custom_name'))
1095
return N
1096
1097
def __deepcopy__(self, memo={}):
1098
"""
1099
Create a deep copy.
1100
1101
.. NOTE::
1102
1103
Identical to shallow copy for BasisMatroid class.
1104
1105
EXAMPLES::
1106
1107
sage: from sage.matroids.advanced import *
1108
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
1109
sage: N = deepcopy(M) # indirect doctest
1110
sage: M == N
1111
True
1112
sage: M.groundset() is N.groundset()
1113
False
1114
"""
1115
N = BasisMatroid(M=self)
1116
N.rename(getattr(self, '__custom_name'))
1117
return N
1118
1119
def __reduce__(self):
1120
"""
1121
Save the matroid for later reloading.
1122
1123
OUTPUT:
1124
1125
A tuple ``(unpickle, (version, data))``, where ``unpickle`` is the
1126
name of a function that, when called with ``(version, data)``,
1127
produces a matroid isomorphic to ``self``. ``version`` is an integer
1128
(currently 0) and ``data`` is a tuple ``(E, R, name, BB)`` where
1129
``E`` is the groundset of the matroid, ``R`` is the rank, ``name`` is a
1130
custom name, and ``BB`` is the bitpacked list of bases, as pickled by
1131
Sage's ``bitset_pickle``.
1132
1133
EXAMPLES::
1134
1135
sage: from sage.matroids.advanced import *
1136
sage: M = BasisMatroid(matroids.named_matroids.Vamos())
1137
sage: M == loads(dumps(M)) # indirect doctest
1138
True
1139
sage: M.rename('Vamos')
1140
sage: loads(dumps(M))
1141
Vamos
1142
"""
1143
import sage.matroids.unpickling
1144
BB = bitset_pickle(self._bb)
1145
data = (self._E, self._matroid_rank, getattr(self, '__custom_name'), BB)
1146
version = 0
1147
return sage.matroids.unpickling.unpickle_basis_matroid, (version, data)
1148
1149
1150
cdef long binom[2956][33] # Cached binomial table
1151
1152
1153
cdef binom_init(long N, long K):
1154
"""
1155
Fill up the cached binomial table.
1156
"""
1157
cdef long bin
1158
if binom[0][0] != 1:
1159
binom[0][0] = 1
1160
binom[0][1] = 0
1161
for n in xrange(1, 2955):
1162
bin = 1
1163
k = 0
1164
while bin < 2 ** 32 and k <= 32 and k <= n:
1165
binom[n][k] = bin
1166
k += 1
1167
bin = binom[n - 1][k - 1] + binom[n - 1][k]
1168
while k < 33:
1169
binom[n][k] = 0
1170
k += 1
1171
1172
if N > 2954:
1173
raise ValueError("BasisMatroid: size of groundset exceeds 2954") # if n > 2954 and k > 2, then binomial(n, k) > 2^32
1174
if K > 32:
1175
raise ValueError("BasisMatroid: rank exceeds 32") # if n > 2954 and k > 2, then binomial(n, k) > 2^32
1176
if binom[N][K] == 0:
1177
raise ValueError("BasisMatroid: number of potential bases would exceed 2^32")
1178
1179
cdef long set_to_index(bitset_t S):
1180
"""
1181
Compute the rank of a set of integers amongst the sets of integers
1182
of the same cardinality.
1183
"""
1184
cdef long index = 0
1185
cdef long count = 1
1186
cdef long s
1187
s = bitset_first(S)
1188
while s >= 0:
1189
index += binom[s][count]
1190
count += 1
1191
s = bitset_next(S, s + 1)
1192
return index
1193
1194
cdef index_to_set(bitset_t S, long index, long k, long n):
1195
"""
1196
Compute the k-subset of `\{0, ..., n-1\}` of rank index
1197
"""
1198
bitset_clear(S)
1199
cdef long s = n
1200
while s > 0:
1201
s -= 1
1202
if binom[s][k] <= index:
1203
index = index - binom[s][k]
1204
k = k - 1
1205
bitset_add(S, s)
1206
1207