Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/matroids/circuit_closures_matroid.pyx
8817 views
1
r"""
2
Circuit closures matroids
3
4
Matroids are characterized by a list of all tuples `(C, k)`, where `C` is the
5
closure of a circuit, and `k` the rank of `C`. The CircuitClosuresMatroid
6
class implements matroids using this information as data.
7
8
Construction
9
============
10
11
A ``CircuitClosuresMatroid`` can be created from another matroid or from a
12
list of circuit-closures. For a full description of allowed inputs, see
13
:class:`below <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`.
14
It is recommended to use the
15
:func:`Matroid() <sage.matroids.constructor.Matroid>` function for a more
16
flexible construction of a ``CircuitClosuresMatroid``. For direct access to
17
the ``CircuitClosuresMatroid`` constructor, run::
18
19
sage: from sage.matroids.advanced import *
20
21
See also :mod:`sage.matroids.advanced`.
22
23
EXAMPLES::
24
25
sage: from sage.matroids.advanced import *
26
sage: M1 = CircuitClosuresMatroid(groundset='abcdef',
27
....: circuit_closures={2: ['abc', 'ade'], 3: ['abcdef']})
28
sage: M2 = Matroid(circuit_closures={2: ['abc', 'ade'], 3: ['abcdef']})
29
sage: M3 = Matroid(circuit_closures=[(2, 'abc'),
30
....: (3, 'abcdef'), (2, 'ade')])
31
sage: M1 == M2
32
True
33
sage: M1 == M3
34
True
35
36
Note that the class does not implement custom minor and dual operations::
37
38
sage: from sage.matroids.advanced import *
39
sage: M = CircuitClosuresMatroid(groundset='abcdef',
40
....: circuit_closures={2: ['abc', 'ade'], 3: ['abcdef']})
41
sage: isinstance(M.contract('a'), MinorMatroid)
42
True
43
sage: isinstance(M.dual(), DualMatroid)
44
True
45
46
AUTHORS:
47
48
- Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version
49
50
TESTS::
51
52
sage: from sage.matroids.advanced import *
53
sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())
54
sage: TestSuite(M).run()
55
56
Methods
57
=======
58
"""
59
#*****************************************************************************
60
# Copyright (C) 2013 Rudi Pendavingh <[email protected]>
61
# Copyright (C) 2013 Stefan van Zwam <[email protected]>
62
#
63
# Distributed under the terms of the GNU General Public License (GPL)
64
# as published by the Free Software Foundation; either version 2 of
65
# the License, or (at your option) any later version.
66
# http://www.gnu.org/licenses/
67
#*****************************************************************************
68
from matroid cimport Matroid
69
from set_system cimport SetSystem
70
from utilities import setprint_s
71
72
cdef class CircuitClosuresMatroid(Matroid):
73
"""
74
A general matroid `M` is characterized by its rank `r(M)` and the set of
75
pairs
76
77
`(k, \{` closure `(C) : C ` circuit of ` M, r(C)=k\})` for `k=0, .., r(M)-1`
78
79
As each independent set of size `k` is in at most one closure(`C`) of rank
80
`k`, and each closure(`C`) of rank `k` contains at least `k + 1`
81
independent sets of size `k`, there are at most `{n \choose k}/(k + 1)`
82
such closures-of-circuits of rank `k`. Each closure(`C`) takes `O(n)` bits
83
to store, giving an upper bound of `O(2^n)` on the space complexity of the
84
entire matroid.
85
86
A subset `X` of the ground set is independent if and only if
87
88
`| X \cap ` closure `(C) | \leq k` for all circuits `C` of `M` with
89
`r(C)=k`.
90
91
So determining whether a set is independent takes time proportional to the
92
space complexity of the matroid.
93
94
INPUT:
95
96
- ``M`` -- (default: ``None``) an arbitrary matroid.
97
- ``groundset`` -- (default: ``None``) the groundset of a matroid.
98
- ``circuit_closures`` -- (default: ``None``) the collection of circuit
99
closures of a matroid, presented as a dictionary whose keys are ranks,
100
and whose values are sets of circuit closures of the specified rank.
101
102
OUTPUT:
103
104
- If the input is a matroid ``M``, return a ``CircuitClosuresMatroid``
105
instance representing ``M``.
106
- Otherwise, return a ``CircuitClosuresMatroid`` instance based on
107
``groundset`` and ``circuit_closures``.
108
109
.. NOTE::
110
111
For a more flexible means of input, use the ``Matroid()`` function.
112
113
EXAMPLES::
114
115
sage: from sage.matroids.advanced import *
116
sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())
117
sage: M
118
Matroid of rank 3 on 7 elements with circuit-closures
119
{2: {{'b', 'e', 'g'}, {'b', 'c', 'd'}, {'a', 'c', 'e'},
120
{'c', 'f', 'g'}, {'d', 'e', 'f'}, {'a', 'd', 'g'},
121
{'a', 'b', 'f'}}, 3: {{'a', 'b', 'c', 'd', 'e', 'f', 'g'}}}
122
sage: M = CircuitClosuresMatroid(groundset='abcdefgh',
123
....: circuit_closures={3: ['edfg', 'acdg', 'bcfg', 'cefh',
124
....: 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'],
125
....: 4: ['abcdefgh']})
126
sage: M.equals(matroids.named_matroids.P8())
127
True
128
"""
129
130
# NECESSARY
131
def __init__(self, M=None, groundset=None, circuit_closures=None):
132
"""
133
Initialization of the matroid. See class docstring for full
134
documentation.
135
136
EXAMPLES::
137
138
sage: from sage.matroids.advanced import *
139
sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())
140
sage: M
141
Matroid of rank 3 on 7 elements with circuit-closures
142
{2: {{'b', 'e', 'g'}, {'b', 'c', 'd'}, {'a', 'c', 'e'},
143
{'c', 'f', 'g'}, {'d', 'e', 'f'}, {'a', 'd', 'g'},
144
{'a', 'b', 'f'}}, 3: {{'a', 'b', 'c', 'd', 'e', 'f', 'g'}}}
145
146
sage: M = CircuitClosuresMatroid(groundset='abcdefgh',
147
....: circuit_closures={3: ['edfg', 'acdg', 'bcfg', 'cefh',
148
....: 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'],
149
....: 4: ['abcdefgh']})
150
sage: M.equals(matroids.named_matroids.P8())
151
True
152
"""
153
if M is not None:
154
self._groundset = M.groundset()
155
self._circuit_closures = M.circuit_closures()
156
else:
157
self._groundset = frozenset(groundset)
158
self._circuit_closures = {}
159
for k in circuit_closures.iterkeys():
160
self._circuit_closures[k] = frozenset([frozenset(X) for X in circuit_closures[k]])
161
self._matroid_rank = self.rank(self._groundset)
162
163
cpdef groundset(self):
164
"""
165
Return the groundset of the matroid.
166
167
The groundset is the set of elements that comprise the matroid.
168
169
OUTPUT:
170
171
A set.
172
173
EXAMPLES::
174
175
sage: M = matroids.named_matroids.Pappus()
176
sage: sorted(M.groundset())
177
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
178
"""
179
return frozenset(self._groundset)
180
181
cpdef _rank(self, X):
182
"""
183
Return the rank of a set ``X``.
184
185
This method does no checking on ``X``, and
186
``X`` may be assumed to have the same interface as ``frozenset``.
187
188
INPUT:
189
190
- ``X`` -- an object with Python's ``frozenset`` interface.
191
192
OUTPUT:
193
194
The rank of ``X`` in the matroid.
195
196
EXAMPLES::
197
198
sage: M = matroids.named_matroids.NonPappus()
199
sage: M._rank('abc')
200
2
201
"""
202
return len(self._max_independent(X))
203
204
# OPTIONAL, OPTIMIZED FOR THIS CLASS
205
cpdef full_rank(self):
206
r"""
207
Return the rank of the matroid.
208
209
The *rank* of the matroid is the size of the largest independent
210
subset of the groundset.
211
212
OUTPUT:
213
214
Integer.
215
216
EXAMPLES::
217
218
sage: M = matroids.named_matroids.Vamos()
219
sage: M.full_rank()
220
4
221
sage: M.dual().full_rank()
222
4
223
"""
224
return self._matroid_rank
225
226
cpdef _is_independent(self, F):
227
"""
228
Test if input is independent.
229
230
INPUT:
231
232
- ``X`` -- An object with Python's ``frozenset`` interface containing
233
a subset of ``self.groundset()``.
234
235
OUTPUT:
236
237
Boolean.
238
239
EXAMPLES::
240
241
sage: M = matroids.named_matroids.Vamos()
242
sage: M._is_independent(set(['a', 'b', 'c']))
243
True
244
sage: M._is_independent(set(['a', 'b', 'c', 'd']))
245
False
246
247
"""
248
for r in sorted(self._circuit_closures.keys()):
249
if len(F) <= r:
250
break
251
for C in self._circuit_closures[r]:
252
S = F & C
253
if(len(S) > r):
254
return False
255
return True
256
257
cpdef _max_independent(self, F):
258
"""
259
Compute a maximal independent subset.
260
261
INPUT:
262
263
- ``X`` -- An object with Python's ``frozenset`` interface containing
264
a subset of ``self.groundset()``.
265
266
OUTPUT:
267
268
A maximal independent subset of ``X``.
269
270
EXAMPLES::
271
272
sage: M = matroids.named_matroids.Vamos()
273
sage: sorted(M._max_independent(set(['a', 'c', 'd', 'e', 'f'])))
274
['a', 'd', 'e', 'f']
275
276
"""
277
I = set(F)
278
for r in sorted(self._circuit_closures.keys()):
279
if len(I) == 0:
280
break
281
for C in self._circuit_closures[r]:
282
if len(I) == 0:
283
break
284
S = I & C
285
while(len(S) > r):
286
I.discard(S.pop())
287
288
return frozenset(I)
289
290
cpdef _circuit(self, F):
291
"""
292
Return a minimal dependent subset.
293
294
INPUT:
295
296
- ``X`` -- An object with Python's ``frozenset`` interface containing
297
a subset of ``self.groundset()``.
298
299
OUTPUT:
300
301
A circuit contained in ``X``, if it exists. Otherwise an error is
302
raised.
303
304
EXAMPLES::
305
306
sage: M = matroids.named_matroids.Vamos()
307
sage: sorted(M._circuit(set(['a', 'c', 'd', 'e', 'f'])))
308
['c', 'd', 'e', 'f']
309
sage: sorted(M._circuit(set(['a', 'c', 'd'])))
310
Traceback (most recent call last):
311
...
312
ValueError: no circuit in independent set.
313
314
"""
315
for r in sorted(self._circuit_closures.keys()):
316
for C in self._circuit_closures[r]:
317
S = set(F & C)
318
if(len(S) > r):
319
while len(S) > r + 1:
320
S.pop()
321
return frozenset(S)
322
raise ValueError("no circuit in independent set.")
323
324
cpdef circuit_closures(self):
325
"""
326
Return the list of closures of circuits of the matroid.
327
328
A *circuit closure* is a closed set containing a circuit.
329
330
OUTPUT:
331
332
A dictionary containing the circuit closures of the matroid, indexed
333
by their ranks.
334
335
.. SEEALSO::
336
337
:meth:`Matroid.circuit() <sage.matroids.matroid.Matroid.circuit>`,
338
:meth:`Matroid.closure() <sage.matroids.matroid.Matroid.closure>`
339
340
EXAMPLES::
341
342
sage: from sage.matroids.advanced import *
343
sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())
344
sage: CC = M.circuit_closures()
345
sage: len(CC[2])
346
7
347
sage: len(CC[3])
348
1
349
sage: len(CC[1])
350
Traceback (most recent call last):
351
...
352
KeyError: 1
353
sage: [sorted(X) for X in CC[3]]
354
[['a', 'b', 'c', 'd', 'e', 'f', 'g']]
355
"""
356
return self._circuit_closures
357
358
cpdef _is_isomorphic(self, other):
359
"""
360
Test if ``self`` is isomorphic to ``other``.
361
362
Internal version that performs no checks on input.
363
364
INPUT:
365
366
- ``other`` -- A matroid.
367
368
OUTPUT:
369
370
Boolean.
371
372
EXAMPLES::
373
374
sage: from sage.matroids.advanced import *
375
sage: M1 = CircuitClosuresMatroid(matroids.Wheel(3))
376
sage: M2 = matroids.CompleteGraphic(4)
377
sage: M1._is_isomorphic(M2)
378
True
379
sage: M1 = CircuitClosuresMatroid(matroids.named_matroids.Fano())
380
sage: M2 = matroids.named_matroids.NonFano()
381
sage: M1._is_isomorphic(M2)
382
False
383
384
"""
385
N = CircuitClosuresMatroid(other)
386
if sorted(self._circuit_closures.keys()) != sorted(N._circuit_closures.keys()):
387
return False
388
SM = SetSystem(list(self.groundset()))
389
for r in self._circuit_closures:
390
for C in self._circuit_closures[r]:
391
SM.append(C)
392
SN = SetSystem(list(N.groundset()))
393
for r in N._circuit_closures:
394
for C in N._circuit_closures[r]:
395
SN.append(C)
396
return SM._isomorphism(SN) is not None
397
398
# REPRESENTATION
399
def _repr_(self):
400
"""
401
Return a string representation of the matroid.
402
403
EXAMPLES::
404
405
sage: M = matroids.named_matroids.Vamos()
406
sage: print(M._repr_())
407
Matroid of rank 4 on 8 elements with circuit-closures
408
{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},
409
{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'},
410
{'c', 'd', 'e', 'f'}},
411
4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}
412
"""
413
return Matroid._repr_(self) + " with circuit-closures\n" + setprint_s(self._circuit_closures)
414
415
# COMPARISON
416
417
def __hash__(self):
418
r"""
419
Return an invariant of the matroid.
420
421
This function is called when matroids are added to a set. It is very
422
desirable to override it so it can distinguish matroids on the same
423
groundset, which is a very typical use case!
424
425
.. WARNING::
426
427
This method is linked to __richcmp__ (in Cython) and __cmp__ or
428
__eq__/__ne__ (in Python). If you override one, you should
429
(and in Cython: MUST) override the other!
430
431
EXAMPLES::
432
433
sage: M = matroids.named_matroids.Vamos()
434
sage: N = matroids.named_matroids.Vamos()
435
sage: hash(M) == hash(N)
436
True
437
sage: O = matroids.named_matroids.NonVamos()
438
sage: hash(M) == hash(O)
439
False
440
"""
441
return hash(tuple([self.groundset(), tuple([(r, len(self._circuit_closures[r])) for r in sorted(self._circuit_closures.keys())])]))
442
443
def __richcmp__(left, right, int op):
444
r"""
445
Compare two matroids.
446
447
We take a very restricted view on equality: the objects need to be of
448
the exact same type (so no subclassing) and the internal data need to
449
be the same. For BasisMatroids, this means that the groundsets and the
450
sets of bases of the two matroids are equal.
451
452
EXAMPLES::
453
454
sage: M = matroids.named_matroids.Pappus()
455
sage: N = matroids.named_matroids.NonPappus()
456
sage: M == N
457
False
458
sage: N = Matroid(M.bases())
459
sage: M == N
460
False
461
"""
462
cdef CircuitClosuresMatroid lt, rt
463
if op in [0, 1, 4, 5]: # <, <=, >, >=
464
return NotImplemented
465
if not isinstance(left, CircuitClosuresMatroid) or not isinstance(right, CircuitClosuresMatroid):
466
return NotImplemented
467
lt = <CircuitClosuresMatroid> left
468
rt = <CircuitClosuresMatroid> right
469
if op == 2: # ==
470
res = True
471
if op == 3: # !=
472
res = False
473
# res gets inverted if matroids are deemed different.
474
if lt.groundset() != rt.groundset():
475
return not res
476
if lt.full_rank() != rt.full_rank():
477
return not res
478
if lt._circuit_closures == rt._circuit_closures:
479
return res
480
return not res
481
482
# COPYING, LOADING, SAVING
483
484
def __copy__(self):
485
"""
486
Create a shallow copy.
487
488
EXAMPLES::
489
490
sage: M = matroids.named_matroids.Vamos()
491
sage: N = copy(M) # indirect doctest
492
sage: M == N
493
True
494
sage: M.groundset() is N.groundset()
495
True
496
497
"""
498
N = CircuitClosuresMatroid(groundset=[], circuit_closures={})
499
N._groundset = self._groundset
500
N._circuit_closures = self._circuit_closures
501
N._matroid_rank = self._matroid_rank
502
if getattr(self, '__custom_name') is not None: # because of name wrangling, this is not caught by the default copy
503
N.rename(getattr(self, '__custom_name'))
504
return N
505
506
def __deepcopy__(self, memo={}):
507
"""
508
Create a deep copy.
509
510
.. NOTE::
511
512
Since matroids are immutable, a shallow copy normally suffices.
513
514
EXAMPLES::
515
516
sage: M = matroids.named_matroids.Vamos()
517
sage: N = deepcopy(M) # indirect doctest
518
sage: M == N
519
True
520
sage: M.groundset() is N.groundset()
521
False
522
"""
523
from copy import deepcopy
524
# Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion.
525
N = CircuitClosuresMatroid(groundset=deepcopy(self._groundset, memo), circuit_closures=deepcopy(self._circuit_closures, memo))
526
if getattr(self, '__custom_name') is not None: # because of name wrangling, this is not caught by the default deepcopy
527
N.rename(deepcopy(getattr(self, '__custom_name'), memo))
528
return N
529
530
def __reduce__(self):
531
"""
532
Save the matroid for later reloading.
533
534
OUTPUT:
535
536
A tuple ``(unpickle, (version, data))``, where ``unpickle`` is the
537
name of a function that, when called with ``(version, data)``,
538
produces a matroid isomorphic to ``self``. ``version`` is an integer
539
(currently 0) and ``data`` is a tuple ``(E, CC, name)`` where ``E`` is
540
the groundset, ``CC`` is the dictionary of circuit closures, and
541
``name`` is a custom name.
542
543
EXAMPLES::
544
545
sage: M = matroids.named_matroids.Vamos()
546
sage: M == loads(dumps(M)) # indirect doctest
547
True
548
sage: M.reset_name()
549
sage: loads(dumps(M))
550
Matroid of rank 4 on 8 elements with circuit-closures
551
{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},
552
{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'},
553
{'c', 'd', 'e', 'f'}},
554
4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}
555
556
"""
557
import sage.matroids.unpickling
558
data = (self._groundset, self._circuit_closures, getattr(self, '__custom_name'))
559
version = 0
560
return sage.matroids.unpickling.unpickle_circuit_closures_matroid, (version, data)
561
562
# todo: customized minor, extend methods.
563
564