Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/matroids/matroid.pyx
8817 views
1
r"""
2
The abstract Matroid class
3
4
Matroids are combinatorial structures that capture the abstract properties
5
of (linear/algebraic/...) dependence. See the :wikipedia:`Wikipedia article on
6
matroids <Matroid>` for theory and examples. In Sage, various types of
7
matroids are supported:
8
:class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`,
9
:class:`CircuitClosuresMatroid <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`,
10
:class:`LinearMatroid <sage.matroids.linear_matroid.LinearMatroid>`
11
(and some specialized subclasses),
12
:class:`RankMatroid <sage.matroids.rank_matroid.RankMatroid>`.
13
To construct them, use the function
14
:func:`Matroid() <sage.matroids.constructor.Matroid>`.
15
16
All these classes share a common interface, which includes the following
17
methods (organized by category). Note that most subclasses (notably
18
:mod:`LinearMatroids <sage.matroids.linear_matroid>`) will implement
19
additional functionality (e.g. linear extensions).
20
21
- Ground set:
22
- :meth:`groundset() <sage.matroids.matroid.Matroid.groundset>`
23
- :meth:`size() <sage.matroids.matroid.Matroid.size>`
24
25
- Rank, bases, circuits, closure
26
- :meth:`rank() <sage.matroids.matroid.Matroid.rank>`
27
- :meth:`full_rank() <sage.matroids.matroid.Matroid.full_rank>`
28
- :meth:`basis() <sage.matroids.matroid.Matroid.basis>`
29
- :meth:`max_independent() <sage.matroids.matroid.Matroid.max_independent>`
30
- :meth:`circuit() <sage.matroids.matroid.Matroid.circuit>`
31
- :meth:`fundamental_circuit() <sage.matroids.matroid.Matroid.fundamental_circuit>`
32
- :meth:`closure() <sage.matroids.matroid.Matroid.closure>`
33
- :meth:`augment() <sage.matroids.matroid.Matroid.augment>`
34
35
- :meth:`corank() <sage.matroids.matroid.Matroid.corank>`
36
- :meth:`full_corank() <sage.matroids.matroid.Matroid.full_corank>`
37
- :meth:`cobasis() <sage.matroids.matroid.Matroid.cobasis>`
38
- :meth:`max_coindependent() <sage.matroids.matroid.Matroid.max_coindependent>`
39
- :meth:`cocircuit() <sage.matroids.matroid.Matroid.cocircuit>`
40
- :meth:`fundamental_cocircuit() <sage.matroids.matroid.Matroid.fundamental_cocircuit>`
41
- :meth:`coclosure() <sage.matroids.matroid.Matroid.coclosure>`
42
43
- :meth:`is_independent() <sage.matroids.matroid.Matroid.is_independent>`
44
- :meth:`is_dependent() <sage.matroids.matroid.Matroid.is_dependent>`
45
- :meth:`is_basis() <sage.matroids.matroid.Matroid.is_basis>`
46
- :meth:`is_circuit() <sage.matroids.matroid.Matroid.is_circuit>`
47
- :meth:`is_closed() <sage.matroids.matroid.Matroid.is_closed>`
48
49
- :meth:`is_coindependent() <sage.matroids.matroid.Matroid.is_coindependent>`
50
- :meth:`is_codependent() <sage.matroids.matroid.Matroid.is_codependent>`
51
- :meth:`is_cobasis() <sage.matroids.matroid.Matroid.is_cobasis>`
52
- :meth:`is_cocircuit() <sage.matroids.matroid.Matroid.is_cocircuit>`
53
- :meth:`is_coclosed() <sage.matroids.matroid.Matroid.is_coclosed>`
54
55
- Verification
56
- :meth:`is_valid() <sage.matroids.matroid.Matroid.is_valid>`
57
58
- Enumeration
59
- :meth:`circuits() <sage.matroids.matroid.Matroid.circuits>`
60
- :meth:`nonspanning_circuits() <sage.matroids.matroid.Matroid.nonspanning_circuits>`
61
- :meth:`cocircuits() <sage.matroids.matroid.Matroid.cocircuits>`
62
- :meth:`noncospanning_cocircuits() <sage.matroids.matroid.Matroid.noncospanning_cocircuits>`
63
- :meth:`circuit_closures() <sage.matroids.matroid.Matroid.circuit_closures>`
64
- :meth:`nonspanning_circuit_closures() <sage.matroids.matroid.Matroid.nonspanning_circuit_closures>`
65
- :meth:`bases() <sage.matroids.matroid.Matroid.bases>`
66
- :meth:`independent_r_sets() <sage.matroids.matroid.Matroid.independent_r_sets>`
67
- :meth:`nonbases() <sage.matroids.matroid.Matroid.nonbases>`
68
- :meth:`dependent_r_sets() <sage.matroids.matroid.Matroid.dependent_r_sets>`
69
- :meth:`flats() <sage.matroids.matroid.Matroid.flats>`
70
- :meth:`coflats() <sage.matroids.matroid.Matroid.coflats>`
71
- :meth:`hyperplanes() <sage.matroids.matroid.Matroid.hyperplanes>`
72
- :meth:`f_vector() <sage.matroids.matroid.Matroid.f_vector>`
73
74
- Comparison
75
- :meth:`is_isomorphic() <sage.matroids.matroid.Matroid.is_isomorphic>`
76
- :meth:`equals() <sage.matroids.matroid.Matroid.equals>`
77
- :meth:`is_isomorphism() <sage.matroids.matroid.Matroid.is_isomorphism>`
78
79
- Minors, duality, truncation
80
- :meth:`minor() <sage.matroids.matroid.Matroid.minor>`
81
- :meth:`contract() <sage.matroids.matroid.Matroid.contract>`
82
- :meth:`delete() <sage.matroids.matroid.Matroid.delete>`
83
- :meth:`dual() <sage.matroids.matroid.Matroid.dual>`
84
- :meth:`truncation() <sage.matroids.matroid.Matroid.truncation>`
85
- :meth:`has_minor() <sage.matroids.matroid.Matroid.has_minor>`
86
- :meth:`has_line_minor() <sage.matroids.matroid.Matroid.has_line_minor>`
87
88
89
- Extension
90
- :meth:`extension() <sage.matroids.matroid.Matroid.extension>`
91
- :meth:`coextension() <sage.matroids.matroid.Matroid.coextension>`
92
- :meth:`modular_cut() <sage.matroids.matroid.Matroid.modular_cut>`
93
- :meth:`linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`
94
- :meth:`extensions() <sage.matroids.matroid.Matroid.extensions>`
95
- :meth:`coextensions() <sage.matroids.matroid.Matroid.coextensions>`
96
97
- Connectivity, simplicity
98
- :meth:`loops() <sage.matroids.matroid.Matroid.loops>`
99
- :meth:`coloops() <sage.matroids.matroid.Matroid.coloops>`
100
- :meth:`simplify() <sage.matroids.matroid.Matroid.simplify>`
101
- :meth:`cosimplify() <sage.matroids.matroid.Matroid.cosimplify>`
102
- :meth:`is_simple() <sage.matroids.matroid.Matroid.is_simple>`
103
- :meth:`is_cosimple() <sage.matroids.matroid.Matroid.is_cosimple>`
104
- :meth:`components() <sage.matroids.matroid.Matroid.components>`
105
- :meth:`is_connected() <sage.matroids.matroid.Matroid.is_connected>`
106
- :meth:`is_3connected() <sage.matroids.matroid.Matroid.is_3connected>`
107
108
- Optimization
109
- :meth:`max_weight_independent() <sage.matroids.matroid.Matroid.max_weight_independent>`
110
- :meth:`max_weight_coindependent() <sage.matroids.matroid.Matroid.max_weight_coindependent>`
111
- :meth:`intersection() <sage.matroids.matroid.Matroid.intersection>`
112
113
- Invariants
114
- :meth:`tutte_polynomial() <sage.matroids.matroid.Matroid.tutte_polynomial>`
115
- :meth:`flat_cover() <sage.matroids.matroid.Matroid.flat_cover>`
116
117
In addition to these, all methods provided by
118
:class:`SageObject <sage.structure.sage_object.SageObject>` are available,
119
notably :meth:`save() <sage.structure.sage_object.SageObject.save>` and
120
:meth:`rename() <sage.structure.sage_object.SageObject.rename>`.
121
122
Advanced usage
123
==============
124
Many methods (such as ``M.rank()``) have a companion method whose name starts
125
with an underscore (such as ``M._rank()``). The method with the underscore
126
does not do any checks on its input. For instance, it may assume of its input
127
that
128
129
- It is a subset of the groundset. The interface is compatible with Python's
130
``frozenset`` type.
131
- It is a list of things, supports iteration, and recursively these rules
132
apply to its members.
133
134
Using the underscored version could improve the speed of code a little, but
135
will generate more cryptic error messages when presented with wrong input.
136
In some instances, no error might occur and a nonsensical answer returned.
137
138
A subclass should always override the underscored method, if available, and as
139
a rule leave the regular method alone.
140
141
These underscored methods are not documented in the reference manual. To see
142
them, within Sage you can create a matroid ``M`` and type ``M._<tab>``. Then
143
``M._rank?`` followed by ``<tab>`` will bring up the documentation string of
144
the ``_rank()`` method.
145
146
Creating new Matroid subclasses
147
===============================
148
Many mathematical objects give rise to matroids, and not all are available
149
through the provided code. For incidental use, the
150
:mod:`RankMatroid <sage.matroids.rank_matroid>` subclass may suffice. If you
151
regularly use matroids based on a new data type, you can write a subclass of
152
``Matroid``. You only need to override the ``__init__``, ``_rank()`` and
153
``groundset()`` methods to get a fully working class.
154
155
EXAMPLE:
156
157
In a partition matroid, a subset is independent if it has at most one
158
element from each partition. The following is a very basic implementation,
159
in which the partition is specified as a list of lists::
160
161
sage: import sage.matroids.matroid
162
sage: class PartitionMatroid(sage.matroids.matroid.Matroid):
163
....: def __init__(self, partition):
164
....: self.partition = partition
165
....: E = set()
166
....: for P in partition:
167
....: E.update(P)
168
....: self.E = frozenset(E)
169
....: def groundset(self):
170
....: return self.E
171
....: def _rank(self, X):
172
....: X2 = set(X)
173
....: used_indices = set()
174
....: rk = 0
175
....: while len(X2) > 0:
176
....: e = X2.pop()
177
....: for i in range(len(self.partition)):
178
....: if e in self.partition[i]:
179
....: if i not in used_indices:
180
....: used_indices.add(i)
181
....: rk = rk + 1
182
....: break
183
....: return rk
184
....:
185
sage: M = PartitionMatroid([[1, 2], [3, 4, 5], [6, 7]])
186
sage: M.full_rank()
187
3
188
sage: M.tutte_polynomial(var('x'), var('y'))
189
x^2*y^2 + 2*x*y^3 + y^4 + x^3 + 3*x^2*y + 3*x*y^2 + y^3
190
191
.. NOTE::
192
193
The abstract base class has no idea about the data used to represent the
194
matroid. Hence some methods need to be customized to function properly.
195
196
Necessary:
197
198
- ``def __init__(self, ...)``
199
- ``def groundset(self)``
200
- ``def _rank(self, X)``
201
202
Representation:
203
204
- ``def _repr_(self)``
205
206
Comparison:
207
208
- ``def __hash__(self)``
209
- ``def __eq__(self, other)``
210
- ``def __ne__(self, other)``
211
212
In Cythonized classes, use ``__richcmp__()`` instead of ``__eq__()``,
213
``__ne__()``.
214
215
Copying, loading, saving:
216
217
- ``def __copy__(self)``
218
- ``def __deepcopy__(self, memo={})``
219
- ``def __reduce__(self)``
220
221
See, for instance, :class:`rank_matroid <sage.matroids.rank_matroid>` or
222
:class:`circuit_closures_matroid <sage.matroids.circuit_closures_matroid>`
223
for sample implementations of these.
224
225
.. NOTE::
226
227
The example provided does not check its input at all. You may want to make
228
sure the input data are not corrupt.
229
230
Some examples
231
=============
232
233
EXAMPLES:
234
235
Construction::
236
237
sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1],
238
....: [0, 1, 0, 1, 0, 1, 1],
239
....: [0, 0, 1, 1, 1, 0, 1]]))
240
sage: sorted(M.groundset())
241
[0, 1, 2, 3, 4, 5, 6]
242
sage: M.rank([0, 1, 2])
243
3
244
sage: M.rank([0, 1, 5])
245
2
246
247
Minors::
248
249
sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1],
250
....: [0, 1, 0, 1, 0, 1, 1],
251
....: [0, 0, 1, 1, 1, 0, 1]]))
252
sage: N = M / [2] \ [3, 4]
253
sage: sorted(N.groundset())
254
[0, 1, 5, 6]
255
sage: N.full_rank()
256
2
257
258
Testing. Note that the abstract base class does not support pickling::
259
260
sage: M = sage.matroids.matroid.Matroid()
261
sage: TestSuite(M).run(skip="_test_pickling")
262
263
REFERENCES
264
==========
265
266
.. [BC79] R. E. Bixby, W. H. Cunningham, Matroids, Graphs, and 3-Connectivity. In Graph theory and related topics (Proc. Conf., Univ. Waterloo, Waterloo, ON, 1977), 91-103
267
.. [CMO11] C. Chun, D. Mayhew, J. Oxley, A chain theorem for internally 4-connected binary matroids. J. Combin. Theory Ser. B 101 (2011), 141-189.
268
.. [CMO12] C. Chun, D. Mayhew, J. Oxley, Towards a splitter theorem for internally 4-connected binary matroids. J. Combin. Theory Ser. B 102 (2012), 688-700.
269
.. [GG12] Jim Geelen and Bert Gerards, Characterizing graphic matroids by a system of linear equations, submitted, 2012. Preprint: http://www.gerardsbase.nl/papers/geelen_gerards=testing-graphicness%5B2013%5D.pdf
270
.. [GR01] C.Godsil and G.Royle, Algebraic Graph Theory. Graduate Texts in Mathematics, Springer, 2001.
271
.. [Hlineny] Petr Hlineny, "Equivalence-free exhaustive generation of matroid representations", Discrete Applied Mathematics 154 (2006), pp. 1210-1222.
272
.. [Hochstaettler] Winfried Hochstaettler, "About the Tic-Tac-Toe Matroid", preprint.
273
.. [Lyons] R. Lyons, Determinantal probability measures. Publications Mathematiques de l'Institut des Hautes Etudes Scientifiques 98(1) (2003), pp. 167-212.
274
.. [Oxley1] James Oxley, "Matroid theory", Oxford University Press, 1992.
275
.. [Oxley] James Oxley, "Matroid Theory, Second Edition". Oxford University Press, 2011.
276
.. [Pen12] R. Pendavingh, On the evaluation at (-i, i) of the Tutte polynomial of a binary matroid. Preprint: http://arxiv.org/abs/1203.0910
277
278
AUTHORS:
279
280
- Michael Welsh (2013-04-03): Changed flats() to use SetSystem
281
- Michael Welsh (2013-04-01): Added is_3connected(), using naive algorithm
282
- Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version
283
284
Methods
285
=======
286
"""
287
#*****************************************************************************
288
# Copyright (C) 2013 Rudi Pendavingh <[email protected] >
289
# Copyright (C) 2013 Michael Welsh <[email protected] >
290
# Copyright (C) 2013 Stefan van Zwam <[email protected]>
291
#
292
#
293
# Distributed under the terms of the GNU General Public License (GPL)
294
# as published by the Free Software Foundation; either version 2 of
295
# the License, or (at your option) any later version.
296
# http://www.gnu.org/licenses/
297
#*****************************************************************************
298
299
from sage.structure.sage_object cimport SageObject
300
from itertools import combinations, permutations
301
from set_system cimport SetSystem
302
from sage.combinat.subset import Subsets
303
304
from utilities import newlabel, sanitize_contractions_deletions
305
from sage.calculus.all import var
306
from sage.rings.all import ZZ
307
from sage.numerical.mip import MixedIntegerLinearProgram
308
309
310
# On some systems, macros "minor()" and "major()" are defined in system header
311
# files. This will undefine those:
312
cdef extern from "minorfix.h":
313
pass
314
315
cdef class Matroid(SageObject):
316
r"""
317
The abstract matroid class, from which all matroids are derived. Do not
318
use this class directly!
319
320
To implement a subclass, the least you should do is implement the
321
``__init__()``, ``_rank()`` and ``groundset()`` methods. See the source of
322
:mod:`rank_matroid.py <sage.matroids.rank_matroid>` for a bare-bones
323
example of this.
324
325
EXAMPLES:
326
327
In a partition matroid, a subset is independent if it has at most one
328
element from each partition. The following is a very basic implementation,
329
in which the partition is specified as a list of lists::
330
331
sage: class PartitionMatroid(sage.matroids.matroid.Matroid):
332
....: def __init__(self, partition):
333
....: self.partition = partition
334
....: E = set()
335
....: for P in partition:
336
....: E.update(P)
337
....: self.E = frozenset(E)
338
....: def groundset(self):
339
....: return self.E
340
....: def _rank(self, X):
341
....: X2 = set(X)
342
....: used_indices = set()
343
....: rk = 0
344
....: while len(X2) > 0:
345
....: e = X2.pop()
346
....: for i in range(len(self.partition)):
347
....: if e in self.partition[i]:
348
....: if i not in used_indices:
349
....: used_indices.add(i)
350
....: rk = rk + 1
351
....: break
352
....: return rk
353
....:
354
sage: M = PartitionMatroid([[1, 2], [3, 4, 5], [6, 7]])
355
sage: M.full_rank()
356
3
357
sage: M.tutte_polynomial(var('x'), var('y'))
358
x^2*y^2 + 2*x*y^3 + y^4 + x^3 + 3*x^2*y + 3*x*y^2 + y^3
359
360
.. NOTE::
361
362
The abstract base class has no idea about the data used to represent
363
the matroid. Hence some methods need to be customized to function
364
properly.
365
366
Necessary:
367
368
- ``def __init__(self, ...)``
369
- ``def groundset(self)``
370
- ``def _rank(self, X)``
371
372
Representation:
373
374
- ``def _repr_(self)``
375
376
Comparison:
377
378
- ``def __hash__(self)``
379
- ``def __eq__(self, other)``
380
- ``def __ne__(self, other)``
381
382
In Cythonized classes, use ``__richcmp__()`` instead of ``__eq__()``,
383
``__ne__()``.
384
385
Copying, loading, saving:
386
387
- ``def __copy__(self)``
388
- ``def __deepcopy__(self, memo={})``
389
- ``def __reduce__(self)``
390
391
See, for instance,
392
:mod:`rank_matroid.py <sage.matroids.rank_matroid>` or
393
:mod:`circuit_closures_matroid.pyx <sage.matroids.circuit_closures_matroid>`
394
for sample implementations of these.
395
396
.. NOTE::
397
398
Many methods (such as ``M.rank()``) have a companion method whose name
399
starts with an underscore (such as ``M._rank()``). The method with the
400
underscore does not do any checks on its input. For instance, it may
401
assume of its input that
402
403
- Any input that should be a subset of the groundset, is one. The
404
interface is compatible with Python's ``frozenset`` type.
405
- Any input that should be a list of things, supports iteration, and
406
recursively these rules apply to its members.
407
408
Using the underscored version could improve the speed of code a
409
little, but will generate more cryptic error messages when presented
410
with wrong input. In some instances, no error might occur and a
411
nonsensical answer returned.
412
413
A subclass should always override the underscored method, if
414
available, and as a rule leave the regular method alone.
415
416
"""
417
418
# virtual methods
419
420
cpdef groundset(self):
421
"""
422
Return the groundset of the matroid.
423
424
The groundset is the set of elements that comprise the matroid.
425
426
OUTPUT:
427
428
A set.
429
430
.. NOTE::
431
432
Subclasses should implement this method. The return type should be
433
frozenset or any type with compatible interface.
434
435
EXAMPLES::
436
437
sage: M = sage.matroids.matroid.Matroid()
438
sage: M.groundset()
439
Traceback (most recent call last):
440
...
441
NotImplementedError: subclasses need to implement this.
442
443
"""
444
raise NotImplementedError("subclasses need to implement this.")
445
446
cpdef _rank(self, X):
447
r"""
448
Return the rank of a set ``X``.
449
450
This method does no checking on ``X``, and ``X`` may be assumed to
451
have the same interface as ``frozenset``.
452
453
INPUT:
454
455
- ``X`` -- an object with Python's ``frozenset`` interface.
456
457
OUTPUT:
458
459
Integer.
460
461
.. NOTE::
462
463
Subclasses should implement this method.
464
465
EXAMPLES::
466
467
sage: M = sage.matroids.matroid.Matroid()
468
sage: M._rank([0, 1, 2])
469
Traceback (most recent call last):
470
...
471
NotImplementedError: subclasses need to implement this.
472
473
"""
474
raise NotImplementedError("subclasses need to implement this.")
475
476
# internal methods, assuming verified input
477
478
# for better efficiency, its best to override the following methods in
479
# each derived class
480
481
cpdef _max_independent(self, X):
482
"""
483
Compute a maximal independent subset.
484
485
INPUT:
486
487
- ``X`` -- An object with Python's ``frozenset`` interface containing
488
a subset of ``self.groundset()``.
489
490
OUTPUT:
491
492
``frozenset`` instance containing a subset of the groundset.
493
494
EXAMPLES::
495
496
sage: M = matroids.named_matroids.Vamos()
497
sage: sorted(M._max_independent(set(['a', 'c', 'd', 'e', 'f'])))
498
['a', 'd', 'e', 'f']
499
500
"""
501
res = set([])
502
r = 0
503
for e in X:
504
res.add(e)
505
if self._rank(res) > r:
506
r += 1
507
else:
508
res.discard(e)
509
return frozenset(res)
510
511
cpdef _circuit(self, X):
512
"""
513
Return a minimal dependent subset.
514
515
INPUT:
516
517
- ``X`` -- An object with Python's ``frozenset`` interface containing
518
a subset of ``self.groundset()``.
519
520
OUTPUT:
521
522
``frozenset`` instance containing a subset of the groundset.
523
A ``ValueError`` is raised if the set contains no circuit.
524
525
EXAMPLES::
526
527
sage: M = matroids.named_matroids.Vamos()
528
sage: sorted(sage.matroids.matroid.Matroid._circuit(M,
529
....: set(['a', 'c', 'd', 'e', 'f'])))
530
['c', 'd', 'e', 'f']
531
sage: sorted(sage.matroids.matroid.Matroid._circuit(M,
532
....: set(['a', 'c', 'd'])))
533
Traceback (most recent call last):
534
...
535
ValueError: no circuit in independent set.
536
537
"""
538
Z = set(X)
539
if self._is_independent(X):
540
raise ValueError("no circuit in independent set.")
541
l = len(X) - 1
542
for x in X:
543
Z.discard(x)
544
if self._rank(Z) == l:
545
Z.add(x)
546
else:
547
l -= 1
548
return frozenset(Z)
549
550
cpdef _fundamental_circuit(self, B, e):
551
r"""
552
Return the `B`-fundamental circuit using `e`.
553
554
Internal version that does no input checking.
555
556
INPUT:
557
558
- ``B`` -- a basis of the matroid.
559
- ``e`` -- an element not in ``B``.
560
561
OUTPUT:
562
563
A set of elements.
564
565
EXAMPLES::
566
567
sage: M = matroids.named_matroids.Vamos()
568
sage: sorted(M._fundamental_circuit(frozenset('defg'), 'c'))
569
['c', 'd', 'e', 'f']
570
"""
571
return self._circuit(B.union([e]))
572
573
cpdef _closure(self, X):
574
"""
575
Return the closure of a set.
576
577
INPUT:
578
579
- ``X`` -- An object with Python's ``frozenset`` interface containing
580
a subset of ``self.groundset()``.
581
582
OUTPUT:
583
584
``frozenset`` instance containing a subset of the groundset.
585
586
EXAMPLES::
587
588
sage: M = matroids.named_matroids.Vamos()
589
sage: sorted(M._closure(set(['a', 'b', 'c'])))
590
['a', 'b', 'c', 'd']
591
592
"""
593
X = set(X)
594
Y = self.groundset().difference(X)
595
r = self._rank(X)
596
for y in Y:
597
X.add(y)
598
if self._rank(X) > r:
599
X.discard(y)
600
return frozenset(X)
601
602
cpdef _corank(self, X):
603
"""
604
Return the corank of a set.
605
606
INPUT:
607
608
- ``X`` -- An object with Python's ``frozenset`` interface containing
609
a subset of ``self.groundset()``.
610
611
OUTPUT:
612
613
Integer.
614
615
EXAMPLES::
616
617
sage: M = matroids.named_matroids.Vamos()
618
sage: M._corank(set(['a', 'e', 'g', 'd', 'h']))
619
4
620
621
"""
622
return len(X) + self._rank(self.groundset().difference(X)) - self.full_rank()
623
624
cpdef _max_coindependent(self, X):
625
"""
626
Compute a maximal coindependent subset.
627
628
INPUT:
629
630
- ``X`` -- An object with Python's ``frozenset`` interface containing
631
a subset of ``self.groundset()``.
632
633
OUTPUT:
634
635
``frozenset`` instance containing a subset of the groundset.
636
637
EXAMPLES::
638
639
sage: M = matroids.named_matroids.Vamos()
640
sage: sorted(M._max_coindependent(set(['a', 'c', 'd', 'e', 'f'])))
641
['a', 'c', 'd', 'e']
642
643
"""
644
res = set([])
645
r = 0
646
for e in X:
647
res.add(e)
648
if self._corank(res) > r:
649
r += 1
650
else:
651
res.discard(e)
652
return frozenset(res)
653
654
cpdef _cocircuit(self, X):
655
"""
656
Return a minimal codependent subset.
657
658
INPUT:
659
660
- ``X`` -- An object with Python's ``frozenset`` interface containing
661
a subset of ``self.groundset()``.
662
663
OUTPUT:
664
665
``frozenset`` instance containing a subset of the groundset.
666
A ``ValueError`` is raised if the set contains no cocircuit.
667
668
EXAMPLES::
669
670
sage: M = matroids.named_matroids.Vamos()
671
sage: sorted(sage.matroids.matroid.Matroid._cocircuit(M,
672
....: set(['a', 'c', 'd', 'e', 'f'])))
673
['c', 'd', 'e', 'f']
674
sage: sorted(sage.matroids.matroid.Matroid._cocircuit(M,
675
....: set(['a', 'c', 'd'])))
676
Traceback (most recent call last):
677
...
678
ValueError: no cocircuit in coindependent set.
679
680
"""
681
Z = set(X)
682
if self._is_coindependent(X):
683
raise ValueError("no cocircuit in coindependent set.")
684
l = len(X) - 1
685
for x in X:
686
Z.discard(x)
687
if self._corank(Z) == l:
688
Z.add(x)
689
else:
690
l -= 1
691
return frozenset(Z)
692
693
cpdef _fundamental_cocircuit(self, B, e):
694
r"""
695
Return the `B`-fundamental circuit using `e`.
696
697
Internal version that does no input checking.
698
699
INPUT:
700
701
- ``B`` -- a basis of the matroid.
702
- ``e`` -- an element of ``B``.
703
704
OUTPUT:
705
706
A set of elements.
707
708
EXAMPLES::
709
710
sage: M = matroids.named_matroids.Vamos()
711
sage: sorted(M._fundamental_cocircuit('abch', 'c'))
712
['c', 'd', 'e', 'f']
713
"""
714
return self._cocircuit(self.groundset().difference(B).union([e]))
715
716
cpdef _coclosure(self, X):
717
"""
718
Return the coclosure of a set.
719
720
INPUT:
721
722
- ``X`` -- An object with Python's ``frozenset`` interface containing
723
a subset of ``self.groundset()``.
724
725
OUTPUT:
726
727
``frozenset`` instance containing a subset of the groundset.
728
729
EXAMPLES::
730
731
sage: M = matroids.named_matroids.Vamos()
732
sage: sorted(M._coclosure(set(['a', 'b', 'c'])))
733
['a', 'b', 'c', 'd']
734
735
"""
736
X = set(X)
737
Y = self.groundset().difference(X)
738
r = self._corank(X)
739
for y in Y:
740
X.add(y)
741
if self._corank(X) > r:
742
X.discard(y)
743
return frozenset(X)
744
745
cpdef _augment(self, X, Y):
746
r"""
747
Return a maximal subset `I` of `Y` such that `r(X + I)=r(X) + r(I)`.
748
749
This version of ``augment`` does no type checking. In particular,
750
``Y`` is assumed to be disjoint from ``X``.
751
752
INPUT:
753
754
- ``X`` -- An object with Python's ``frozenset`` interface containing
755
a subset of ``self.groundset()``.
756
- ``Y`` -- An object with Python's ``frozenset`` interface containing
757
a subset of ``self.groundset()``, and disjoint from ``X``.
758
759
OUTPUT:
760
761
``frozenset`` instance containing a subset of the groundset.
762
763
EXAMPLES::
764
765
sage: M = matroids.named_matroids.Vamos()
766
sage: sorted(M._augment(set(['a']), set(['e', 'f', 'g', 'h'])))
767
['e', 'g', 'h']
768
769
"""
770
X = set(X)
771
res = set([])
772
r = self._rank(X)
773
for e in Y:
774
X.add(e)
775
if self.rank(X) > r:
776
r += 1
777
res.add(e)
778
return frozenset(res)
779
780
# override the following methods for even better efficiency
781
782
cpdef _is_independent(self, X):
783
"""
784
Test if input is independent.
785
786
INPUT:
787
788
- ``X`` -- An object with Python's ``frozenset`` interface containing
789
a subset of ``self.groundset()``.
790
791
OUTPUT:
792
793
Boolean.
794
795
EXAMPLES::
796
797
sage: M = matroids.named_matroids.Vamos()
798
sage: M._is_independent(set(['a', 'b', 'c']))
799
True
800
sage: M._is_independent(set(['a', 'b', 'c', 'd']))
801
False
802
803
"""
804
return len(X) == self._rank(X)
805
806
cpdef _is_basis(self, X):
807
"""
808
Test if input is a basis.
809
810
INPUT:
811
812
- ``X`` -- An object with Python's ``frozenset`` interface containing
813
a subset of ``self.groundset()``.
814
815
.. WARNING::
816
817
This method assumes that ``X`` has the right size to be a basis,
818
i.e. ``len(X) == self.full_rank()``. Otherwise its behavior is
819
undefined.
820
821
OUTPUT:
822
823
Boolean.
824
825
EXAMPLES::
826
827
sage: M = matroids.named_matroids.Vamos()
828
sage: M._is_basis(set(['a', 'b', 'c', 'e']))
829
True
830
sage: M._is_basis(set(['a', 'b', 'c', 'd']))
831
False
832
833
If ``X`` does not have the right size, behavior can become
834
unpredictable::
835
836
sage: M._is_basis(set(['a', 'b', 'c']))
837
True
838
839
"""
840
return self._is_independent(X)
841
842
cpdef _is_circuit(self, X):
843
"""
844
Test if input is a circuit.
845
846
INPUT:
847
848
- ``X`` -- An object with Python's ``frozenset`` interface containing
849
a subset of ``self.groundset()``.
850
851
OUTPUT:
852
853
Boolean.
854
855
EXAMPLES::
856
857
sage: M = matroids.named_matroids.Vamos()
858
sage: M._is_circuit(set(['a', 'b', 'c', 'd']))
859
True
860
sage: M._is_circuit(set(['a', 'b', 'c', 'e']))
861
False
862
sage: M._is_circuit(set(['a', 'b', 'c', 'd', 'e']))
863
False
864
865
"""
866
l = len(X) - 1
867
if self._rank(X) != l:
868
return False
869
Z = set(X)
870
for x in X:
871
Z.discard(x)
872
if self._rank(frozenset(Z)) < l:
873
return False
874
Z.add(x)
875
return True
876
877
cpdef _is_closed(self, X):
878
"""
879
Test if input is a closed set.
880
881
INPUT:
882
883
- ``X`` -- An object with Python's ``frozenset`` interface containing
884
a subset of ``self.groundset()``.
885
886
OUTPUT:
887
888
Boolean.
889
890
EXAMPLES::
891
892
sage: M = matroids.named_matroids.Vamos()
893
sage: M._is_closed(set(['a', 'b', 'c', 'd']))
894
True
895
sage: M._is_closed(set(['a', 'b', 'c', 'e']))
896
False
897
898
"""
899
X = set(X)
900
Y = self.groundset().difference(X)
901
r = self._rank(X)
902
for y in Y:
903
X.add(y)
904
if self._rank(frozenset(X)) == r:
905
return False
906
X.discard(y)
907
return True
908
909
cpdef _is_coindependent(self, X):
910
"""
911
Test if input is coindependent.
912
913
INPUT:
914
915
- ``X`` -- An object with Python's ``frozenset`` interface containing
916
a subset of ``self.groundset()``.
917
918
OUTPUT:
919
920
Boolean.
921
922
EXAMPLES::
923
924
sage: M = matroids.named_matroids.Vamos()
925
sage: M._is_coindependent(set(['a', 'b', 'c']))
926
True
927
sage: M._is_coindependent(set(['a', 'b', 'c', 'd']))
928
False
929
930
"""
931
return self._corank(X) == len(X)
932
933
cpdef _is_cobasis(self, X):
934
"""
935
Test if input is a cobasis.
936
937
INPUT:
938
939
- ``X`` -- An object with Python's ``frozenset`` interface containing
940
a subset of ``self.groundset()``.
941
942
OUTPUT:
943
944
Boolean.
945
946
.. WARNING::
947
948
This method assumes ``X`` has the size of a cobasis, i.e.
949
``len(X) == self.full_corank()``.
950
951
EXAMPLES::
952
953
sage: M = matroids.named_matroids.Vamos()
954
sage: M._is_cobasis(set(['a', 'b', 'c', 'e']))
955
True
956
sage: M._is_cobasis(set(['a', 'b', 'c', 'd']))
957
False
958
sage: M._is_cobasis(set(['a', 'b', 'c']))
959
False
960
961
"""
962
return self._is_basis(self.groundset().difference(X))
963
964
cpdef _is_cocircuit(self, X):
965
"""
966
Test if input is a cocircuit.
967
968
INPUT:
969
970
- ``X`` -- An object with Python's ``frozenset`` interface containing
971
a subset of ``self.groundset()``.
972
973
OUTPUT:
974
975
Boolean.
976
977
EXAMPLES::
978
979
sage: M = matroids.named_matroids.Vamos()
980
sage: M._is_cocircuit(set(['a', 'b', 'c', 'd']))
981
True
982
sage: M._is_cocircuit(set(['a', 'b', 'c', 'e']))
983
False
984
sage: M._is_cocircuit(set(['a', 'b', 'c', 'd', 'e']))
985
False
986
987
"""
988
l = len(X) - 1
989
if self._corank(X) != l:
990
return False
991
Z = set(X)
992
for x in X:
993
Z.discard(x)
994
if self._corank(frozenset(Z)) < l:
995
return False
996
Z.add(x)
997
return True
998
999
cpdef _is_coclosed(self, X):
1000
"""
1001
Test if input is a coclosed set.
1002
1003
INPUT:
1004
1005
- ``X`` -- An object with Python's ``frozenset`` interface containing
1006
a subset of ``self.groundset()``.
1007
1008
OUTPUT:
1009
1010
Boolean.
1011
1012
EXAMPLES::
1013
1014
sage: M = matroids.named_matroids.Vamos()
1015
sage: M._is_coclosed(set(['a', 'b', 'c', 'd']))
1016
True
1017
sage: M._is_coclosed(set(['a', 'b', 'c', 'e']))
1018
False
1019
1020
"""
1021
X = set(X)
1022
Y = self.groundset().difference(X)
1023
r = self._corank(X)
1024
for y in Y:
1025
X.add(y)
1026
if self._corank(frozenset(X)) == r:
1027
return False
1028
X.discard(y)
1029
return True
1030
1031
cpdef _minor(self, contractions, deletions):
1032
r"""
1033
Return a minor.
1034
1035
INPUT:
1036
1037
- ``contractions`` -- An object with Python's ``frozenset`` interface
1038
containing a subset of ``self.groundset()``.
1039
- ``deletions`` -- An object with Python's ``frozenset`` interface
1040
containing a subset of ``self.groundset()``.
1041
1042
.. NOTE::
1043
1044
This method does NOT do any checks. Besides the assumptions above,
1045
we assume the following:
1046
1047
- ``contractions`` is independent
1048
- ``deletions`` is coindependent
1049
- ``contractions`` and ``deletions`` are disjoint.
1050
1051
OUTPUT:
1052
1053
A matroid.
1054
1055
EXAMPLES::
1056
1057
sage: M = matroids.named_matroids.Vamos()
1058
sage: N = M._minor(contractions=set(['a']), deletions=set([]))
1059
sage: N._minor(contractions=set([]), deletions=set(['b', 'c']))
1060
M / {'a'} \ {'b', 'c'}, where M is Vamos: Matroid of rank 4 on 8
1061
elements with circuit-closures
1062
{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},
1063
{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'}, {'c', 'd', 'e', 'f'}},
1064
4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}
1065
"""
1066
import minor_matroid
1067
return minor_matroid.MinorMatroid(self, contractions, deletions)
1068
1069
cpdef _has_minor(self, N):
1070
"""
1071
Test if matroid has the specified minor.
1072
1073
INPUT:
1074
1075
- ``N`` -- An instance of a ``Matroid`` object.
1076
1077
OUTPUT:
1078
1079
Boolean.
1080
1081
EXAMPLES::
1082
1083
sage: M = matroids.named_matroids.Vamos()
1084
sage: M._has_minor(matroids.Whirl(3))
1085
False
1086
sage: M._has_minor(matroids.Uniform(2, 4))
1087
True
1088
1089
.. TODO::
1090
1091
This important method can (and should) be optimized considerably.
1092
See [Hlineny]_ p.1219 for hints to that end.
1093
"""
1094
if self is N:
1095
return True
1096
rd = self.full_rank() - N.full_rank()
1097
cd = self.full_corank() - N.full_corank()
1098
if rd < 0 or cd < 0:
1099
return False
1100
YY = self.dual().independent_r_sets(cd)
1101
for X in self.independent_r_sets(rd):
1102
for Y in YY:
1103
if X.isdisjoint(Y):
1104
if N._is_isomorphic(self._minor(contractions=X, deletions=Y)):
1105
return True
1106
return False
1107
1108
cpdef _line_length(self, F):
1109
"""
1110
Compute the length of the line specified through flat ``F``.
1111
1112
This is the number of elements in `si(M / F)`.
1113
1114
INPUT:
1115
1116
- ``F`` -- a subset of the groundset, assumed to be a closed set of
1117
rank `r(M) - 2`.
1118
1119
OUTPUT:
1120
1121
Integer.
1122
1123
EXAMPLES::
1124
1125
sage: M = matroids.named_matroids.Pappus()
1126
sage: M._line_length(['d'])
1127
5
1128
sage: M = matroids.named_matroids.NonPappus()
1129
sage: M._line_length(['d'])
1130
6
1131
"""
1132
return len(self.minor(contractions=F).simplify())
1133
1134
cpdef _extension(self, element, hyperplanes):
1135
"""
1136
Extend the matroid by a new element.
1137
1138
The result is a matroid on ``self.groundset() + {element}``, where
1139
``element`` is contained in exactly the hyperplanes of ``self``
1140
specified by ``hyperplanes``.
1141
1142
INPUT:
1143
1144
- ``element`` -- a hashable object not in ``self.groundset()``.
1145
- ``hyperplanes`` -- the set of hyperplanes of a linear subclass of
1146
``self``.
1147
1148
OUTPUT:
1149
1150
A matroid.
1151
1152
EXAMPLES::
1153
1154
sage: M = matroids.Uniform(3, 6)
1155
sage: H = [frozenset([0, 1])]
1156
sage: N = M._extension(6, H)
1157
sage: N
1158
Matroid of rank 3 on 7 elements with 34 bases
1159
sage: [sorted(C) for C in N.circuits() if len(C) == 3]
1160
[[0, 1, 6]]
1161
"""
1162
import basis_matroid
1163
return basis_matroid.BasisMatroid(self)._extension(element, hyperplanes)
1164
1165
# ** user-facing methods **
1166
1167
# Display:
1168
# cpdef _latex_(self):
1169
# return "\\texttt{Matroid on groundset }" + latex(self.groundset())
1170
1171
def _repr_(self):
1172
"""
1173
Return a string representation of the matroid.
1174
1175
EXAMPLES::
1176
1177
sage: M = matroids.named_matroids.Vamos()
1178
sage: sage.matroids.matroid.Matroid._repr_(M)
1179
'Matroid of rank 4 on 8 elements'
1180
"""
1181
S = "Matroid of rank "
1182
S = S + str(self.rank()) + " on " + str(self.size()) + " elements"
1183
return S
1184
1185
# cpdef show(self):
1186
# Show either the graph, or the matrix with labels, or the lattice,
1187
# or (in rank 3) the geometric diagram.
1188
# raise NotImplementedError
1189
1190
def __len__(self):
1191
"""
1192
Return the size of the groundset.
1193
1194
EXAMPLES::
1195
1196
sage: M = matroids.named_matroids.Vamos()
1197
sage: len(M)
1198
8
1199
"""
1200
return self.size()
1201
1202
cpdef size(self):
1203
"""
1204
Return the size of the groundset.
1205
1206
OUTPUT:
1207
1208
Integer.
1209
1210
EXAMPLES::
1211
1212
sage: M = matroids.named_matroids.Vamos()
1213
sage: M.size()
1214
8
1215
"""
1216
if self._stored_size == 0:
1217
self._stored_size = len(self.groundset())
1218
return self._stored_size
1219
1220
# User-visible methods
1221
1222
cpdef rank(self, X=None):
1223
r"""
1224
Return the rank of ``X``.
1225
1226
The *rank* of a subset `X` is the size of the largest independent set
1227
contained in `X`.
1228
1229
If ``X`` is ``None``, the rank of the groundset is returned.
1230
1231
INPUT:
1232
1233
- ``X`` -- (default: ``None``) a subset of the groundset
1234
1235
OUTPUT:
1236
1237
Integer.
1238
1239
EXAMPLES::
1240
1241
sage: M = matroids.named_matroids.Fano()
1242
sage: M.rank()
1243
3
1244
sage: M.rank(['a', 'b', 'f'])
1245
2
1246
sage: M.rank(['a', 'b', 'x'])
1247
Traceback (most recent call last):
1248
...
1249
ValueError: input is not a subset of the groundset.
1250
1251
"""
1252
if X is None:
1253
return self.full_rank()
1254
else:
1255
if not self.groundset().issuperset(X):
1256
raise ValueError("input is not a subset of the groundset.")
1257
return self._rank(frozenset(X))
1258
1259
cpdef full_rank(self):
1260
r"""
1261
Return the rank of the matroid.
1262
1263
The *rank* of the matroid is the size of the largest independent
1264
subset of the groundset.
1265
1266
OUTPUT:
1267
1268
Integer.
1269
1270
EXAMPLES::
1271
1272
sage: M = matroids.named_matroids.Vamos()
1273
sage: M.full_rank()
1274
4
1275
sage: M.dual().full_rank()
1276
4
1277
"""
1278
if self._stored_full_rank == 0:
1279
self._stored_full_rank = self._rank(self.groundset())
1280
return self._stored_full_rank
1281
1282
cpdef basis(self):
1283
r"""
1284
Return an arbitrary basis of the matroid.
1285
1286
A *basis* is an inclusionwise maximal independent set.
1287
1288
.. NOTE::
1289
1290
The output of this method can change in between calls.
1291
1292
OUTPUT:
1293
1294
Set of elements.
1295
1296
EXAMPLES::
1297
1298
sage: M = matroids.named_matroids.Pappus()
1299
sage: B = M.basis()
1300
sage: M.is_basis(B)
1301
True
1302
sage: len(B)
1303
3
1304
sage: M.rank(B)
1305
3
1306
sage: M.full_rank()
1307
3
1308
"""
1309
return self._max_independent(self.groundset())
1310
1311
cpdef max_independent(self, X):
1312
"""
1313
Compute a maximal independent subset of ``X``.
1314
1315
INPUT:
1316
1317
- ``X`` -- A subset of ``self.groundset()``.
1318
1319
OUTPUT:
1320
1321
Subset of ``X``.
1322
1323
EXAMPLES::
1324
1325
sage: M = matroids.named_matroids.Vamos()
1326
sage: sorted(M.max_independent(['a', 'c', 'd', 'e', 'f']))
1327
['a', 'd', 'e', 'f']
1328
sage: M.max_independent(['x'])
1329
Traceback (most recent call last):
1330
...
1331
ValueError: input is not a subset of the groundset.
1332
"""
1333
if not self.groundset().issuperset(X):
1334
raise ValueError("input is not a subset of the groundset.")
1335
return self._max_independent(frozenset(X))
1336
1337
cpdef circuit(self, X=None):
1338
"""
1339
Return a circuit.
1340
1341
A *circuit* of a matroid is an inclusionwise minimal dependent subset.
1342
1343
INPUT:
1344
1345
- ``X`` -- A subset of ``self.groundset()``, or ``None``. In the
1346
latter case, a circuit contained in the full groundset is returned.
1347
1348
OUTPUT:
1349
1350
Set of elements.
1351
1352
- If ``X`` is not ``None``, the output is a circuit contained in ``X``
1353
if such a circuit exists. Otherwise an error is raised.
1354
- If ``X`` is ``None``, the output is a circuit contained in
1355
``self.groundset()`` if such a circuit exists. Otherwise an error is
1356
raised.
1357
1358
EXAMPLES::
1359
1360
sage: M = matroids.named_matroids.Vamos()
1361
sage: sorted(M.circuit(['a', 'c', 'd', 'e', 'f']))
1362
['c', 'd', 'e', 'f']
1363
sage: sorted(M.circuit(['a', 'c', 'd']))
1364
Traceback (most recent call last):
1365
...
1366
ValueError: no circuit in independent set.
1367
sage: M.circuit(['x'])
1368
Traceback (most recent call last):
1369
...
1370
ValueError: input X is not a subset of the groundset.
1371
sage: sorted(M.circuit())
1372
['a', 'b', 'c', 'd']
1373
"""
1374
if X is None:
1375
X = self.groundset()
1376
elif not self.groundset().issuperset(X):
1377
raise ValueError("input X is not a subset of the groundset.")
1378
return self._circuit(frozenset(X))
1379
1380
cpdef fundamental_circuit(self, B, e):
1381
r"""
1382
Return the `B`-fundamental circuit using `e`.
1383
1384
If `B` is a basis, and `e` an element not in `B`, then the
1385
`B`-*fundamental circuit* using `e` is the unique matroid circuit
1386
contained in `B\cup e`.
1387
1388
INPUT:
1389
1390
- ``B`` -- a basis of the matroid.
1391
- ``e`` -- an element not in ``B``.
1392
1393
OUTPUT:
1394
1395
A set of elements.
1396
1397
.. SEEALSO::
1398
1399
:meth:`M.circuit() <Matroid.circuit>`,
1400
:meth:`M.basis() <Matroid.basis>`
1401
1402
EXAMPLES::
1403
1404
sage: M = matroids.named_matroids.Vamos()
1405
sage: sorted(M.fundamental_circuit('defg', 'c'))
1406
['c', 'd', 'e', 'f']
1407
"""
1408
B = frozenset(B)
1409
if not self.is_basis(B):
1410
raise ValueError("input B is not a basis of the matroid.")
1411
if not e in self.groundset():
1412
raise ValueError("input e is not an element of the groundset.")
1413
return self._fundamental_circuit(B, e)
1414
1415
cpdef closure(self, X):
1416
"""
1417
Return the closure of a set.
1418
1419
A set is *closed* if adding any extra element to it will increase the
1420
rank of the set. The *closure* of a set is the smallest closed set
1421
containing it.
1422
1423
INPUT:
1424
1425
- ``X`` -- A subset of ``self.groundset()``.
1426
1427
OUTPUT:
1428
1429
Set of elements containing ``X``.
1430
1431
EXAMPLES::
1432
1433
sage: M = matroids.named_matroids.Vamos()
1434
sage: sorted(M.closure(set(['a', 'b', 'c'])))
1435
['a', 'b', 'c', 'd']
1436
sage: M.closure(['x'])
1437
Traceback (most recent call last):
1438
...
1439
ValueError: input X is not a subset of the groundset.
1440
1441
"""
1442
if not self.groundset().issuperset(X):
1443
raise ValueError("input X is not a subset of the groundset.")
1444
return self._closure(frozenset(X))
1445
1446
cpdef augment(self, X, Y=None):
1447
r"""
1448
Return a maximal subset `I` of `Y - X` such that
1449
`r(X + I) = r(X) + r(I)`.
1450
1451
INPUT:
1452
1453
- ``X`` -- A subset of ``self.groundset()``.
1454
- ``Y`` -- a subset of ``self.groundset()``. If ``Y`` is ``None``,
1455
the full groundset is used.
1456
1457
OUTPUT:
1458
1459
A subset of `Y - X`.
1460
1461
EXAMPLES::
1462
1463
sage: M = matroids.named_matroids.Vamos()
1464
sage: sorted(M.augment(['a'], ['e', 'f', 'g', 'h']))
1465
['e', 'g', 'h']
1466
sage: sorted(M.augment(['a']))
1467
['b', 'c', 'e']
1468
sage: sorted(M.augment(['x']))
1469
Traceback (most recent call last):
1470
...
1471
ValueError: input X is not a subset of the groundset.
1472
sage: sorted(M.augment(['a'], ['x']))
1473
Traceback (most recent call last):
1474
...
1475
ValueError: input Y is not a subset of the groundset.
1476
"""
1477
if not self.groundset().issuperset(X):
1478
raise ValueError("input X is not a subset of the groundset.")
1479
if Y is None:
1480
Y = self.groundset()
1481
else:
1482
if not self.groundset().issuperset(Y):
1483
raise ValueError("input Y is not a subset of the groundset.")
1484
1485
return self._augment(frozenset(X), frozenset(Y).difference(X))
1486
1487
cpdef corank(self, X=None):
1488
r"""
1489
Return the corank of ``X``, or the corank of the groundset if ``X`` is
1490
``None``.
1491
1492
The *corank* of a set `X` is the rank of `X` in the dual matroid.
1493
1494
If ``X`` is ``None``, the corank of the groundset is returned.
1495
1496
INPUT:
1497
1498
- ``X`` -- (default: ``None``) a subset of the groundset.
1499
1500
OUTPUT:
1501
1502
Integer.
1503
1504
.. SEEALSO::
1505
1506
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1507
:meth:`M.rank() <sage.matroids.matroid.Matroid.rank>`
1508
1509
EXAMPLES::
1510
1511
sage: M = matroids.named_matroids.Fano()
1512
sage: M.corank()
1513
4
1514
sage: M.corank('cdeg')
1515
3
1516
sage: M.rank(['a', 'b', 'x'])
1517
Traceback (most recent call last):
1518
...
1519
ValueError: input is not a subset of the groundset.
1520
"""
1521
if X is None:
1522
X = self.groundset()
1523
else:
1524
if not self.groundset().issuperset(X):
1525
raise ValueError("input is not a subset of the groundset.")
1526
return self._corank(frozenset(X))
1527
1528
cpdef full_corank(self):
1529
"""
1530
Return the corank of the matroid.
1531
1532
The *corank* of the matroid equals the rank of the dual matroid. It is
1533
given by ``M.size() - M.full_rank()``.
1534
1535
OUTPUT:
1536
1537
Integer.
1538
1539
.. SEEALSO::
1540
1541
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1542
:meth:`M.full_rank() <sage.matroids.matroid.Matroid.full_rank>`
1543
1544
EXAMPLES::
1545
1546
sage: M = matroids.named_matroids.Vamos()
1547
sage: M.full_corank()
1548
4
1549
"""
1550
return self.size() - self.full_rank()
1551
1552
cpdef cobasis(self):
1553
"""
1554
Return an arbitrary cobasis of the matroid.
1555
1556
A *cobasis* is the complement of a basis. A cobasis is
1557
a basis of the dual matroid.
1558
1559
.. NOTE::
1560
1561
Output can change between calls.
1562
1563
OUTPUT:
1564
1565
A set of elements.
1566
1567
.. SEEALSO::
1568
1569
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1570
:meth:`M.full_rank() <sage.matroids.matroid.Matroid.full_rank>`
1571
1572
EXAMPLES::
1573
1574
sage: M = matroids.named_matroids.Pappus()
1575
sage: B = M.cobasis()
1576
sage: M.is_cobasis(B)
1577
True
1578
sage: len(B)
1579
6
1580
sage: M.corank(B)
1581
6
1582
sage: M.full_corank()
1583
6
1584
1585
"""
1586
return self.max_coindependent(self.groundset())
1587
1588
cpdef max_coindependent(self, X):
1589
"""
1590
Compute a maximal coindependent subset.
1591
1592
A set is *coindependent* if it is independent in the dual matroid.
1593
A set is coindependent if and only if the complement is *spanning*
1594
(i.e. contains a basis of the matroid).
1595
1596
INPUT:
1597
1598
- ``X`` -- A subset of ``self.groundset()``.
1599
1600
OUTPUT:
1601
1602
A subset of ``X``.
1603
1604
.. SEEALSO::
1605
1606
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1607
:meth:`M.max_independent() <sage.matroids.matroid.Matroid.max_independent>`
1608
1609
EXAMPLES::
1610
1611
sage: M = matroids.named_matroids.Vamos()
1612
sage: sorted(M.max_coindependent(['a', 'c', 'd', 'e', 'f']))
1613
['a', 'c', 'd', 'e']
1614
sage: M.max_coindependent(['x'])
1615
Traceback (most recent call last):
1616
...
1617
ValueError: input is not a subset of the groundset.
1618
"""
1619
if not self.groundset().issuperset(X):
1620
raise ValueError("input is not a subset of the groundset.")
1621
return self._max_coindependent(frozenset(X))
1622
1623
cpdef coclosure(self, X):
1624
"""
1625
Return the coclosure of a set.
1626
1627
A set is *coclosed* if it is closed in the dual matroid. The
1628
*coclosure* of `X` is the smallest coclosed set containing `X`.
1629
1630
INPUT:
1631
1632
- ``X`` -- A subset of ``self.groundset()``.
1633
1634
OUTPUT:
1635
1636
A set of elements containing ``X``.
1637
1638
.. SEEALSO::
1639
1640
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1641
:meth:`M.closure() <sage.matroids.matroid.Matroid.closure>`
1642
1643
EXAMPLES::
1644
1645
sage: M = matroids.named_matroids.Vamos()
1646
sage: sorted(M.coclosure(set(['a', 'b', 'c'])))
1647
['a', 'b', 'c', 'd']
1648
sage: M.coclosure(['x'])
1649
Traceback (most recent call last):
1650
...
1651
ValueError: input X is not a subset of the groundset.
1652
"""
1653
if not self.groundset().issuperset(X):
1654
raise ValueError("input X is not a subset of the groundset.")
1655
return self._coclosure(frozenset(X))
1656
1657
cpdef cocircuit(self, X=None):
1658
"""
1659
Return a cocircuit.
1660
1661
A *cocircuit* is an inclusionwise minimal subset that is dependent in
1662
the dual matroid.
1663
1664
INPUT:
1665
1666
- ``X`` -- (default: ``None``) a subset of ``self.groundset()``.
1667
1668
OUTPUT:
1669
1670
A set of elements.
1671
1672
- If ``X`` is not ``None``, the output is a cocircuit contained in
1673
``X`` if such a cocircuit exists. Otherwise an error is raised.
1674
- If ``X`` is ``None``, the output is a cocircuit contained in
1675
``self.groundset()`` if such a cocircuit exists. Otherwise an error
1676
is raised.
1677
1678
.. SEEALSO::
1679
1680
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1681
:meth:`M.circuit() <sage.matroids.matroid.Matroid.circuit>`
1682
1683
EXAMPLES::
1684
1685
sage: M = matroids.named_matroids.Vamos()
1686
sage: sorted(M.cocircuit(['a', 'c', 'd', 'e', 'f']))
1687
['c', 'd', 'e', 'f']
1688
sage: sorted(M.cocircuit(['a', 'c', 'd']))
1689
Traceback (most recent call last):
1690
...
1691
ValueError: no cocircuit in coindependent set.
1692
sage: M.cocircuit(['x'])
1693
Traceback (most recent call last):
1694
...
1695
ValueError: input X is not a subset of the groundset.
1696
sage: sorted(M.cocircuit())
1697
['e', 'f', 'g', 'h']
1698
"""
1699
if X is None:
1700
X = self.groundset()
1701
elif not self.groundset().issuperset(X):
1702
raise ValueError("input X is not a subset of the groundset.")
1703
return self._cocircuit(frozenset(X))
1704
1705
cpdef fundamental_cocircuit(self, B, e):
1706
r"""
1707
Return the `B`-fundamental cocircuit using `e`.
1708
1709
If `B` is a basis, and `e` an element of `B`, then the
1710
`B`-*fundamental cocircuit* using `e` is the unique matroid cocircuit
1711
that intersects `B` only in `e`.
1712
1713
This is equal to
1714
``M.dual().fundamental_circuit(M.groundset().difference(B), e)``.
1715
1716
INPUT:
1717
1718
- ``B`` -- a basis of the matroid.
1719
- ``e`` -- an element of ``B``.
1720
1721
OUTPUT:
1722
1723
A set of elements.
1724
1725
.. SEEALSO::
1726
1727
:meth:`M.cocircuit() <Matroid.cocircuit>`,
1728
:meth:`M.basis() <Matroid.basis>`,
1729
:meth:`M.fundamental_circuit() <Matroid.fundamental_circuit>`
1730
1731
EXAMPLES::
1732
1733
sage: M = matroids.named_matroids.Vamos()
1734
sage: sorted(M.fundamental_cocircuit('abch', 'c'))
1735
['c', 'd', 'e', 'f']
1736
"""
1737
B = frozenset(B)
1738
if not self.is_basis(B):
1739
raise ValueError("input B is not a basis of the matroid.")
1740
if not e in B:
1741
raise ValueError("input e is not an element of B.")
1742
return self._fundamental_cocircuit(B, e)
1743
1744
cpdef loops(self):
1745
r"""
1746
Return the set of loops of the matroid.
1747
1748
A *loop* is a one-element dependent set.
1749
1750
OUTPUT:
1751
1752
A set of elements.
1753
1754
EXAMPLES::
1755
1756
sage: M = matroids.named_matroids.Fano()
1757
sage: M.loops()
1758
frozenset([])
1759
sage: (M / ['a', 'b']).loops()
1760
frozenset(['f'])
1761
"""
1762
return self._closure(set())
1763
1764
cpdef is_independent(self, X):
1765
r"""
1766
Check if a subset is independent in the matroid.
1767
1768
INPUT:
1769
1770
- ``X`` -- A subset of ``self.groundset()``.
1771
1772
OUTPUT:
1773
1774
Boolean.
1775
1776
EXAMPLES::
1777
1778
sage: M = matroids.named_matroids.Vamos()
1779
sage: M.is_independent('abc')
1780
True
1781
sage: M.is_independent('abcd')
1782
False
1783
sage: M.is_independent('abcx')
1784
Traceback (most recent call last):
1785
...
1786
ValueError: input X is not a subset of the groundset.
1787
"""
1788
if not self.groundset().issuperset(X):
1789
raise ValueError("input X is not a subset of the groundset.")
1790
return self._is_independent(frozenset(X))
1791
1792
cpdef is_dependent(self, X):
1793
r"""
1794
Check if a subset is dependent in the matroid.
1795
1796
INPUT:
1797
1798
- ``X`` -- A subset of ``self.groundset()``.
1799
1800
OUTPUT:
1801
1802
Boolean.
1803
1804
EXAMPLES::
1805
1806
sage: M = matroids.named_matroids.Vamos()
1807
sage: M.is_dependent('abc')
1808
False
1809
sage: M.is_dependent('abcd')
1810
True
1811
sage: M.is_dependent('abcx')
1812
Traceback (most recent call last):
1813
...
1814
ValueError: input X is not a subset of the groundset.
1815
"""
1816
if not self.groundset().issuperset(X):
1817
raise ValueError("input X is not a subset of the groundset.")
1818
return not self._is_independent(frozenset(X))
1819
1820
cpdef is_basis(self, X):
1821
r"""
1822
Check if a subset is a basis of the matroid.
1823
1824
INPUT:
1825
1826
- ``X`` -- A subset of ``self.groundset()``.
1827
1828
OUTPUT:
1829
1830
Boolean.
1831
1832
EXAMPLES::
1833
1834
sage: M = matroids.named_matroids.Vamos()
1835
sage: M.is_basis('abc')
1836
False
1837
sage: M.is_basis('abce')
1838
True
1839
sage: M.is_basis('abcx')
1840
Traceback (most recent call last):
1841
...
1842
ValueError: input X is not a subset of the groundset.
1843
"""
1844
if not self.groundset().issuperset(X):
1845
raise ValueError("input X is not a subset of the groundset.")
1846
if len(X) != self.full_rank():
1847
return False
1848
return self._is_basis(frozenset(X))
1849
1850
cpdef is_closed(self, X):
1851
r"""
1852
Test if a subset is a closed set of the matroid.
1853
1854
A set is *closed* if adding any element to it will increase the rank
1855
of the set.
1856
1857
INPUT:
1858
1859
- ``X`` -- A subset of ``self.groundset()``.
1860
1861
OUTPUT:
1862
1863
Boolean.
1864
1865
.. SEEALSO::
1866
1867
:meth:`M.closure() <sage.matroids.matroid.Matroid.closure>`
1868
1869
EXAMPLES::
1870
1871
sage: M = matroids.named_matroids.Vamos()
1872
sage: M.is_closed('abc')
1873
False
1874
sage: M.is_closed('abcd')
1875
True
1876
sage: M.is_closed('abcx')
1877
Traceback (most recent call last):
1878
...
1879
ValueError: input X is not a subset of the groundset.
1880
"""
1881
if not self.groundset().issuperset(X):
1882
raise ValueError("input X is not a subset of the groundset.")
1883
return self._is_closed(frozenset(X))
1884
1885
cpdef is_circuit(self, X):
1886
r"""
1887
Test if a subset is a circuit of the matroid.
1888
1889
A *circuit* is an inclusionwise minimal dependent subset.
1890
1891
INPUT:
1892
1893
- ``X`` -- A subset of ``self.groundset()``.
1894
1895
OUTPUT:
1896
1897
Boolean.
1898
1899
EXAMPLES::
1900
1901
sage: M = matroids.named_matroids.Vamos()
1902
sage: M.is_circuit('abc')
1903
False
1904
sage: M.is_circuit('abcd')
1905
True
1906
sage: M.is_circuit('abcx')
1907
Traceback (most recent call last):
1908
...
1909
ValueError: input X is not a subset of the groundset.
1910
"""
1911
if not self.groundset().issuperset(X):
1912
raise ValueError("input X is not a subset of the groundset.")
1913
return self._is_circuit(frozenset(X))
1914
1915
cpdef coloops(self):
1916
r"""
1917
Return the set of coloops of the matroid.
1918
1919
A *coloop* is a one-element cocircuit. It is a loop of the dual of the
1920
matroid.
1921
1922
OUTPUT:
1923
1924
A set of elements.
1925
1926
.. SEEALSO::
1927
1928
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1929
:meth:`M.loops() <sage.matroids.matroid.Matroid.loops>`
1930
1931
EXAMPLES::
1932
1933
sage: M = matroids.named_matroids.Fano().dual()
1934
sage: M.coloops()
1935
frozenset([])
1936
sage: (M \ ['a', 'b']).coloops()
1937
frozenset(['f'])
1938
"""
1939
return self._coclosure(set())
1940
1941
cpdef is_coindependent(self, X):
1942
r"""
1943
Check if a subset is coindependent in the matroid.
1944
1945
A set is *coindependent* if it is independent in the dual matroid.
1946
1947
INPUT:
1948
1949
- ``X`` -- A subset of ``self.groundset()``.
1950
1951
OUTPUT:
1952
1953
Boolean.
1954
1955
.. SEEALSO::
1956
1957
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1958
:meth:`M.is_independent() <sage.matroids.matroid.Matroid.is_independent>`
1959
1960
EXAMPLES::
1961
1962
sage: M = matroids.named_matroids.Vamos()
1963
sage: M.is_coindependent('abc')
1964
True
1965
sage: M.is_coindependent('abcd')
1966
False
1967
sage: M.is_coindependent('abcx')
1968
Traceback (most recent call last):
1969
...
1970
ValueError: input X is not a subset of the groundset.
1971
"""
1972
if not self.groundset().issuperset(X):
1973
raise ValueError("input X is not a subset of the groundset.")
1974
return self._is_coindependent(frozenset(X))
1975
1976
cpdef is_codependent(self, X):
1977
r"""
1978
Check if a subset is codependent in the matroid.
1979
1980
A set is *codependent* if it is dependent in the dual of the matroid.
1981
1982
INPUT:
1983
1984
- ``X`` -- A subset of ``self.groundset()``.
1985
1986
OUTPUT:
1987
1988
Boolean.
1989
1990
.. SEEALSO::
1991
1992
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
1993
:meth:`M.is_dependent() <sage.matroids.matroid.Matroid.is_dependent>`
1994
1995
EXAMPLES::
1996
1997
sage: M = matroids.named_matroids.Vamos()
1998
sage: M.is_codependent('abc')
1999
False
2000
sage: M.is_codependent('abcd')
2001
True
2002
sage: M.is_codependent('abcx')
2003
Traceback (most recent call last):
2004
...
2005
ValueError: input X is not a subset of the groundset.
2006
"""
2007
if not self.groundset().issuperset(X):
2008
raise ValueError("input X is not a subset of the groundset.")
2009
return not self._is_coindependent(X)
2010
2011
cpdef is_cobasis(self, X):
2012
r"""
2013
Check if a subset is a cobasis of the matroid.
2014
2015
A *cobasis* is the complement of a basis. It is a basis of the dual
2016
matroid.
2017
2018
INPUT:
2019
2020
- ``X`` -- A subset of ``self.groundset()``.
2021
2022
OUTPUT:
2023
2024
Boolean.
2025
2026
.. SEEALSO::
2027
2028
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
2029
:meth:`M.is_basis() <sage.matroids.matroid.Matroid.is_basis>`
2030
2031
EXAMPLES::
2032
2033
sage: M = matroids.named_matroids.Vamos()
2034
sage: M.is_cobasis('abc')
2035
False
2036
sage: M.is_cobasis('abce')
2037
True
2038
sage: M.is_cobasis('abcx')
2039
Traceback (most recent call last):
2040
...
2041
ValueError: input X is not a subset of the groundset.
2042
"""
2043
if not self.groundset().issuperset(X):
2044
raise ValueError("input X is not a subset of the groundset.")
2045
if len(X) != self.full_corank():
2046
return False
2047
return self._is_cobasis(frozenset(X))
2048
2049
cpdef is_cocircuit(self, X):
2050
r"""
2051
Test if a subset is a cocircuit of the matroid.
2052
2053
A *cocircuit* is an inclusionwise minimal subset that is dependent in
2054
the dual matroid.
2055
2056
INPUT:
2057
2058
- ``X`` -- A subset of ``self.groundset()``.
2059
2060
OUTPUT:
2061
2062
Boolean.
2063
2064
.. SEEALSO::
2065
2066
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
2067
:meth:`M.is_circuit() <sage.matroids.matroid.Matroid.is_circuit>`
2068
2069
EXAMPLES::
2070
2071
sage: M = matroids.named_matroids.Vamos()
2072
sage: M.is_cocircuit('abc')
2073
False
2074
sage: M.is_cocircuit('abcd')
2075
True
2076
sage: M.is_cocircuit('abcx')
2077
Traceback (most recent call last):
2078
...
2079
ValueError: input X is not a subset of the groundset.
2080
"""
2081
if not self.groundset().issuperset(X):
2082
raise ValueError("input X is not a subset of the groundset.")
2083
return self._is_cocircuit(frozenset(X))
2084
2085
cpdef is_coclosed(self, X):
2086
r"""
2087
Test if a subset is a coclosed set of the matroid.
2088
2089
A set is *coclosed* if it is a closed set of the dual matroid.
2090
2091
INPUT:
2092
2093
- ``X`` -- A subset of ``self.groundset()``.
2094
2095
OUTPUT:
2096
2097
Boolean.
2098
2099
.. SEEALSO::
2100
2101
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
2102
:meth:`M.is_closed() <sage.matroids.matroid.Matroid.is_closed>`
2103
2104
EXAMPLES::
2105
2106
sage: M = matroids.named_matroids.Vamos()
2107
sage: M.is_coclosed('abc')
2108
False
2109
sage: M.is_coclosed('abcd')
2110
True
2111
sage: M.is_coclosed('abcx')
2112
Traceback (most recent call last):
2113
...
2114
ValueError: input X is not a subset of the groundset.
2115
"""
2116
if not self.groundset().issuperset(X):
2117
raise ValueError("input X is not a subset of the groundset.")
2118
return self._is_coclosed(frozenset(X))
2119
2120
# verification
2121
2122
cpdef is_valid(self):
2123
r"""
2124
Test if the data obey the matroid axioms.
2125
2126
The default implementation checks the (disproportionately slow) rank
2127
axioms. If `r` is the rank function of a matroid, we check, for all
2128
pairs `X, Y` of subsets,
2129
2130
* `0 \leq r(X) \leq |X|`
2131
* If `X \subseteq Y` then `r(X) \leq r(Y)`
2132
* `r(X\cup Y) + r(X\cap Y) \leq r(X) + r(Y)`
2133
2134
Certain subclasses may check other axioms instead.
2135
2136
OUTPUT:
2137
2138
Boolean.
2139
2140
EXAMPLES::
2141
2142
sage: M = matroids.named_matroids.Vamos()
2143
sage: M.is_valid()
2144
True
2145
2146
The following is the 'Escher matroid' by Brylawski and Kelly. See
2147
Example 1.5.5 in [Oxley]_ ::
2148
2149
sage: M = Matroid(circuit_closures={2: [[1, 2, 3], [1, 4, 5]],
2150
....: 3: [[1, 2, 3, 4, 5], [1, 2, 3, 6, 7], [1, 4, 5, 6, 7]]})
2151
sage: M.is_valid()
2152
False
2153
"""
2154
E = list(self.groundset())
2155
for i in xrange(0, len(E) + 1):
2156
for X in combinations(E, i):
2157
XX = frozenset(X)
2158
rX = self._rank(XX)
2159
if rX > i:
2160
return False
2161
for j in xrange(i, len(E) + 1):
2162
for Y in combinations(E, j):
2163
YY = frozenset(Y)
2164
rY = self._rank(YY)
2165
if XX.issubset(YY) and rX > rY:
2166
return False
2167
if (self._rank(XX.union(YY)) +
2168
self._rank(XX.intersection(YY)) > rX + rY):
2169
return False
2170
return True
2171
2172
# enumeration
2173
2174
cpdef circuits(self):
2175
"""
2176
Return the list of circuits of the matroid.
2177
2178
OUTPUT:
2179
2180
An iterable containing all circuits.
2181
2182
.. SEEALSO::
2183
2184
:meth:`M.circuit() <sage.matroids.matroid.Matroid.circuit>`
2185
2186
EXAMPLES::
2187
2188
sage: M = matroids.named_matroids.Fano()
2189
sage: sorted([sorted(C) for C in M.circuits()])
2190
[['a', 'b', 'c', 'g'], ['a', 'b', 'd', 'e'], ['a', 'b', 'f'],
2191
['a', 'c', 'd', 'f'], ['a', 'c', 'e'], ['a', 'd', 'g'],
2192
['a', 'e', 'f', 'g'], ['b', 'c', 'd'], ['b', 'c', 'e', 'f'],
2193
['b', 'd', 'f', 'g'], ['b', 'e', 'g'], ['c', 'd', 'e', 'g'],
2194
['c', 'f', 'g'], ['d', 'e', 'f']]
2195
"""
2196
C = set()
2197
for B in self.bases():
2198
C.update([self._circuit(B.union(set([e])))
2199
for e in self.groundset().difference(B)])
2200
return list(C)
2201
2202
cpdef nonspanning_circuits(self):
2203
"""
2204
Return the list of nonspanning circuits of the matroid.
2205
2206
A *nonspanning circuit* is a circuit whose rank is strictly smaller
2207
than the rank of the matroid.
2208
2209
OUTPUT:
2210
2211
An iterable containing all nonspanning circuits.
2212
2213
.. SEEALSO::
2214
2215
:meth:`M.circuit() <sage.matroids.matroid.Matroid.circuit>`,
2216
:meth:`M.rank() <sage.matroids.matroid.Matroid.rank>`
2217
2218
EXAMPLES::
2219
2220
sage: M = matroids.named_matroids.Fano()
2221
sage: sorted([sorted(C) for C in M.nonspanning_circuits()])
2222
[['a', 'b', 'f'], ['a', 'c', 'e'], ['a', 'd', 'g'],
2223
['b', 'c', 'd'], ['b', 'e', 'g'], ['c', 'f', 'g'],
2224
['d', 'e', 'f']]
2225
"""
2226
C = set()
2227
for N in self.nonbases():
2228
if self._rank(N) == self.full_rank() - 1:
2229
C.add(self._circuit(N))
2230
return list(C)
2231
2232
cpdef cocircuits(self):
2233
"""
2234
Return the list of cocircuits of the matroid.
2235
2236
OUTPUT:
2237
2238
An iterable containing all cocircuits.
2239
2240
.. SEEALSO::
2241
2242
:meth:`M.cocircuit() <sage.matroids.matroid.Matroid.cocircuit>`
2243
2244
EXAMPLES::
2245
2246
sage: M = matroids.named_matroids.Fano()
2247
sage: sorted([sorted(C) for C in M.cocircuits()])
2248
[['a', 'b', 'c', 'g'], ['a', 'b', 'd', 'e'], ['a', 'c', 'd', 'f'],
2249
['a', 'e', 'f', 'g'], ['b', 'c', 'e', 'f'], ['b', 'd', 'f', 'g'],
2250
['c', 'd', 'e', 'g']]
2251
"""
2252
C = set()
2253
for B in self.bases():
2254
C.update([self._cocircuit(self.groundset().difference(B).union(set([e]))) for e in B])
2255
return list(C)
2256
2257
cpdef noncospanning_cocircuits(self):
2258
"""
2259
Return the list of noncospanning cocircuits of the matroid.
2260
2261
A *noncospanning cocircuit* is a cocircuit whose corank is strictly
2262
smaller than the corank of the matroid.
2263
2264
OUTPUT:
2265
2266
An iterable containing all nonspanning circuits.
2267
2268
.. SEEALSO::
2269
2270
:meth:`M.cocircuit() <sage.matroids.matroid.Matroid.cocircuit>`,
2271
:meth:`M.corank() <sage.matroids.matroid.Matroid.corank>`
2272
2273
EXAMPLES::
2274
2275
sage: M = matroids.named_matroids.Fano().dual()
2276
sage: sorted([sorted(C) for C in M.noncospanning_cocircuits()])
2277
[['a', 'b', 'f'], ['a', 'c', 'e'], ['a', 'd', 'g'],
2278
['b', 'c', 'd'], ['b', 'e', 'g'], ['c', 'f', 'g'],
2279
['d', 'e', 'f']]
2280
"""
2281
return self.dual().nonspanning_circuits()
2282
2283
cpdef circuit_closures(self):
2284
"""
2285
Return the list of closures of circuits of the matroid.
2286
2287
A *circuit closure* is a closed set containing a circuit.
2288
2289
OUTPUT:
2290
2291
A dictionary containing the circuit closures of the matroid, indexed
2292
by their ranks.
2293
2294
.. SEEALSO::
2295
2296
:meth:`M.circuit() <sage.matroids.matroid.Matroid.circuit>`,
2297
:meth:`M.closure() <sage.matroids.matroid.Matroid.closure>`
2298
2299
EXAMPLES::
2300
2301
sage: M = matroids.named_matroids.Fano()
2302
sage: CC = M.circuit_closures()
2303
sage: len(CC[2])
2304
7
2305
sage: len(CC[3])
2306
1
2307
sage: len(CC[1])
2308
Traceback (most recent call last):
2309
...
2310
KeyError: 1
2311
sage: [sorted(X) for X in CC[3]]
2312
[['a', 'b', 'c', 'd', 'e', 'f', 'g']]
2313
"""
2314
CC = [set([]) for r in xrange(self.rank() + 1)]
2315
for C in self.circuits():
2316
CC[len(C) - 1].add(self.closure(C))
2317
CC = dict([(r, CC[r]) for r in xrange(self.rank() + 1)
2318
if len(CC[r]) > 0])
2319
return CC
2320
2321
cpdef nonspanning_circuit_closures(self):
2322
"""
2323
Return the list of closures of nonspanning circuits of the matroid.
2324
2325
A *nonspanning circuit closure* is a closed set containing a
2326
nonspanning circuit.
2327
2328
OUTPUT:
2329
2330
A dictionary containing the nonspanning circuit closures of the
2331
matroid, indexed by their ranks.
2332
2333
.. SEEALSO::
2334
2335
:meth:`M.nonspanning_circuits() <sage.matroids.matroid.Matroid.nonspanning_circuits>`,
2336
:meth:`M.closure() <sage.matroids.matroid.Matroid.closure>`
2337
2338
EXAMPLES::
2339
2340
sage: M = matroids.named_matroids.Fano()
2341
sage: CC = M.nonspanning_circuit_closures()
2342
sage: len(CC[2])
2343
7
2344
sage: len(CC[3])
2345
Traceback (most recent call last):
2346
...
2347
KeyError: 3
2348
"""
2349
CC = [set([]) for r in xrange(self.rank() + 1)]
2350
for C in self.nonspanning_circuits():
2351
CC[len(C) - 1].add(self.closure(C))
2352
CC = dict([(r, CC[r]) for r in xrange(self.rank() + 1)
2353
if len(CC[r]) > 0])
2354
return CC
2355
2356
cpdef nonbases(self):
2357
r"""
2358
Return the list of nonbases of the matroid.
2359
2360
A *nonbasis* is a set with cardinality ``self.full_rank()`` that is
2361
not a basis.
2362
2363
OUTPUT:
2364
2365
An iterable containing the nonbases of the matroid.
2366
2367
.. SEEALSO::
2368
2369
:meth:`M.basis() <sage.matroids.matroid.Matroid.basis>`
2370
2371
EXAMPLES::
2372
2373
sage: M = matroids.Uniform(2, 4)
2374
sage: list(M.nonbases())
2375
[]
2376
sage: [sorted(X) for X in matroids.named_matroids.P6().nonbases()]
2377
[['a', 'b', 'c']]
2378
2379
ALGORITHM:
2380
2381
Test all subsets of the groundset of cardinality ``self.full_rank()``
2382
"""
2383
cdef SetSystem res
2384
res = SetSystem(list(self.groundset()))
2385
for X in combinations(self.groundset(), self.full_rank()):
2386
if self._rank(X) < len(X):
2387
res.append(X)
2388
return res
2389
2390
cpdef dependent_r_sets(self, long r):
2391
r"""
2392
Return the list of dependent subsets of fixed size.
2393
2394
INPUT:
2395
2396
- ``r`` -- a nonnegative integer.
2397
2398
OUTPUT:
2399
2400
An iterable containing all dependent subsets of size ``r``.
2401
2402
EXAMPLES::
2403
2404
sage: M = matroids.named_matroids.Vamos()
2405
sage: M.dependent_r_sets(3)
2406
[]
2407
sage: sorted([sorted(X) for X in
2408
....: matroids.named_matroids.Vamos().dependent_r_sets(4)])
2409
[['a', 'b', 'c', 'd'], ['a', 'b', 'e', 'f'], ['a', 'b', 'g', 'h'],
2410
['c', 'd', 'e', 'f'], ['e', 'f', 'g', 'h']]
2411
2412
ALGORITHM:
2413
2414
Test all subsets of the groundset of cardinality ``r``
2415
"""
2416
res = []
2417
for X in combinations(self.groundset(), r):
2418
X = frozenset(X)
2419
if self._rank(X) < len(X):
2420
res.append(X)
2421
return res
2422
2423
cpdef bases(self):
2424
r"""
2425
Return the list of bases of the matroid.
2426
2427
A *basis* is a maximal independent set.
2428
2429
OUTPUT:
2430
2431
An iterable containing all bases of the matroid.
2432
2433
EXAMPLES::
2434
2435
sage: M = matroids.Uniform(2, 4)
2436
sage: sorted([sorted(X) for X in M.bases()])
2437
[[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
2438
2439
ALGORITHM:
2440
2441
Test all subsets of the groundset of cardinality ``self.full_rank()``
2442
"""
2443
cdef SetSystem res
2444
res = SetSystem(list(self.groundset()))
2445
for X in combinations(self.groundset(), self.full_rank()):
2446
if self._rank(X) == len(X):
2447
res.append(X)
2448
return res
2449
2450
cpdef independent_r_sets(self, long r):
2451
r"""
2452
Return the list of size-``r`` independent subsets of the matroid.
2453
2454
INPUT:
2455
2456
- ``r`` -- a nonnegative integer.
2457
2458
OUTPUT:
2459
2460
An iterable containing all independent subsets of the matroid of
2461
cardinality ``r``.
2462
2463
EXAMPLES::
2464
2465
sage: M = matroids.named_matroids.Pappus()
2466
sage: M.independent_r_sets(4)
2467
[]
2468
sage: sorted(sorted(M.independent_r_sets(3))[0])
2469
['a', 'c', 'e']
2470
2471
ALGORITHM:
2472
2473
Test all subsets of the groundset of cardinality ``r``
2474
"""
2475
res = []
2476
for X in combinations(self.groundset(), r):
2477
X = frozenset(X)
2478
if self._rank(X) == len(X):
2479
res.append(X)
2480
return res
2481
2482
cpdef _extend_flags(self, flags):
2483
r"""
2484
Recursion for the ``self._flags(r)`` method.
2485
2486
EXAMPLES::
2487
2488
sage: M = matroids.named_matroids.Fano()
2489
sage: F = M._flags(1)
2490
sage: sorted(M._extend_flags(F)) == sorted(M._flags(2))
2491
True
2492
"""
2493
newflags = []
2494
E = set(self.groundset())
2495
for [F, B, X] in flags:
2496
while len(X) > 0:
2497
x = min(X) # TODO: Alert: this sort of thing will break in
2498
# Python 3, I think. --SvZ
2499
newbase = B | set([x])
2500
newflat = self._closure(newbase)
2501
newX = X - newflat
2502
if max(newbase) == x:
2503
if min(newflat - F) == x:
2504
newflags.append([newflat, newbase, newX])
2505
X = newX
2506
return newflags
2507
2508
cpdef _flags(self, r):
2509
r"""
2510
Compute rank-``r`` flats, with extra information for more speed.
2511
2512
Used in the :meth:`flats() <sage.matroids.matroid.Matroid.flats>`
2513
method.
2514
2515
INPUT:
2516
2517
- ``r`` -- A natural number.
2518
2519
OUTPUT:
2520
2521
A list of triples `(F, B, X)` with `F` a flat of rank ``r``,
2522
`B` a lexicographically least basis of `F`, and `X` the remaining
2523
elements of the groundset that can potentially be lex-least extensions
2524
of the flat.
2525
2526
EXAMPLES::
2527
2528
sage: M = matroids.named_matroids.Fano()
2529
sage: sorted([M._flags(1)])
2530
[[[frozenset(['a']), set(['a']),
2531
frozenset(['c', 'b', 'e', 'd', 'g', 'f'])], [frozenset(['b']),
2532
set(['b']), frozenset(['c', 'e', 'd', 'g', 'f'])],
2533
[frozenset(['c']), set(['c']), frozenset(['e', 'd', 'g', 'f'])],
2534
[frozenset(['d']), set(['d']), frozenset(['e', 'g', 'f'])],
2535
[frozenset(['e']), set(['e']), frozenset(['g', 'f'])],
2536
[frozenset(['f']), set(['f']), frozenset(['g'])],
2537
[frozenset(['g']), set(['g']), frozenset([])]]]
2538
"""
2539
if r < 0:
2540
return []
2541
loops = self._closure(set())
2542
flags = [[loops, set(), self.groundset() - loops]]
2543
for r in xrange(r):
2544
flags = self._extend_flags(flags)
2545
return flags
2546
2547
cpdef flats(self, r):
2548
r"""
2549
Return the collection of flats of the matroid of specified rank.
2550
2551
A *flat* is a closed set.
2552
2553
INPUT:
2554
2555
- ``r`` -- A natural number.
2556
2557
OUTPUT:
2558
2559
An iterable containing all flats of rank ``r``.
2560
2561
.. SEEALSO::
2562
2563
:meth:`M.closure() <sage.matroids.matroid.Matroid.closure>`
2564
2565
EXAMPLES::
2566
2567
sage: M = matroids.named_matroids.Fano()
2568
sage: sorted([sorted(F) for F in M.flats(2)])
2569
[['a', 'b', 'f'], ['a', 'c', 'e'], ['a', 'd', 'g'],
2570
['b', 'c', 'd'], ['b', 'e', 'g'], ['c', 'f', 'g'],
2571
['d', 'e', 'f']]
2572
"""
2573
return SetSystem(list(self.groundset()),
2574
subsets=[f[0] for f in self._flags(r)])
2575
2576
cpdef coflats(self, r):
2577
r"""
2578
Return the collection of coflats of the matroid of specified corank.
2579
2580
A *coflat* is a coclosed set.
2581
2582
INPUT:
2583
2584
- ``r`` -- a nonnegative integer.
2585
2586
OUTPUT:
2587
2588
An iterable containing all coflats of corank ``r``.
2589
2590
.. SEEALSO::
2591
2592
:meth:`M.coclosure() <sage.matroids.matroid.Matroid.coclosure>`
2593
2594
EXAMPLES::
2595
2596
sage: M = matroids.named_matroids.Q6()
2597
sage: sorted([sorted(F) for F in M.coflats(2)])
2598
[['a', 'b'], ['a', 'c'], ['a', 'd', 'f'], ['a', 'e'], ['b', 'c'],
2599
['b', 'd'], ['b', 'e'], ['b', 'f'], ['c', 'd'], ['c', 'e', 'f'],
2600
['d', 'e']]
2601
"""
2602
return self.dual().flats(r)
2603
2604
cpdef hyperplanes(self):
2605
"""
2606
Return the set of hyperplanes of the matroid.
2607
2608
A *hyperplane* is a flat of rank ``self.full_rank() - 1``. A *flat* is
2609
a closed set.
2610
2611
OUTPUT:
2612
2613
An iterable containing all hyperplanes of the matroid.
2614
2615
.. SEEALSO::
2616
2617
:meth:`M.flats() <sage.matroids.matroid.Matroid.flats>`
2618
2619
EXAMPLES::
2620
2621
sage: M = matroids.Uniform(2, 3)
2622
sage: sorted([sorted(F) for F in M.hyperplanes()])
2623
[[0], [1], [2]]
2624
2625
"""
2626
return self.flats(self.full_rank() - 1)
2627
2628
cpdef f_vector(self):
2629
r"""
2630
Return the `f`-vector of the matroid.
2631
2632
The `f`-*vector* is a vector `(f_0, ..., f_r)`, where `f_i` is the
2633
number of flats of rank `i`, and `r` is the rank of the matroid.
2634
2635
OUTPUT:
2636
2637
List of integers.
2638
2639
EXAMPLES::
2640
2641
sage: M = matroids.named_matroids.BetsyRoss()
2642
sage: M.f_vector()
2643
[1, 11, 20, 1]
2644
2645
"""
2646
loops = self._closure(set())
2647
flags = [[loops, set(), self.groundset() - loops]]
2648
f_vec = [1]
2649
for r in xrange(self.full_rank()):
2650
flags = self._extend_flags(flags)
2651
f_vec.append(len(flags))
2652
return f_vec
2653
2654
# isomorphism and equality
2655
2656
cpdef is_isomorphic(self, other):
2657
r"""
2658
Test matroid isomorphism.
2659
2660
Two matroids `M` and `N` are *isomorphic* if there is a bijection `f`
2661
from the groundset of `M` to the groundset of `N` such that a subset
2662
`X` is independent in `M` if and only if `f(X)` is independent in `N`.
2663
2664
INPUT:
2665
2666
- ``other`` -- A matroid.
2667
2668
OUTPUT:
2669
2670
Boolean.
2671
2672
EXAMPLES::
2673
2674
sage: M1 = matroids.Wheel(3)
2675
sage: M2 = matroids.CompleteGraphic(4)
2676
sage: M1.is_isomorphic(M2)
2677
True
2678
sage: G3 = graphs.CompleteGraph(4)
2679
sage: M1.is_isomorphic(G3)
2680
Traceback (most recent call last):
2681
...
2682
TypeError: can only test for isomorphism between matroids.
2683
2684
2685
sage: M1 = matroids.named_matroids.Fano()
2686
sage: M2 = matroids.named_matroids.NonFano()
2687
sage: M1.is_isomorphic(M2)
2688
False
2689
"""
2690
if not isinstance(other, Matroid):
2691
raise TypeError("can only test for isomorphism between matroids.")
2692
return self._is_isomorphic(other)
2693
2694
cpdef _is_isomorphic(self, other):
2695
"""
2696
Test if ``self`` is isomorphic to ``other``.
2697
2698
Internal version that performs no checks on input.
2699
2700
INPUT:
2701
2702
- ``other`` -- A matroid.
2703
2704
OUTPUT:
2705
2706
Boolean.
2707
2708
EXAMPLES::
2709
2710
sage: M1 = matroids.Wheel(3)
2711
sage: M2 = matroids.CompleteGraphic(4)
2712
sage: M1._is_isomorphic(M2)
2713
True
2714
2715
sage: M1 = matroids.named_matroids.Fano()
2716
sage: M2 = matroids.named_matroids.NonFano()
2717
sage: M1._is_isomorphic(M2)
2718
False
2719
2720
"""
2721
if self is other:
2722
return True
2723
return (self.full_rank() == other.full_rank() and self.nonbases()._isomorphism(other.nonbases()) is not None)
2724
2725
cpdef equals(self, other):
2726
"""
2727
Test for matroid equality.
2728
2729
Two matroids `M` and `N` are *equal* if they have the same groundset
2730
and a subset `X` is independent in `M` if and only if it is
2731
independent in `N`.
2732
2733
INPUT:
2734
2735
- ``other`` -- A matroid.
2736
2737
OUTPUT:
2738
2739
Boolean.
2740
2741
.. NOTE::
2742
2743
This method tests abstract matroid equality. The ``==`` operator
2744
takes a more restricted view: ``M == N`` returns ``True`` only if
2745
2746
#. the internal representations are of the same type,
2747
#. those representations are equivalent (for an appropriate
2748
meaning of "equivalent" in that class), and
2749
#. ``M.equals(N)``.
2750
2751
EXAMPLES:
2752
2753
A :class:`BinaryMatroid <sage.matroids.linear_matroid.BinaryMatroid>`
2754
and :class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`
2755
use different representations of the matroid internally, so ``==``
2756
yields ``False``, even if the matroids are equal::
2757
2758
sage: from sage.matroids.advanced import *
2759
sage: M = matroids.named_matroids.Fano()
2760
sage: M
2761
Fano: Binary matroid of rank 3 on 7 elements, type (3, 0)
2762
sage: M1 = BasisMatroid(M)
2763
sage: M2 = Matroid(groundset='abcdefg', reduced_matrix=[
2764
....: [0, 1, 1, 1], [1, 0, 1, 1], [1, 1, 0, 1]], field=GF(2))
2765
sage: M.equals(M1)
2766
True
2767
sage: M.equals(M2)
2768
True
2769
sage: M == M1
2770
False
2771
sage: M == M2
2772
True
2773
2774
:class:`LinearMatroid <sage.matroids.linear_matroid.LinearMatroid>`
2775
instances ``M`` and ``N`` satisfy ``M == N`` if the representations
2776
are equivalent up to row operations and column scaling::
2777
2778
sage: M1 = LinearMatroid(groundset='abcd', matrix=Matrix(GF(7),
2779
....: [[1, 0, 1, 1], [0, 1, 1, 2]]))
2780
sage: M2 = LinearMatroid(groundset='abcd', matrix=Matrix(GF(7),
2781
....: [[1, 0, 1, 1], [0, 1, 1, 3]]))
2782
sage: M3 = LinearMatroid(groundset='abcd', matrix=Matrix(GF(7),
2783
....: [[2, 6, 1, 0], [6, 1, 0, 1]]))
2784
sage: M1.equals(M2)
2785
True
2786
sage: M1.equals(M3)
2787
True
2788
sage: M1 == M2
2789
False
2790
sage: M1 == M3
2791
True
2792
"""
2793
if self is other:
2794
return True
2795
if not isinstance(other, Matroid):
2796
raise TypeError("can only test for isomorphism between matroids.")
2797
if (not self.size() == other.size() or len(other.groundset().difference(self.groundset())) > 0):
2798
return False
2799
morphism = {e: e for e in self.groundset()}
2800
return self._is_isomorphism(other, morphism)
2801
2802
cpdef is_isomorphism(self, other, morphism):
2803
"""
2804
Test if a provided morphism induces a matroid isomorphism.
2805
2806
A *morphism* is a map from the groundset of ``self`` to the groundset
2807
of ``other``.
2808
2809
INPUT:
2810
2811
- ``other`` -- A matroid.
2812
- ``morphism`` -- A map. Can be, for instance,
2813
a dictionary, function, or permutation.
2814
2815
OUTPUT:
2816
2817
Boolean.
2818
2819
..SEEALSO::
2820
2821
:meth:`M.is_isomorphism() <sage.matroids.matroid.Matroid.is_isomorphism>`
2822
2823
.. NOTE::
2824
2825
If you know the input is valid, consider using the faster method
2826
``self._is_isomorphism``.
2827
2828
EXAMPLES:
2829
2830
::
2831
2832
sage: M = matroids.named_matroids.Pappus()
2833
sage: N = matroids.named_matroids.NonPappus()
2834
sage: N.is_isomorphism(M, {e:e for e in M.groundset()})
2835
False
2836
2837
sage: M = matroids.named_matroids.Fano() \ ['g']
2838
sage: N = matroids.Wheel(3)
2839
sage: morphism = {'a':0, 'b':1, 'c': 2, 'd':4, 'e':5, 'f':3}
2840
sage: M.is_isomorphism(N, morphism)
2841
True
2842
2843
A morphism can be specified as a dictionary (above), a permutation,
2844
a function, and many other types of maps::
2845
2846
sage: M = matroids.named_matroids.Fano()
2847
sage: P = PermutationGroup([[('a', 'b', 'c'),
2848
....: ('d', 'e', 'f'), ('g')]]).gen()
2849
sage: M.is_isomorphism(M, P)
2850
True
2851
2852
sage: M = matroids.named_matroids.Pappus()
2853
sage: N = matroids.named_matroids.NonPappus()
2854
sage: def f(x):
2855
....: return x
2856
....:
2857
sage: N.is_isomorphism(M, f)
2858
False
2859
sage: N.is_isomorphism(N, f)
2860
True
2861
2862
There is extensive checking for inappropriate input::
2863
2864
sage: M = matroids.CompleteGraphic(4)
2865
sage: M.is_isomorphism(graphs.CompleteGraph(4), lambda x:x)
2866
Traceback (most recent call last):
2867
...
2868
TypeError: can only test for isomorphism between matroids.
2869
2870
sage: M = matroids.CompleteGraphic(4)
2871
sage: sorted(M.groundset())
2872
[0, 1, 2, 3, 4, 5]
2873
sage: M.is_isomorphism(M, {0: 1, 1: 2, 2: 3})
2874
Traceback (most recent call last):
2875
...
2876
ValueError: domain of morphism does not contain groundset of this
2877
matroid.
2878
2879
sage: M = matroids.CompleteGraphic(4)
2880
sage: sorted(M.groundset())
2881
[0, 1, 2, 3, 4, 5]
2882
sage: M.is_isomorphism(M, {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1})
2883
Traceback (most recent call last):
2884
...
2885
ValueError: range of morphism does not contain groundset of other
2886
matroid.
2887
2888
sage: M = matroids.CompleteGraphic(3)
2889
sage: N = Matroid(bases=['ab', 'ac', 'bc'])
2890
sage: f = [0, 1, 2]
2891
sage: g = {'a': 0, 'b': 1, 'c': 2}
2892
sage: N.is_isomorphism(M, f)
2893
Traceback (most recent call last):
2894
...
2895
ValueError: the morphism argument does not seem to be an
2896
isomorphism.
2897
2898
sage: N.is_isomorphism(M, g)
2899
True
2900
"""
2901
from copy import copy
2902
if not isinstance(other, Matroid):
2903
raise TypeError("can only test for isomorphism between matroids.")
2904
if not isinstance(morphism, dict):
2905
mf = {}
2906
try:
2907
for e in self.groundset():
2908
mf[e] = morphism[e]
2909
except (IndexError, TypeError, ValueError):
2910
try:
2911
for e in self.groundset():
2912
mf[e] = morphism(e)
2913
except (TypeError, ValueError):
2914
raise ValueError("the morphism argument does not seem to be an isomorphism.")
2915
else:
2916
mf = morphism
2917
if len(self.groundset().difference(mf.keys())) > 0:
2918
raise ValueError("domain of morphism does not contain groundset of this matroid.")
2919
if len(other.groundset().difference([mf[e] for e in self.groundset()])) > 0:
2920
raise ValueError("range of morphism does not contain groundset of other matroid.")
2921
if self is other:
2922
return self._is_isomorphism(copy(other), mf)
2923
return self._is_isomorphism(other, mf)
2924
2925
cpdef _is_isomorphism(self, other, morphism):
2926
"""
2927
Version of is_isomorphism() that does no type checking.
2928
2929
INPUT:
2930
2931
- ``other`` -- A matroid instance.
2932
- ``morphism`` -- a dictionary mapping the groundset of ``self`` to
2933
the groundset of ``other``.
2934
2935
OUTPUT:
2936
2937
Boolean.
2938
2939
EXAMPLES::
2940
2941
sage: M = matroids.named_matroids.Pappus()
2942
sage: N = matroids.named_matroids.NonPappus()
2943
sage: N._is_isomorphism(M, {e:e for e in M.groundset()})
2944
False
2945
2946
sage: M = matroids.named_matroids.Fano() \ ['g']
2947
sage: N = matroids.Wheel(3)
2948
sage: morphism = {'a':0, 'b':1, 'c': 2, 'd':4, 'e':5, 'f':3}
2949
sage: M._is_isomorphism(N, morphism)
2950
True
2951
"""
2952
import basis_exchange_matroid
2953
import basis_matroid
2954
sf = basis_matroid.BasisMatroid(self)
2955
if not isinstance(other, basis_exchange_matroid.BasisExchangeMatroid):
2956
ot = basis_matroid.BasisMatroid(other)
2957
else:
2958
ot = other
2959
return sf._is_isomorphism(ot, morphism)
2960
2961
def __hash__(self):
2962
r"""
2963
Return an invariant of the matroid.
2964
2965
This function is called when matroids are added to a set. It is very
2966
desirable to override it so it can distinguish matroids on the same
2967
groundset, which is a very typical use case!
2968
2969
.. WARNING::
2970
2971
This method is linked to ``__richcmp__`` (in Cython) and
2972
``__cmp__`` or ``__eq__``/``__ne__`` (in Python). If you override
2973
one, you should (and in Cython: MUST) override the other!
2974
2975
EXAMPLES::
2976
2977
sage: M = matroids.named_matroids.BetsyRoss()
2978
sage: N = matroids.named_matroids.BetsyRoss()
2979
sage: hash(M) == hash(N)
2980
True
2981
sage: O = matroids.named_matroids.TicTacToe()
2982
sage: hash(M) == hash(O)
2983
False
2984
"""
2985
return hash((self.groundset(), self.full_rank()))
2986
2987
def __richcmp__(left, right, int op):
2988
r"""
2989
Compare two matroids.
2990
2991
We take a very restricted view on equality: the objects need to be of
2992
the exact same type (so no subclassing) and the internal data need to
2993
be the same. The default implementation below, testing matroid
2994
equality, should be overridden by subclasses.
2995
2996
.. TODO::
2997
2998
In a user guide, write about "pitfalls": testing something like
2999
``M in S`` could yield ``False``, even if ``N.equals(M)`` is
3000
``True`` for some ``N`` in ``S``.
3001
3002
.. WARNING::
3003
3004
This method is linked to ``__hash__``. If you override one, you
3005
MUST override the other!
3006
3007
.. SEEALSO::
3008
3009
:meth:`M.equals() <sage.matroids.matroid.Matroid.equals>`
3010
3011
EXAMPLES::
3012
3013
sage: M1 = Matroid(groundset='abcd', matrix=Matrix(GF(7),
3014
....: [[1, 0, 1, 1], [0, 1, 1, 2]]))
3015
sage: M2 = Matroid(groundset='abcd', matrix=Matrix(GF(7),
3016
....: [[1, 0, 1, 1], [0, 1, 1, 3]]))
3017
sage: M3 = Matroid(groundset='abcd', matrix=Matrix(GF(7),
3018
....: [[2, 6, 1, 0], [6, 1, 0, 1]]))
3019
sage: M1.equals(M2)
3020
True
3021
sage: M1.equals(M3)
3022
True
3023
sage: M1 != M2 # indirect doctest
3024
True
3025
sage: M1 == M3 # indirect doctest
3026
True
3027
"""
3028
import basis_matroid
3029
if op in [0, 1, 4, 5]: # <, <=, >, >=
3030
return NotImplemented
3031
if left.__class__ != right.__class__:
3032
return NotImplemented
3033
# The above test can be tricked. One could:
3034
# * spoof the __class__ attribute
3035
# * call the method with two things that are not Matroid instances, as in
3036
# sage.matroids.matroid.Matroid.__richcmp__(p, q, 2)
3037
# Non-abstract subclasses should just call isinstance on both left and right.
3038
if hash(left) != hash(right):
3039
if op == 2: # ==
3040
return False
3041
if op == 3: # !=
3042
return True
3043
3044
res = (basis_matroid.BasisMatroid(left) == basis_matroid.BasisMatroid(right)) # Default implementation
3045
if op == 2: # ==
3046
return res
3047
if op == 3: # !=
3048
return not res
3049
3050
# Minors and duality
3051
3052
cpdef minor(self, contractions=None, deletions=None):
3053
r"""
3054
Return the minor of ``self`` obtained by contracting, respectively
3055
deleting, the element(s) of ``contractions`` and ``deletions``.
3056
3057
A *minor* of a matroid is a matroid obtained by repeatedly removing
3058
elements in one of two ways: either
3059
:meth:`contract <sage.matroids.matroid.Matroid.contract>` or
3060
:meth:`delete <sage.matroids.matroid.Matroid.delete>` them. It can be
3061
shown that the final matroid does not depend on the order in which
3062
elements are removed.
3063
3064
INPUT:
3065
3066
- ``contractions`` -- (default: ``None``) an element or set of
3067
elements to be contracted.
3068
- ``deletions`` -- (default: ``None``) an element or set of elements
3069
to be deleted.
3070
3071
OUTPUT:
3072
3073
A matroid.
3074
3075
.. NOTE::
3076
3077
The output is either of the same type as ``self``, or an instance
3078
of
3079
:class:`MinorMatroid <sage.matroids.minor_matroid.MinorMatroid>`.
3080
3081
.. SEEALSO::
3082
3083
:meth:`M.contract() <sage.matroids.matroid.Matroid.contract>`,
3084
:meth:`M.delete() <sage.matroids.matroid.Matroid.delete>`
3085
3086
EXAMPLES:
3087
3088
::
3089
3090
sage: M = matroids.Wheel(4)
3091
sage: N = M.minor(contractions=[7], deletions=[0])
3092
sage: N.is_isomorphic(matroids.Wheel(3))
3093
True
3094
3095
The sets of contractions and deletions need not be independent,
3096
respectively coindependent::
3097
3098
sage: M = matroids.named_matroids.Fano()
3099
sage: M.rank('abf')
3100
2
3101
sage: M.minor(contractions='abf')
3102
Binary matroid of rank 1 on 4 elements, type (1, 0)
3103
3104
However, they need to be subsets of the groundset, and disjoint::
3105
3106
sage: M = matroids.named_matroids.Vamos()
3107
sage: M.minor('abc', 'defg')
3108
M / {'a', 'b', 'c'} \ {'d', 'e', 'f', 'g'}, where M is Vamos:
3109
Matroid of rank 4 on 8 elements with circuit-closures
3110
{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},
3111
{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'}, {'c', 'd', 'e', 'f'}},
3112
4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}
3113
3114
sage: M.minor('defgh', 'abc')
3115
M / {'d', 'e', 'f', 'g'} \ {'a', 'b', 'c', 'h'}, where M is Vamos:
3116
Matroid of rank 4 on 8 elements with circuit-closures
3117
{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},
3118
{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'}, {'c', 'd', 'e', 'f'}},
3119
4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}
3120
3121
sage: M.minor([1, 2, 3], 'efg')
3122
Traceback (most recent call last):
3123
...
3124
ValueError: input contractions is not a subset of the groundset.
3125
sage: M.minor('efg', [1, 2, 3])
3126
Traceback (most recent call last):
3127
...
3128
ValueError: input deletions is not a subset of the groundset.
3129
sage: M.minor('ade', 'efg')
3130
Traceback (most recent call last):
3131
...
3132
ValueError: contraction and deletion sets are not disjoint.
3133
3134
3135
.. WARNING::
3136
3137
There can be ambiguity if elements of the groundset are themselves
3138
iterable, and their elements are in the groundset. The main
3139
example of this is when an element is a string. See the
3140
documentation of the methods
3141
:meth:`contract() <sage.matroids.matroid.Matroid.contract>` and
3142
:meth:`delete() <sage.matroids.matroid.Matroid.delete>` for an
3143
example of this.
3144
"""
3145
try:
3146
if contractions in self.groundset():
3147
contractions = [contractions]
3148
# Else we expect to have to enumerate over the characters in the string.
3149
except TypeError:
3150
pass
3151
if (not isinstance(contractions, str) and not hasattr(contractions, '__iter__') and not contractions is None):
3152
contractions = [contractions]
3153
try:
3154
if deletions in self.groundset():
3155
deletions = [deletions]
3156
# Else we expect to have to enumerate over the characters in the string.
3157
except TypeError:
3158
pass
3159
if (not isinstance(deletions, str) and not hasattr(deletions, '__iter__') and not deletions is None):
3160
deletions = [deletions]
3161
conset, delset = sanitize_contractions_deletions(self, contractions, deletions)
3162
return self._minor(conset, delset)
3163
3164
cpdef contract(self, X):
3165
r"""
3166
Contract elements.
3167
3168
If `e` is a non-loop element, then the matroid `M / e` is a matroid
3169
on groundset `E(M) - e`. A set `X` is independent in `M / e` if and
3170
only if `X \cup e` is independent in `M`. If `e` is a loop then
3171
contracting `e` is the same as deleting `e`. We say that `M / e` is
3172
the matroid obtained from `M` by *contracting* `e`. Contracting an
3173
element in `M` is the same as deleting an element in the dual of `M`.
3174
3175
When contracting a set, the elements of that set are contracted one by
3176
one. It can be shown that the resulting matroid does not depend on the
3177
order of the contractions.
3178
3179
Sage supports the shortcut notation ``M / X`` for ``M.contract(X)``.
3180
3181
INPUT:
3182
3183
- ``X`` -- Either a single element of the groundset, or a collection
3184
of elements.
3185
3186
OUTPUT:
3187
3188
The matroid obtained by contracting the element(s) in ``X``.
3189
3190
.. SEEALSO::
3191
3192
:meth:`M.delete() <sage.matroids.matroid.Matroid.delete>`
3193
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`
3194
:meth:`M.minor() <sage.matroids.matroid.Matroid.minor>`
3195
3196
EXAMPLES:
3197
3198
::
3199
3200
sage: M = matroids.named_matroids.Fano()
3201
sage: sorted(M.groundset())
3202
['a', 'b', 'c', 'd', 'e', 'f', 'g']
3203
sage: M.contract(['a', 'c'])
3204
Binary matroid of rank 1 on 5 elements, type (1, 0)
3205
sage: M.contract(['a']) == M / ['a']
3206
True
3207
3208
One can use a single element, rather than a set::
3209
3210
sage: M = matroids.CompleteGraphic(4)
3211
sage: M.contract(1) == M.contract([1])
3212
True
3213
sage: M / 1
3214
Regular matroid of rank 2 on 5 elements with 8 bases
3215
3216
Note that one can iterate over strings::
3217
3218
sage: M = matroids.named_matroids.Fano()
3219
sage: M / 'abc'
3220
Binary matroid of rank 0 on 4 elements, type (0, 0)
3221
3222
The following is therefore ambiguous. Sage will contract the single
3223
element::
3224
3225
sage: M = Matroid(groundset=['a', 'b', 'c', 'abc'],
3226
....: bases=[['a', 'b', 'c'], ['a', 'b', 'abc']])
3227
sage: sorted((M / 'abc').groundset())
3228
['a', 'b', 'c']
3229
"""
3230
return self.minor(contractions=X)
3231
3232
def __div__(self, X):
3233
r"""
3234
Shorthand for ``self.contract(X)``.
3235
3236
EXAMPLES::
3237
3238
sage: M = matroids.CompleteGraphic(4)
3239
sage: M.contract(1) == M / 1 # indirect doctest
3240
True
3241
"""
3242
# Shorthand: M / X
3243
return self.contract(X)
3244
3245
cpdef delete(self, X):
3246
r"""
3247
Delete elements.
3248
3249
If `e` is an element, then the matroid `M \setminus e` is a matroid
3250
on groundset `E(M) - e`. A set `X` is independent in `M \setminus e`
3251
if and only if `X` is independent in `M`. We say that `M \setminus e`
3252
is the matroid obtained from `M` by *deleting* `e`.
3253
3254
When deleting a set, the elements of that set are deleted one by
3255
one. It can be shown that the resulting matroid does not depend on the
3256
order of the deletions.
3257
3258
Sage supports the shortcut notation ``M \ X`` for ``M.delete(X)``.
3259
3260
INPUT:
3261
3262
- ``X`` -- Either a single element of the groundset, or a collection
3263
of elements.
3264
3265
OUTPUT:
3266
3267
The matroid obtained by deleting the element(s) in ``X``.
3268
3269
.. SEEALSO::
3270
3271
:meth:`M.contract() <sage.matroids.matroid.Matroid.contract>`
3272
:meth:`M.minor() <sage.matroids.matroid.Matroid.minor>`
3273
3274
EXAMPLES:
3275
3276
::
3277
3278
sage: M = matroids.named_matroids.Fano()
3279
sage: sorted(M.groundset())
3280
['a', 'b', 'c', 'd', 'e', 'f', 'g']
3281
sage: M.delete(['a', 'c'])
3282
Binary matroid of rank 3 on 5 elements, type (1, 6)
3283
sage: M.delete(['a']) == M \ ['a']
3284
True
3285
3286
One can use a single element, rather than a set::
3287
3288
sage: M = matroids.CompleteGraphic(4)
3289
sage: M.delete(1) == M.delete([1])
3290
True
3291
sage: M \ 1
3292
Regular matroid of rank 3 on 5 elements with 8 bases
3293
3294
Note that one can iterate over strings::
3295
3296
sage: M = matroids.named_matroids.Fano()
3297
sage: M \ 'abc'
3298
Binary matroid of rank 3 on 4 elements, type (0, 5)
3299
3300
The following is therefore ambiguous. Sage will delete the single
3301
element::
3302
3303
sage: M = Matroid(groundset=['a', 'b', 'c', 'abc'],
3304
....: bases=[['a', 'b', 'c'], ['a', 'b', 'abc']])
3305
sage: sorted((M \ 'abc').groundset())
3306
['a', 'b', 'c']
3307
"""
3308
return self.minor(deletions=X)
3309
3310
cpdef _backslash_(self, X):
3311
r"""
3312
Shorthand for ``self.delete(X)``.
3313
3314
EXAMPLES::
3315
3316
sage: M = matroids.CompleteGraphic(4)
3317
sage: M.delete(1) == M \ 1 # indirect doctest
3318
True
3319
"""
3320
return self.delete(X)
3321
3322
cpdef dual(self):
3323
"""
3324
Return the dual of the matroid.
3325
3326
Let `M` be a matroid with ground set `E`. If `B` is the set of bases
3327
of `M`, then the set `\{E - b : b \in B\}` is the set of bases of
3328
another matroid, the *dual* of `M`.
3329
3330
.. NOTE::
3331
3332
This function wraps ``self`` in a ``DualMatroid`` object. For more
3333
efficiency, subclasses that can, should override this method.
3334
3335
OUTPUT:
3336
3337
The dual matroid.
3338
3339
EXAMPLES::
3340
3341
sage: M = matroids.named_matroids.Pappus()
3342
sage: N = M.dual()
3343
sage: N.rank()
3344
6
3345
sage: N
3346
Dual of 'Pappus: Matroid of rank 3 on 9 elements with
3347
circuit-closures
3348
{2: {{'a', 'b', 'c'}, {'a', 'f', 'h'}, {'c', 'e', 'g'},
3349
{'b', 'f', 'g'}, {'c', 'd', 'h'}, {'d', 'e', 'f'},
3350
{'a', 'e', 'i'}, {'b', 'd', 'i'}, {'g', 'h', 'i'}},
3351
3: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'}}}'
3352
"""
3353
import dual_matroid
3354
return dual_matroid.DualMatroid(self)
3355
3356
cpdef truncation(self):
3357
"""
3358
Return a rank-1 truncation of the matroid.
3359
3360
Let `M` be a matroid of rank `r`. The *truncation* of `M` is the
3361
matroid obtained by declaring all subsets of size `r` dependent. It
3362
can be obtained by adding an element freely to the span of the matroid
3363
and then contracting that element.
3364
3365
OUTPUT:
3366
3367
A matroid.
3368
3369
.. SEEALSO::
3370
3371
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`,
3372
:meth:`M.contract() <sage.matroids.matroid.Matroid.contract>`
3373
3374
EXAMPLES::
3375
3376
sage: M = matroids.named_matroids.Fano()
3377
sage: N = M.truncation()
3378
sage: N.is_isomorphic(matroids.Uniform(2, 7))
3379
True
3380
"""
3381
if self.full_rank() == 0:
3382
return None
3383
l = newlabel(self.groundset())
3384
return self._extension(l, [])._minor(contractions=frozenset([l]),
3385
deletions=frozenset([]))
3386
3387
cpdef has_minor(self, N):
3388
"""
3389
Check if ``self`` has a minor isomorphic to ``N``.
3390
3391
INPUT:
3392
3393
- ``N`` -- A matroid.
3394
3395
OUTPUT:
3396
3397
Boolean.
3398
3399
.. SEEALSO::
3400
3401
:meth:`M.minor() <sage.matroids.matroid.Matroid.minor>`,
3402
:meth:`M.is_isomorphic() <sage.matroids.matroid.Matroid.is_isomorphic>`
3403
3404
.. TODO::
3405
3406
This important method can (and should) be optimized considerably.
3407
See [Hlineny]_ p.1219 for hints to that end.
3408
3409
EXAMPLES::
3410
3411
sage: M = matroids.Whirl(3)
3412
sage: matroids.named_matroids.Fano().has_minor(M)
3413
False
3414
sage: matroids.named_matroids.NonFano().has_minor(M)
3415
True
3416
"""
3417
if not isinstance(N, Matroid):
3418
raise ValueError("N must be a matroid.")
3419
return self._has_minor(N)
3420
3421
cpdef has_line_minor(self, k, hyperlines=None):
3422
"""
3423
Test if the matroid has a `U_{2, k}`-minor.
3424
3425
The matroid `U_{2, k}` is a matroid on `k` elements in which every
3426
subset of at most 2 elements is independent, and every subset of more
3427
than two elements is dependent.
3428
3429
The optional argument ``hyperlines`` restricts the search space: this
3430
method returns ``False`` if `si(M/F)` is isomorphic to `U_{2, l}` with
3431
`l \geq k` for some `F` in ``hyperlines``, and ``True`` otherwise.
3432
3433
INPUT:
3434
3435
- ``k`` -- the length of the line minor
3436
- ``hyperlines`` -- (default: ``None``) a set of flats of codimension
3437
2. Defaults to the set of all flats of codimension 2.
3438
3439
OUTPUT:
3440
3441
Boolean.
3442
3443
.. SEEALSO::
3444
3445
:meth:`Matroid.has_minor`
3446
3447
EXAMPLES::
3448
3449
sage: M = matroids.named_matroids.N1()
3450
sage: M.has_line_minor(4)
3451
True
3452
sage: M.has_line_minor(5)
3453
False
3454
sage: M.has_line_minor(k=4, hyperlines=[['a', 'b', 'c']])
3455
False
3456
sage: M.has_line_minor(k=4, hyperlines=[['a', 'b', 'c'],
3457
....: ['a', 'b', 'd' ]])
3458
True
3459
3460
"""
3461
if self.full_rank() < 2:
3462
return False
3463
if self.full_corank() < k - 2:
3464
return False
3465
if hyperlines is None:
3466
hyperlines = self.flats(self.full_rank() - 2)
3467
else:
3468
hyperlines = [frozenset(X) for X in hyperlines]
3469
allright = True
3470
for X in hyperlines:
3471
if not X.issubset(self.groundset()):
3472
raise ValueError("input sets need to be subset of the groundset.")
3473
if not self._rank(X) == self.full_rank() - 2:
3474
raise ValueError("input sets need to have rank 2 less than the rank of the matroid.")
3475
# Note that we don't check if the sets are flats, because loops
3476
# get simplified away anyway.
3477
return self._has_line_minor(k, hyperlines)
3478
3479
cpdef _has_line_minor(self, k, hyperlines):
3480
"""
3481
Test if the matroid has a `U_{2, k}`-minor.
3482
3483
Internal version that does no input checking.
3484
3485
INPUT:
3486
3487
- ``k`` -- the length of the line minor
3488
- ``hyperlines`` -- (default: None) a set of flats of codimension 2.
3489
The flats are assumed to be ``frozenset`` compatible.
3490
3491
OUTPUT:
3492
3493
Boolean. ``False`` if `si(M/F)` is isomorphic to `U_{2, l}` with
3494
`l \geq k` for some `F` in ``hyperlines``. ``True``, otherwise.
3495
3496
EXAMPLES::
3497
3498
sage: M = matroids.named_matroids.NonPappus()
3499
sage: M._has_line_minor(5, M.flats(1))
3500
True
3501
"""
3502
if self.full_rank() < 2:
3503
return False
3504
if self.full_corank() < k - 2:
3505
return False
3506
for F in hyperlines:
3507
if self._line_length(F) >= k:
3508
return True
3509
return False
3510
3511
# extensions
3512
3513
cpdef extension(self, element=None, subsets=None):
3514
r"""
3515
Return an extension of the matroid.
3516
3517
An *extension* of `M` by an element `e` is a matroid `M'` such that
3518
`M' \setminus e = M`. The element ``element`` is placed such that it
3519
lies in the :meth:`closure <sage.matroids.matroid.Matroid.closure>` of
3520
each set in ``subsets``, and otherwise as freely as possible. More
3521
precisely, the extension is defined by the
3522
:meth:`modular cut <sage.matroids.matroid.Matroid.modular_cut>`
3523
generated by the sets in ``subsets``.
3524
3525
INPUT:
3526
3527
- ``element`` -- (default: ``None``) the label of the new element. If
3528
not specified, a new label will be generated automatically.
3529
- ``subsets`` -- (default: ``None``) a set of subsets of the matroid.
3530
The extension should be such that the new element is in the span of
3531
each of these. If not specified, the element is assumed to be in the
3532
span of the full groundset.
3533
3534
OUTPUT:
3535
3536
A matroid.
3537
3538
.. NOTE::
3539
3540
Internally, sage uses the notion of a *linear subclass* for
3541
matroid extension. If ``subsets`` already consists of a linear
3542
subclass (i.e. the set of hyperplanes of a modular cut) then the
3543
faster method ``M._extension()`` can be used.
3544
3545
.. SEEALSO::
3546
3547
:meth:`M.extensions() <sage.matroids.matroid.Matroid.extensions>`,
3548
:meth:`M.modular_cut() <sage.matroids.matroid.Matroid.modular_cut>`,
3549
:meth:`M.coextension() <sage.matroids.matroid.Matroid.coextension>`,
3550
:meth:`M.linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`,
3551
:mod:`sage.matroids.extension <sage.matroids.extension>`
3552
3553
EXAMPLES:
3554
3555
First we add an element in general position::
3556
3557
sage: M = matroids.Uniform(3, 6)
3558
sage: N = M.extension(6)
3559
sage: N.is_isomorphic(matroids.Uniform(3, 7))
3560
True
3561
3562
Next we add one inside the span of a specified hyperplane::
3563
3564
sage: M = matroids.Uniform(3, 6)
3565
sage: H = [frozenset([0, 1])]
3566
sage: N = M.extension(6, H)
3567
sage: N
3568
Matroid of rank 3 on 7 elements with 34 bases
3569
sage: [sorted(C) for C in N.circuits() if len(C) == 3]
3570
[[0, 1, 6]]
3571
3572
Putting an element in parallel with another::
3573
3574
sage: M = matroids.named_matroids.Fano()
3575
sage: N = M.extension('z', ['c'])
3576
sage: N.rank('cz')
3577
1
3578
"""
3579
r = self.full_rank() - 1
3580
if element is None:
3581
element = newlabel(self.groundset())
3582
elif element in self.groundset():
3583
raise ValueError("cannot extend by element already in groundset")
3584
if subsets is None:
3585
hyperplanes = []
3586
else:
3587
hyperplanes = [H for H in self.modular_cut(subsets) if self._rank(H) == r]
3588
return self._extension(element, hyperplanes)
3589
3590
cpdef coextension(self, element=None, subsets=None):
3591
r"""
3592
Return a coextension of the matroid.
3593
3594
A *coextension* of `M` by an element `e` is a matroid `M'` such that
3595
`M' / e = M`. The element ``element`` is placed such that it
3596
lies in the
3597
:meth:`coclosure <sage.matroids.matroid.Matroid.coclosure>` of
3598
each set in ``subsets``, and otherwise as freely as possible.
3599
3600
This is the dual method of
3601
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`. See
3602
the documentation there for more details.
3603
3604
INPUT:
3605
3606
- ``element`` -- (default: ``None``) the label of the new element. If
3607
not specified, a new label will be generated automatically.
3608
- ``subsets`` -- (default: ``None``) a set of subsets of the matroid.
3609
The coextension should be such that the new element is in the cospan
3610
of each of these. If not specified, the element is assumed to be in
3611
the cospan of the full groundset.
3612
3613
OUTPUT:
3614
3615
A matroid.
3616
3617
.. SEEALSO::
3618
3619
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`,
3620
:meth:`M.coextensions() <sage.matroids.matroid.Matroid.coextensions>`,
3621
:meth:`M.modular_cut() <sage.matroids.matroid.Matroid.modular_cut>`,
3622
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`,
3623
:meth:`M.linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`,
3624
:mod:`sage.matroids.extension <sage.matroids.extension>`
3625
3626
EXAMPLES:
3627
3628
Add an element in general position::
3629
3630
sage: M = matroids.Uniform(3, 6)
3631
sage: N = M.coextension(6)
3632
sage: N.is_isomorphic(matroids.Uniform(4, 7))
3633
True
3634
3635
Add one inside the span of a specified hyperplane::
3636
3637
sage: M = matroids.Uniform(3, 6)
3638
sage: H = [frozenset([0, 1])]
3639
sage: N = M.coextension(6, H)
3640
sage: N
3641
Matroid of rank 4 on 7 elements with 34 bases
3642
sage: [sorted(C) for C in N.cocircuits() if len(C) == 3]
3643
[[0, 1, 6]]
3644
3645
Put an element in series with another::
3646
3647
sage: M = matroids.named_matroids.Fano()
3648
sage: N = M.coextension('z', ['c'])
3649
sage: N.corank('cz')
3650
1
3651
"""
3652
return self.dual().extension(element, subsets).dual()
3653
3654
cpdef modular_cut(self, subsets):
3655
"""
3656
Compute the modular cut generated by ``subsets``.
3657
3658
A *modular cut* is a collection `C` of flats such that
3659
3660
- If `F \in C` and `F'` is a flat containing `F`, then `F' \in C`
3661
- If `F_1, F_2 \in C` form a modular pair of flats, then
3662
`F_1\cap F_2 \in C`.
3663
3664
A *flat* is a closed set, a *modular pair* is a pair `F_1, F_2` of
3665
flats with `r(F_1) + r(F_2) = r(F_1\cup F_2) + r(F_1\cap F_2)`,
3666
where `r` is the rank function of the matroid.
3667
3668
The modular cut *generated by* ``subsets`` is the smallest modular cut
3669
`C` for which closure`(S) \in C` for all `S` in ``subsets``.
3670
3671
There is a one-to-one correspondence between the modular cuts of a
3672
matroid and the single-element extensions of the matroid. See [Oxley]_
3673
Section 7.2 for more information.
3674
3675
.. NOTE::
3676
3677
Sage uses linear subclasses, rather than modular cuts, internally
3678
for matroid extension. A linear subclass is the set of hyperplanes
3679
(flats of rank `r(M) - 1`) of a modular cut. It determines the
3680
modular cut uniquely (see [Oxley]_ Section 7.2).
3681
3682
INPUT:
3683
3684
- ``subsets`` -- A collection of subsets of the groundset.
3685
3686
OUTPUT:
3687
3688
A collection of subsets.
3689
3690
.. SEEALSO::
3691
3692
:meth:`M.flats() <sage.matroids.matroid.Matroid.flats>`,
3693
:meth:`M.linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`,
3694
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`
3695
3696
EXAMPLES:
3697
3698
Any extension of the Vamos matroid where the new point is placed on
3699
the lines through elements `\{a, b\}` and through `\{c, d\}` is an
3700
extension by a loop::
3701
3702
sage: M = matroids.named_matroids.Vamos()
3703
sage: frozenset([]) in M.modular_cut(['ab', 'cd'])
3704
True
3705
3706
In any extension of the matroid `S_8 \setminus h`, a point on the
3707
lines through `\{c, g\}` and `\{a, e\}` also is on the line through
3708
`\{b, f\}`::
3709
3710
sage: M = matroids.named_matroids.S8()
3711
sage: N = M \ 'h'
3712
sage: frozenset('bf') in N.modular_cut(['cg', 'ae'])
3713
True
3714
3715
The modular cut of the full groundset is equal to just the groundset::
3716
3717
sage: M = matroids.named_matroids.Fano()
3718
sage: M.modular_cut([M.groundset()]).difference(
3719
....: [frozenset(M.groundset())])
3720
set([])
3721
"""
3722
final_list = set()
3723
temp_list = set([self.closure(X) for X in subsets]) # Checks validity
3724
while len(temp_list) > 0:
3725
F = temp_list.pop()
3726
r = self._rank(F)
3727
# Check modular pairs
3728
for FF in final_list:
3729
H = FF.intersection(F)
3730
rH = self._rank(H)
3731
if rH < r:
3732
if rH + self._rank(FF.union(F)) == self._rank(FF) + r:
3733
if not H in final_list:
3734
temp_list.add(H)
3735
# Check upper closure (going just one level up)
3736
if r < self.full_rank() - 1:
3737
for e in self.groundset().difference(F):
3738
FF = self.closure(F.union([e]))
3739
if self._rank(FF) > r and not FF in final_list:
3740
temp_list.add(FF)
3741
final_list.add(F)
3742
return final_list
3743
3744
cpdef linear_subclasses(self, line_length=None, subsets=None):
3745
"""
3746
Return an iterable set of linear subclasses of the matroid.
3747
3748
A *linear subclass* is a set of hyperplanes (i.e. closed sets of rank
3749
`r(M) - 1`) with the following property:
3750
3751
- If `H_1` and `H_2` are members, and `r(H_1 \cap H_2) = r(M) - 2`,
3752
then any hyperplane `H_3` containing `H_1 \cap H_2` is a member too.
3753
3754
A linear subclass is the set of hyperplanes of a
3755
:meth:`modular cut <sage.matroids.matroid.Matroid.modular_cut>` and
3756
uniquely determines the modular cut. Hence the collection of linear
3757
subclasses is in 1-to-1 correspondence with the collection of
3758
single-element extensions of a matroid. See [Oxley]_, section 7.2.
3759
3760
INPUT:
3761
3762
- ``line_length`` -- (default: ``None``) a natural number. If given,
3763
restricts the output to modular cuts that generate an extension by
3764
`e` that does not contain a minor `N` isomorphic to `U_{2, k}`,
3765
where ``k > line_length``, and such that `e \in E(N)`.
3766
- ``subsets`` -- (default: ``None``) a collection of subsets of the
3767
ground set. If given, restricts the output to linear subclasses such
3768
that each hyperplane contains an element of ``subsets``.
3769
3770
OUTPUT:
3771
3772
An iterable collection of linear subclasses.
3773
3774
.. NOTE::
3775
3776
The ``line_length`` argument only checks for lines using the new
3777
element of the corresponding extension. It is still possible that
3778
a long line exists by contracting the new element!
3779
3780
.. SEEALSO::
3781
3782
:meth:`M.flats() <sage.matroids.matroid.Matroid.flats>`,
3783
:meth:`M.modular_cut() <sage.matroids.matroid.Matroid.modular_cut>`,
3784
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`,
3785
:mod:`sage.matroids.extension <sage.matroids.extension>`
3786
3787
EXAMPLES::
3788
3789
sage: M = matroids.named_matroids.Fano()
3790
sage: len(list(M.linear_subclasses()))
3791
16
3792
sage: len(list(M.linear_subclasses(line_length=3)))
3793
8
3794
sage: len(list(M.linear_subclasses(subsets=[{'a', 'b'}])))
3795
5
3796
3797
The following matroid has an extension by element `e` such that
3798
contracting `e` creates a 6-point line, but no 6-point line minor uses
3799
`e`. Consequently, this method returns the modular cut, but the
3800
:meth:`M.extensions() <sage.matroids.matroid.Matroid.extensions>`
3801
method doesn't return the corresponding extension::
3802
3803
sage: M = Matroid(circuit_closures={2: ['abc', 'def'],
3804
....: 3: ['abcdef']})
3805
sage: len(list(M.extensions('g', line_length=5)))
3806
43
3807
sage: len(list(M.linear_subclasses(line_length=5)))
3808
44
3809
"""
3810
import extension
3811
return extension.LinearSubclasses(self, line_length=line_length, subsets=subsets)
3812
3813
cpdef extensions(self, element=None, line_length=None, subsets=None):
3814
r"""
3815
Return an iterable set of single-element extensions of the matroid.
3816
3817
An *extension* of a matroid `M` by element `e` is a matroid `M'` such
3818
that `M' \setminus e = M`. By default, this method returns an iterable
3819
containing all extensions, but it can be restricted in two ways. If
3820
``line_length`` is specified, the output is restricted to those
3821
matroids not containing a line minor of length `k` greater than
3822
``line_length``. If ``subsets`` is specified, then the output is
3823
restricted to those matroids for which the new element lies in the
3824
:meth:`closure <sage.matroids.matroid.Matroid.closure>` of each
3825
member of ``subsets``.
3826
3827
INPUT:
3828
3829
- ``element`` -- (optional) the name of the newly added element in
3830
each extension.
3831
- ``line_length`` -- (optional) a natural number. If given, restricts
3832
the output to extensions that do not contain a `U_{2, k}` minor
3833
where ``k > line_length``.
3834
- ``subsets`` -- (optional) a collection of subsets of the ground set.
3835
If given, restricts the output to extensions where the new element
3836
is contained in all hyperplanes that contain an element of
3837
``subsets``.
3838
3839
OUTPUT:
3840
3841
An iterable containing matroids.
3842
3843
.. NOTE::
3844
3845
The extension by a loop will always occur.
3846
The extension by a coloop will never occur.
3847
3848
.. SEEALSO::
3849
3850
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`,
3851
:meth:`M.modular_cut() <sage.matroids.matroid.Matroid.modular_cut>`,
3852
:meth:`M.linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`,
3853
:mod:`sage.matroids.extension <sage.matroids.extension>`,
3854
:meth:`M.coextensions() <sage.matroids.matroid.Matroid.coextensions>`
3855
3856
EXAMPLES::
3857
3858
sage: M = matroids.named_matroids.P8()
3859
sage: len(list(M.extensions()))
3860
1705
3861
sage: len(list(M.extensions(line_length=4)))
3862
41
3863
sage: sorted(M.groundset())
3864
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
3865
sage: len(list(M.extensions(subsets=[{'a', 'b'}], line_length=4)))
3866
5
3867
3868
"""
3869
import extension
3870
if element is None:
3871
element = newlabel(self.groundset())
3872
else:
3873
if element in self.groundset():
3874
raise ValueError("cannot extend by element already in groundset")
3875
return extension.MatroidExtensions(self, element, line_length=line_length, subsets=subsets) # return enumerator
3876
3877
def coextensions(self, element=None, coline_length=None, subsets=None):
3878
r"""
3879
Return an iterable set of single-element coextensions of the matroid.
3880
3881
A *coextension* of a matroid `M` by element `e` is a matroid `M'` such
3882
that `M' / e = M`. By default, this method returns an iterable
3883
containing all coextensions, but it can be restricted in two ways. If
3884
``coline_length`` is specified, the output is restricted to those
3885
matroids not containing a coline minor of length `k` greater than
3886
``coline_length``. If ``subsets`` is specified, then the output is
3887
restricted to those matroids for which the new element lies in the
3888
:meth:`coclosure <sage.matroids.matroid.Matroid.coclosure>` of each
3889
member of ``subsets``.
3890
3891
This method is dual to
3892
:meth:`M.extensions() <sage.matroids.matroid.Matroid.extensions>`.
3893
3894
INPUT:
3895
3896
- ``element`` -- (optional) the name of the newly added element in
3897
each coextension.
3898
- ``coline_length`` -- (optional) a natural number. If given,
3899
restricts the output to coextensions that do not contain a
3900
`U_{k - 2, k}` minor where ``k > coline_length``.
3901
- ``subsets`` -- (optional) a collection of subsets of the ground set.
3902
If given, restricts the output to extensions where the new element
3903
is contained in all cohyperplanes that contain an element of
3904
``subsets``.
3905
3906
OUTPUT:
3907
3908
An iterable containing matroids.
3909
3910
.. NOTE::
3911
3912
The coextension by a coloop will always occur.
3913
The extension by a loop will never occur.
3914
3915
.. SEEALSO::
3916
3917
:meth:`M.coextension() <sage.matroids.matroid.Matroid.coextension>`,
3918
:meth:`M.modular_cut() <sage.matroids.matroid.Matroid.modular_cut>`,
3919
:meth:`M.linear_subclasses() <sage.matroids.matroid.Matroid.linear_subclasses>`,
3920
:mod:`sage.matroids.extension <sage.matroids.extension>`,
3921
:meth:`M.extensions() <sage.matroids.matroid.Matroid.extensions>`,
3922
:meth:`M.dual() <sage.matroids.matroid.Matroid.dual>`
3923
3924
EXAMPLES::
3925
3926
sage: M = matroids.named_matroids.P8()
3927
sage: len(list(M.coextensions()))
3928
1705
3929
sage: len(list(M.coextensions(coline_length=4)))
3930
41
3931
sage: sorted(M.groundset())
3932
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
3933
sage: len(list(M.coextensions(subsets=[{'a', 'b'}], coline_length=4)))
3934
5
3935
3936
"""
3937
it = self.dual().extensions(element, coline_length, subsets)
3938
return [N.dual() for N in it]
3939
# for M in it:
3940
# yield M.dual() # gives segfault in 5.1
3941
3942
# connectivity
3943
3944
cpdef simplify(self):
3945
r"""
3946
Return the simplification of the matroid.
3947
3948
A matroid is *simple* if it contains no circuits of length 1 or 2.
3949
The *simplification* of a matroid is obtained by deleting all loops
3950
(circuits of length 1) and deleting all but one element from each
3951
parallel class (a closed set of rank 1, that is, each pair in it forms
3952
a circuit of length 2).
3953
3954
OUTPUT:
3955
3956
A matroid.
3957
3958
.. SEEALSO::
3959
3960
:meth:`M.is_simple() <sage.matroids.matroid.Matroid.is_simple>`,
3961
:meth:`M.loops() <sage.matroids.matroid.Matroid.loops>`,
3962
:meth:`M.circuit() <sage.matroids.matroid.Matroid.circuit>`,
3963
:meth:`M.cosimplify() <sage.matroids.matroid.Matroid.cosimplify>`
3964
3965
EXAMPLES::
3966
3967
sage: M = matroids.named_matroids.Fano().contract('a')
3968
sage: M.size() - M.simplify().size()
3969
3
3970
3971
"""
3972
E = set(self.groundset())
3973
E.difference_update(self._closure(frozenset([]))) # groundset minus loops
3974
res = set([])
3975
3976
while len(E) > 0:
3977
e = E.pop()
3978
res.add(e)
3979
E.difference_update(self._closure(frozenset([e])))
3980
return self._minor(contractions=frozenset([]),
3981
deletions=self.groundset().difference(res))
3982
3983
cpdef cosimplify(self):
3984
r"""
3985
Return the cosimplification of the matroid.
3986
3987
A matroid is *cosimple* if it contains no cocircuits of length 1 or 2.
3988
The *cosimplification* of a matroid is obtained by contracting
3989
all coloops (cocircuits of length 1) and contracting all but one
3990
element from each series class (a coclosed set of rank 1, that is,
3991
each pair in it forms a cocircuit of length 2).
3992
3993
OUTPUT:
3994
3995
A matroid.
3996
3997
.. SEEALSO::
3998
3999
:meth:`M.is_cosimple() <sage.matroids.matroid.Matroid.is_cosimple>`,
4000
:meth:`M.coloops() <sage.matroids.matroid.Matroid.coloops>`,
4001
:meth:`M.cocircuit() <sage.matroids.matroid.Matroid.cocircuit>`,
4002
:meth:`M.simplify() <sage.matroids.matroid.Matroid.simplify>`
4003
4004
EXAMPLES::
4005
4006
sage: M = matroids.named_matroids.Fano().dual().delete('a')
4007
sage: M.cosimplify().size()
4008
3
4009
4010
"""
4011
E = set(self.groundset())
4012
E.difference_update(self._coclosure(frozenset([]))) # groundset minus coloops
4013
res = set([])
4014
4015
while len(E) > 0:
4016
e = E.pop()
4017
res.add(e)
4018
E.difference_update(self._coclosure(frozenset([e])))
4019
return self._minor(contractions=self.groundset().difference(res),
4020
deletions=frozenset([]))
4021
4022
cpdef is_simple(self):
4023
"""
4024
Test if the matroid is simple.
4025
4026
A matroid is *simple* if it contains no circuits of length 1 or 2.
4027
4028
OUTPUT:
4029
4030
Boolean.
4031
4032
.. SEEALSO::
4033
4034
:meth:`M.is_cosimple() <sage.matroids.matroid.Matroid.is_cosimple>`,
4035
:meth:`M.loops() <sage.matroids.matroid.Matroid.loops>`,
4036
:meth:`M.circuit() <sage.matroids.matroid.Matroid.circuit>`,
4037
:meth:`M.simplify() <sage.matroids.matroid.Matroid.simplify>`
4038
4039
EXAMPLES::
4040
4041
sage: M = matroids.named_matroids.Fano()
4042
sage: M.is_simple()
4043
True
4044
sage: N = M / 'a'
4045
sage: N.is_simple()
4046
False
4047
"""
4048
if len(self._closure(frozenset())) > 0:
4049
return False
4050
for e in self.groundset():
4051
if len(self._closure(frozenset([e]))) > 1:
4052
return False
4053
return True
4054
4055
cpdef is_cosimple(self):
4056
"""
4057
Test if the matroid is cosimple.
4058
4059
A matroid is *cosimple* if it contains no cocircuits of length 1 or 2.
4060
4061
Dual method of
4062
:meth:`M.is_simple() <sage.matroids.matroid.Matroid.is_simple>`.
4063
4064
OUTPUT:
4065
4066
Boolean.
4067
4068
.. SEEALSO::
4069
4070
:meth:`M.is_simple() <sage.matroids.matroid.Matroid.is_simple>`,
4071
:meth:`M.coloops() <sage.matroids.matroid.Matroid.coloops>`,
4072
:meth:`M.cocircuit() <sage.matroids.matroid.Matroid.cocircuit>`,
4073
:meth:`M.cosimplify() <sage.matroids.matroid.Matroid.cosimplify>`
4074
4075
EXAMPLES::
4076
4077
sage: M = matroids.named_matroids.Fano().dual()
4078
sage: M.is_cosimple()
4079
True
4080
sage: N = M \ 'a'
4081
sage: N.is_cosimple()
4082
False
4083
"""
4084
if len(self._coclosure(frozenset())) > 0:
4085
return False
4086
for e in self.groundset():
4087
if len(self._coclosure(frozenset([e]))) > 1:
4088
return False
4089
return True
4090
4091
cpdef components(self):
4092
"""
4093
Return a list of the components of the matroid.
4094
4095
A *component* is an inclusionwise maximal connected subset of the
4096
matroid. A subset is *connected* if the matroid resulting from
4097
deleting the complement of that subset is
4098
:meth:`connected <sage.matroids.matroid.Matroid.is_connected>`.
4099
4100
OUTPUT:
4101
4102
A list of subsets.
4103
4104
.. SEEALSO::
4105
4106
:meth:`M.is_connected() <sage.matroids.matroid.Matroid.is_connected>`,
4107
:meth:`M.delete() <sage.matroids.matroid.Matroid.delete>`
4108
4109
EXAMPLES::
4110
4111
sage: from sage.matroids.advanced import setprint
4112
sage: M = Matroid(ring=QQ, matrix=[[1, 0, 0, 1, 1, 0],
4113
....: [0, 1, 0, 1, 2, 0],
4114
....: [0, 0, 1, 0, 0, 1]])
4115
sage: setprint(M.components())
4116
[{0, 1, 3, 4}, {2, 5}]
4117
"""
4118
B = self.basis()
4119
components = [frozenset([e]) for e in self.groundset()]
4120
for e in self.groundset() - B:
4121
C = self.circuit(B | set([e]))
4122
components2 = []
4123
for comp in components:
4124
if len(C & comp) != 0:
4125
C = C | comp
4126
else:
4127
components2.append(comp)
4128
components2.append(C)
4129
components = components2
4130
return components
4131
4132
cpdef is_connected(self):
4133
"""
4134
Test if the matroid is connected.
4135
4136
A *separation* in a matroid is a partition `(X, Y)` of the
4137
groundset with `X, Y` nonempty and `r(X) + r(Y) = r(X\cup Y)`.
4138
A matroid is *connected* if it has no separations.
4139
4140
OUTPUT:
4141
4142
Boolean.
4143
4144
.. SEEALSO::
4145
4146
:meth:`M.components() <sage.matroids.matroid.Matroid.components>`,
4147
:meth:`M.is_3connected() <sage.matroids.matroid.Matroid.is_3connected>`
4148
4149
EXAMPLES::
4150
4151
sage: M = Matroid(ring=QQ, matrix=[[1, 0, 0, 1, 1, 0],
4152
....: [0, 1, 0, 1, 2, 0],
4153
....: [0, 0, 1, 0, 0, 1]])
4154
sage: M.is_connected()
4155
False
4156
sage: matroids.named_matroids.Pappus().is_connected()
4157
True
4158
4159
"""
4160
return len(self.components()) == 1
4161
4162
cpdef is_3connected(self):
4163
"""
4164
Test if the matroid is 3-connected.
4165
4166
A `k`-*separation* in a matroid is a partition `(X, Y)` of the
4167
groundset with `|X| \geq k, |Y| \geq k` and `r(X) + r(Y) - r(M) < k`.
4168
A matroid is `k`-*connected* if it has no `l`-separations for `l < k`.
4169
4170
OUTPUT:
4171
4172
Boolean.
4173
4174
.. TODO::
4175
4176
Implement this using the efficient algorithm from [BC79]_.
4177
4178
.. SEEALSO::
4179
4180
:meth:`M.is_connected() <sage.matroids.matroid.Matroid.is_connected>`
4181
4182
EXAMPLES::
4183
4184
sage: matroids.Uniform(2, 3).is_3connected()
4185
True
4186
sage: M = Matroid(ring=QQ, matrix=[[1, 0, 0, 1, 1, 0],
4187
....: [0, 1, 0, 1, 2, 0],
4188
....: [0, 0, 1, 0, 0, 1]])
4189
sage: M.is_3connected()
4190
False
4191
sage: N = Matroid(circuit_closures={2: ['abc', 'cdef'],
4192
....: 3: ['abcdef']},
4193
....: groundset='abcdef')
4194
sage: N.is_3connected()
4195
False
4196
sage: matroids.named_matroids.BetsyRoss().is_3connected()
4197
True
4198
4199
ALGORITHM:
4200
4201
Test all subsets `X` to see if `r(X) + r(E - X) - r(E)` does not equal
4202
`1`.
4203
4204
"""
4205
groundset = self.groundset()
4206
size = len(groundset)
4207
if not self.is_connected():
4208
return False
4209
if (size < 4):
4210
return True # vacuously true
4211
r = self.full_rank()
4212
part_sizes = xrange(2, (size / 2) + 1) # all possible partition sizes
4213
for part in part_sizes:
4214
subs = Subsets(groundset, part)
4215
for X in subs:
4216
Y = groundset.difference(X)
4217
if (self.rank(X) + self.rank(Y) - r == 1):
4218
return False
4219
return True
4220
4221
# optimization
4222
4223
cpdef max_weight_independent(self, X=None, weights=None):
4224
r"""
4225
Return a maximum-weight independent set contained in a subset.
4226
4227
The *weight* of a subset ``S`` is ``sum(weights(e) for e in S)``.
4228
4229
INPUT:
4230
4231
- ``X`` -- (default: ``None``) an iterable with a subset of
4232
``self.groundset()``. If ``X`` is ``None``, the method returns a
4233
maximum-weight independent subset of the groundset.
4234
- ``weights`` -- a dictionary or function mapping the elements of
4235
``X`` to nonnegative weights.
4236
4237
OUTPUT:
4238
4239
A subset of ``X``.
4240
4241
ALGORITHM:
4242
4243
The greedy algorithm. Sort the elements of ``X`` by decreasing
4244
weight, then greedily select elements if they are independent of
4245
all that was selected before.
4246
4247
EXAMPLES::
4248
4249
sage: from sage.matroids.advanced import setprint
4250
sage: M = matroids.named_matroids.Fano()
4251
sage: X = M.max_weight_independent()
4252
sage: M.is_basis(X)
4253
True
4254
4255
sage: wt = {'a': 1, 'b': 2, 'c': 2, 'd': 1/2, 'e': 1,
4256
....: 'f': 2, 'g': 2}
4257
sage: setprint(M.max_weight_independent(weights=wt))
4258
{'b', 'f', 'g'}
4259
sage: def wt(x):
4260
....: return x
4261
....:
4262
sage: M = matroids.Uniform(2, 8)
4263
sage: setprint(M.max_weight_independent(weights=wt))
4264
{6, 7}
4265
sage: setprint(M.max_weight_independent())
4266
{0, 1}
4267
sage: M.max_weight_coindependent(X=[], weights={})
4268
frozenset([])
4269
"""
4270
if X is None:
4271
X = self.groundset()
4272
else:
4273
if not self.groundset().issuperset(X):
4274
raise ValueError("input is not a subset of the groundset.")
4275
if len(X) == 0:
4276
return frozenset([])
4277
if weights is None:
4278
Y = list(X)
4279
else:
4280
wt = []
4281
try:
4282
for e in X:
4283
wt.append((weights[e], e))
4284
except (IndexError, TypeError, ValueError):
4285
try:
4286
wt = []
4287
for e in X:
4288
wt.append((weights(e), e))
4289
except (TypeError, ValueError):
4290
raise TypeError("the weights argument does not seem to be a collection of weights for the set X.")
4291
wt = sorted(wt, reverse=True)
4292
if wt[-1][1] < 0:
4293
raise ValueError("nonnegative weights were expected.")
4294
Y = [e for (w, e) in wt]
4295
res = set([])
4296
r = 0
4297
for e in Y:
4298
res.add(e)
4299
if self._rank(res) > r:
4300
r += 1
4301
else:
4302
res.discard(e)
4303
return frozenset(res)
4304
4305
cpdef max_weight_coindependent(self, X=None, weights=None):
4306
r"""
4307
Return a maximum-weight coindependent set contained in ``X``.
4308
4309
The *weight* of a subset ``S`` is ``sum(weights(e) for e in S)``.
4310
4311
INPUT:
4312
4313
- ``X`` -- (default: ``None``) an iterable with a subset of
4314
``self.groundset()``. If ``X`` is ``None``, the method returns a
4315
maximum-weight coindependent subset of the groundset.
4316
- ``weights`` -- a dictionary or function mapping the elements of
4317
``X`` to nonnegative weights.
4318
4319
OUTPUT:
4320
4321
A subset of ``X``.
4322
4323
ALGORITHM:
4324
4325
The greedy algorithm. Sort the elements of ``X`` by decreasing
4326
weight, then greedily select elements if they are coindependent of
4327
all that was selected before.
4328
4329
EXAMPLES::
4330
4331
sage: from sage.matroids.advanced import setprint
4332
sage: M = matroids.named_matroids.Fano()
4333
sage: X = M.max_weight_coindependent()
4334
sage: M.is_cobasis(X)
4335
True
4336
4337
sage: wt = {'a': 1, 'b': 2, 'c': 2, 'd': 1/2, 'e': 1, 'f': 2,
4338
....: 'g': 2}
4339
sage: setprint(M.max_weight_coindependent(weights=wt))
4340
{'b', 'c', 'f', 'g'}
4341
sage: def wt(x):
4342
....: return x
4343
....:
4344
sage: M = matroids.Uniform(2, 8)
4345
sage: setprint(M.max_weight_coindependent(weights=wt))
4346
{2, 3, 4, 5, 6, 7}
4347
sage: setprint(M.max_weight_coindependent())
4348
{0, 1, 2, 3, 4, 5}
4349
sage: M.max_weight_coindependent(X=[], weights={})
4350
frozenset([])
4351
"""
4352
if X is None:
4353
X = self.groundset()
4354
else:
4355
if not self.groundset().issuperset(X):
4356
raise ValueError("input is not a subset of the groundset.")
4357
if len(X) == 0:
4358
return frozenset([])
4359
if weights is None:
4360
Y = list(X)
4361
else:
4362
wt = []
4363
try:
4364
for e in X:
4365
wt.append((weights[e], e))
4366
except (IndexError, TypeError, ValueError):
4367
try:
4368
wt = []
4369
for e in X:
4370
wt.append((weights(e), e))
4371
except (TypeError, ValueError):
4372
raise TypeError("the weights argument does not seem to be a collection of weights for the set X.")
4373
wt = sorted(wt, reverse=True)
4374
if wt[-1][1] < 0:
4375
raise ValueError("nonnegative weights were expected.")
4376
Y = [e for (w, e) in wt]
4377
res = set([])
4378
r = 0
4379
for e in Y:
4380
res.add(e)
4381
if self._corank(res) > r:
4382
r += 1
4383
else:
4384
res.discard(e)
4385
return frozenset(res)
4386
4387
cpdef intersection(self, other, weights=None):
4388
r"""
4389
Return a maximum-weight common independent set.
4390
4391
A *common independent set* of matroids `M` and `N` with the same
4392
groundset `E` is a subset of `E` that is independent both in `M` and
4393
`N`. The *weight* of a subset ``S`` is ``sum(weights(e) for e in S)``.
4394
4395
INPUT:
4396
4397
- ``other`` -- a second matroid with the same groundset as this
4398
matroid.
4399
- ``weights`` -- (default: ``None``) a dictionary which specifies a
4400
weight for each element of the common groundset. Defaults to the
4401
all-1 weight function.
4402
4403
OUTPUT:
4404
4405
A subset of the groundset.
4406
4407
EXAMPLES::
4408
4409
sage: M = matroids.named_matroids.T12()
4410
sage: N = matroids.named_matroids.ExtendedTernaryGolayCode()
4411
sage: w = {'a':30, 'b':10, 'c':11, 'd':20, 'e':70, 'f':21, 'g':90,
4412
....: 'h':12, 'i':80, 'j':13, 'k':40, 'l':21}
4413
sage: Y = M.intersection(N, w)
4414
sage: sorted(Y)
4415
['a', 'd', 'e', 'g', 'i', 'k']
4416
sage: sum([w[y] for y in Y])
4417
330
4418
sage: M = matroids.named_matroids.Fano()
4419
sage: N = matroids.Uniform(4, 7)
4420
sage: M.intersection(N)
4421
Traceback (most recent call last):
4422
...
4423
ValueError: matroid intersection requires equal groundsets.
4424
"""
4425
if not isinstance(other, Matroid):
4426
raise TypeError("can only intersect two matroids.")
4427
if not self.groundset() == other.groundset():
4428
raise ValueError("matroid intersection requires equal groundsets.")
4429
if weights is None:
4430
wt = {e: 1 for e in self.groundset()}
4431
else:
4432
wt = {}
4433
try:
4434
for e in self.groundset():
4435
wt[e] = weights[e]
4436
except (IndexError, TypeError, ValueError):
4437
try:
4438
wt = {}
4439
for e in self.groundset():
4440
wt[e] = weights(e)
4441
except (TypeError, ValueError):
4442
raise TypeError("the weights argument does not seem to be a collection of weights for the groundset.")
4443
return self._intersection(other, wt)
4444
4445
cpdef _intersection(self, other, weights):
4446
r"""
4447
Return a maximum-weight common independent.
4448
4449
INPUT:
4450
4451
- ``other`` -- a second matroid with the same groundset as this
4452
matroid.
4453
- ``weights`` -- a dictionary which must specify a weight for each
4454
element of the common groundset.
4455
4456
OUTPUT:
4457
4458
A subset of the groundset.
4459
4460
.. NOTE::
4461
4462
This is the unguarded version of the method
4463
:meth:`<sage.matroids.matroid.Matroid.intersection>`, which does
4464
not test if the input is well-formed.
4465
4466
EXAMPLES::
4467
4468
sage: M = matroids.named_matroids.T12()
4469
sage: N = matroids.named_matroids.ExtendedTernaryGolayCode()
4470
sage: w = {'a':30, 'b':10, 'c':11, 'd':20, 'e':70, 'f':21, 'g':90,
4471
....: 'h':12, 'i':80, 'j':13, 'k':40, 'l':21}
4472
sage: Y = M._intersection(N, w)
4473
sage: sorted(Y)
4474
['a', 'd', 'e', 'g', 'i', 'k']
4475
sage: sum([w[y] for y in Y])
4476
330
4477
"""
4478
Y = set()
4479
U = self._intersection_augmentation(other, weights, Y)
4480
while U is not None and sum([weights[x] for x in U - Y]) > sum([weights[y] for y in U.intersection(Y)]):
4481
Y = Y.symmetric_difference(U)
4482
U = self._intersection_augmentation(other, weights, Y)
4483
return Y
4484
4485
cpdef _intersection_augmentation(self, other, weights, Y):
4486
r"""
4487
Return a an augmenting set for the matroid intersection problem.
4488
4489
INPUT:
4490
4491
- ``other`` -- a matroid with the same ground set as ``self``.
4492
- ``weights`` -- a dictionary specifying a weight for each element of
4493
the ground set
4494
- ``Y`` -- an extremal common independent set of ``self`` and
4495
``other`` of size `k`. That is, a common independent set of maximum
4496
weight among common independent sets of size `k`.
4497
4498
OUTPUT:
4499
4500
A set ``U`` such that the symmetric difference of ``Y`` and ``U``
4501
is extremal and has `k + 1` elements; or ``None``, if there is no
4502
common independent ste of size `k + 1`.
4503
4504
.. NOTE::
4505
4506
This is an unchecked method. In particular, if the given ``Y`` is
4507
not extremal, the algorithm will not terminate. The usage is to
4508
run the algorithm on its own output.
4509
4510
EXAMPLES::
4511
4512
sage: M = matroids.named_matroids.T12()
4513
sage: N = matroids.named_matroids.ExtendedTernaryGolayCode()
4514
sage: w = {'a':30, 'b':10, 'c':11, 'd':20, 'e':70, 'f':21, 'g':90,
4515
....: 'h':12, 'i':80, 'j':13, 'k':40, 'l':21}
4516
sage: Y = M.intersection(N, w)
4517
sage: sorted(Y)
4518
['a', 'd', 'e', 'g', 'i', 'k']
4519
sage: M._intersection_augmentation(N, w, Y) is None
4520
True
4521
"""
4522
X = self.groundset() - Y
4523
4524
X1 = frozenset([x for x in X if self._is_independent(Y.union([x]))])
4525
X2 = frozenset([x for x in X if other._is_independent(Y.union([x]))])
4526
4527
w = {x: -weights[x] for x in X1}
4528
predecessor = {x: None for x in X1}
4529
out_neighbors = {x: set() for x in X2}
4530
4531
todo = set(X1)
4532
next_layer = set()
4533
while todo:
4534
while todo:
4535
u = todo.pop()
4536
m = w[u]
4537
if u in Y:
4538
if u not in out_neighbors:
4539
out_neighbors[u] = X - self._closure(Y - set([u]))
4540
for x in out_neighbors[u]:
4541
m2 = m - weights[x]
4542
if not x in w or w[x] > m2:
4543
predecessor[x] = u
4544
w[x] = m2
4545
next_layer.add(x)
4546
else:
4547
if u not in out_neighbors:
4548
out_neighbors[u] = other._circuit(Y.union([u])) - set([u]) # have to make sure that u is not in X2 for this
4549
for y in out_neighbors[u]:
4550
m2 = m + weights[y]
4551
if not y in w or w[y] > m2:
4552
predecessor[y] = u
4553
w[y] = m2
4554
next_layer.add(y)
4555
todo = next_layer
4556
next_layer = set()
4557
X3 = X2.intersection(w)
4558
if not X3:
4559
return None
4560
s = min([w[x] for x in X3])
4561
for u in X3:
4562
if w[u] == s:
4563
path = set([u])
4564
while predecessor[u] is not None:
4565
u = predecessor[u]
4566
path.add(u)
4567
return path
4568
4569
# invariants
4570
4571
cpdef _internal(self, B):
4572
"""
4573
Return the set of internally active elements of a basis `B`.
4574
4575
An element `e` is *internally active* if it is the smallest element in
4576
the `B`-fundamental cocircuit using `e`. Smallest is interpreted as
4577
the output of the built-in ``min`` function on the subset.
4578
4579
The `B`-fundamental cocircuit using `e` is the unique cocircuit
4580
intersecting basis `B` in exactly element `e`.
4581
4582
INPUT:
4583
4584
- ``B`` -- a basis of the matroid, assumed to have Python's
4585
``frozenset`` interface.
4586
4587
OUTPUT:
4588
4589
A subset of ``B``.
4590
4591
.. SEEALSO::
4592
4593
:meth:`M.tutte_polynomial() <sage.matroids.matroid.Matroid.tutte_polynomial>`
4594
4595
EXAMPLES::
4596
4597
sage: M = matroids.named_matroids.Fano()
4598
sage: sorted(M._internal({'a', 'b', 'c'}))
4599
['a', 'b', 'c']
4600
sage: sorted(M._internal({'e', 'f', 'g'}))
4601
[]
4602
"""
4603
N = self.groundset() - B
4604
A = set()
4605
for e in B:
4606
if min(self._cocircuit(N | set([e]))) == e:
4607
A.add(e)
4608
return A
4609
4610
cpdef _external(self, B):
4611
"""
4612
Return the set of externally active elements of a basis `B`.
4613
4614
An element `e` is *externally active* if it is the smallest element in
4615
the `B`-fundamental circuit using `e`. Smallest is interpreted as
4616
the output of the built-in ``min`` function on the subset.
4617
4618
The `B`-fundamental circuit using `e` is the unique circuit contained
4619
in `B + e`.
4620
4621
INPUT:
4622
4623
- ``B`` -- a basis of the matroid, assumed to have Python's
4624
``frozenset`` interface.
4625
4626
OUTPUT:
4627
4628
A subset of ``self.groundset() - B``.
4629
4630
.. SEEALSO::
4631
4632
:meth:`M.tutte_polynomial() <sage.matroids.matroid.Matroid.tutte_polynomial>`
4633
4634
EXAMPLES::
4635
4636
sage: M = matroids.named_matroids.Fano()
4637
sage: sorted(M._external({'a', 'b', 'c'}))
4638
[]
4639
sage: sorted(M._external({'e', 'f', 'g'}))
4640
['a', 'b', 'c', 'd']
4641
4642
"""
4643
N = self.groundset() - B
4644
A = set()
4645
for e in N:
4646
if min(self._circuit(B | set([e]))) == e:
4647
A.add(e)
4648
return A
4649
4650
cpdef tutte_polynomial(self, x=None, y=None):
4651
"""
4652
Return the Tutte polynomial of the matroid.
4653
4654
The *Tutte polynomial* of a matroid is the polynomial
4655
4656
.. MATH::
4657
4658
T(x, y) = \sum_{A \subseteq E} (x - 1)^{r(E) - r(A)} (y - 1)^{r^*(E) - r^*(E\setminus A)},
4659
4660
where `E` is the groundset of the matroid, `r` is the rank function,
4661
and `r^*` is the corank function. Tutte defined his polynomial
4662
differently:
4663
4664
.. MATH::
4665
4666
T(x, y)=\sum_{B} x^i(B) y^e(B),
4667
4668
where the sum ranges over all bases of the matroid, `i(B)` is the
4669
number of internally active elements of `B`, and `e(B)` is the number
4670
of externally active elements of `B`.
4671
4672
INPUT:
4673
4674
- ``x`` -- (optional) a variable or numerical argument.
4675
- ``y`` -- (optional) a variable or numerical argument.
4676
4677
OUTPUT:
4678
4679
The Tutte-polynomial `T(x, y)`, where `x` and `y` are substituted with
4680
any values provided as input.
4681
4682
.. TODO::
4683
4684
Make implementation more efficient, e.g. generalizing the
4685
approach from :trac:`1314` from graphs to matroids.
4686
4687
EXAMPLES::
4688
4689
sage: M = matroids.named_matroids.Fano()
4690
sage: M.tutte_polynomial()
4691
y^4 + x^3 + 3*y^3 + 4*x^2 + 7*x*y + 6*y^2 + 3*x + 3*y
4692
sage: M.tutte_polynomial(1, 1) == M.bases_count()
4693
True
4694
4695
ALGORITHM:
4696
4697
Enumerate the bases and compute the internal and external activities
4698
for each `B`.
4699
"""
4700
a = x
4701
b = y
4702
R = ZZ['x, y']
4703
x, y = R._first_ngens(2)
4704
T = R(0)
4705
for B in self.bases():
4706
T += x ** len(self._internal(B)) * y ** len(self._external(B))
4707
if a is not None and b is not None:
4708
T = T(a, b)
4709
return T
4710
4711
cpdef flat_cover(self):
4712
"""
4713
Return a minimum-size cover of the nonbases by non-spanning flats.
4714
4715
A *nonbasis* is a subset that has the size of a basis, yet is
4716
dependent. A *flat* is a closed set.
4717
4718
.. SEEALSO::
4719
4720
:meth:`M.nonbases() <sage.matroids.matroid.Matroid.nonbases>`,
4721
:meth:`M.flats() <sage.matroids.matroid.Matroid.flats>`
4722
4723
EXAMPLES::
4724
4725
sage: from sage.matroids.advanced import setprint
4726
sage: M = matroids.named_matroids.Fano()
4727
sage: setprint(M.flat_cover())
4728
[{'a', 'b', 'f'}, {'a', 'c', 'e'}, {'a', 'd', 'g'},
4729
{'b', 'c', 'd'}, {'b', 'e', 'g'}, {'c', 'f', 'g'},
4730
{'d', 'e', 'f'}]
4731
4732
"""
4733
NB = self.nonbases()
4734
if len(NB) == 0:
4735
return []
4736
FF = []
4737
for r in range(self.full_rank()):
4738
FF.extend(self.flats(r))
4739
4740
MIP = MixedIntegerLinearProgram(maximization=False)
4741
f = MIP.new_variable(binary=True)
4742
MIP.set_objective(sum([f[F] for F in FF]))
4743
for N in NB:
4744
MIP.add_constraint(sum([f[F] for F in FF if len(F.intersection(N)) > self.rank(F)]), min=1)
4745
opt = MIP.solve()
4746
4747
fsol = MIP.get_values(f)
4748
eps = 0.00000001
4749
4750
return [F for F in FF if fsol[F] > 1 - eps]
4751
4752