Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/matroids/linear_matroid.pyx
8817 views
1
r"""
2
Linear matroids
3
4
When `A` is an `r` times `E` matrix, the linear matroid `M[A]` has ground set
5
`E` and, for independent sets, all `F` subset of `E` such that the columns of
6
`M[A]` indexed by `F` are linearly independent.
7
8
Construction
9
============
10
11
The recommended way to create a linear matroid is by using the
12
:func:`Matroid() <sage.matroids.constructor.Matroid>` function, with a
13
representation matrix `A` as input. This function will intelligently choose
14
one of the dedicated classes :class:`BinaryMatroid`, :class:`TernaryMatroid`,
15
:class:`QuaternaryMatroid`, :class:`RegularMatroid` when appropriate. However,
16
invoking the classes directly is possible too. To get access to them, type::
17
18
sage: from sage.matroids.advanced import *
19
20
See also :mod:`sage.matroids.advanced`. In both cases, it is possible to
21
provide a reduced matrix `B`, to create the matroid induced by `A = [ I B ]`::
22
23
sage: from sage.matroids.advanced import *
24
sage: A = Matrix(GF(2), [[1, 0, 0, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 1],
25
....: [0, 0, 1, 0, 1, 1, 1]])
26
sage: B = Matrix(GF(2), [[1, 1, 0, 1], [1, 0, 1, 1], [0, 1, 1, 1]])
27
sage: M1 = Matroid(A)
28
sage: M2 = LinearMatroid(A)
29
sage: M3 = BinaryMatroid(A)
30
sage: M4 = Matroid(reduced_matrix=B)
31
sage: M5 = LinearMatroid(reduced_matrix=B)
32
sage: isinstance(M1, BinaryMatroid)
33
True
34
sage: M1.equals(M2)
35
True
36
sage: M1.equals(M3)
37
True
38
sage: M1 == M4
39
True
40
sage: M1.is_field_isomorphic(M5)
41
True
42
sage: M2 == M3 # comparing LinearMatroid and BinaryMatroid always yields False
43
False
44
45
Class methods
46
=============
47
48
The ``LinearMatroid`` class and its derivatives inherit all methods from the
49
:mod:`Matroid <sage.matroids.matroid>` and
50
:mod:`BasisExchangeMatroid <sage.matroids.basis_exchange_matroid>` classes.
51
See the documentation for these classes for an overview. In addition, the
52
following methods are available:
53
54
- :class:`LinearMatroid`
55
56
- :func:`base_ring() <sage.matroids.linear_matroid.LinearMatroid.base_ring>`
57
- :func:`characteristic() <sage.matroids.linear_matroid.LinearMatroid.characteristic>`
58
- :func:`representation() <sage.matroids.linear_matroid.LinearMatroid.representation>`
59
- :func:`representation_vectors() <sage.matroids.linear_matroid.LinearMatroid.representation_vectors>`
60
- :func:`is_field_equivalent() <sage.matroids.linear_matroid.LinearMatroid.is_field_equivalent>`
61
- :func:`is_field_isomorphism() <sage.matroids.linear_matroid.LinearMatroid.is_field_isomorphism>`
62
- :func:`has_field_minor() <sage.matroids.linear_matroid.LinearMatroid.has_field_minor>`
63
- :func:`fundamental_cycle() <sage.matroids.linear_matroid.LinearMatroid.fundamental_cycle>`
64
- :func:`fundamental_cocycle() <sage.matroids.linear_matroid.LinearMatroid.fundamental_cocycle>`
65
- :func:`cross_ratios() <sage.matroids.linear_matroid.LinearMatroid.cross_ratios>`
66
- :func:`cross_ratio() <sage.matroids.linear_matroid.LinearMatroid.cross_ratio>`
67
68
- :func:`linear_extension() <sage.matroids.linear_matroid.LinearMatroid.linear_extension>`
69
- :func:`linear_coextension() <sage.matroids.linear_matroid.LinearMatroid.linear_coextension>`
70
- :func:`linear_extension_chains() <sage.matroids.linear_matroid.LinearMatroid.linear_extension_chains>`
71
- :func:`linear_coextension_cochains() <sage.matroids.linear_matroid.LinearMatroid.linear_coextension_cochains>`
72
- :func:`linear_extensions() <sage.matroids.linear_matroid.LinearMatroid.linear_extensions>`
73
- :func:`linear_coextensions() <sage.matroids.linear_matroid.LinearMatroid.linear_coextensions>`
74
75
- :class:`BinaryMatroid` has all of the :class:`LinearMatroid` ones, and
76
77
- :func:`bicycle_dimension() <sage.matroids.linear_matroid.BinaryMatroid.bicycle_dimension>`
78
- :func:`brown_invariant() <sage.matroids.linear_matroid.BinaryMatroid.brown_invariant>`
79
- :func:`is_graphic() <sage.matroids.linear_matroid.BinaryMatroid.is_graphic>`
80
81
- :class:`TernaryMatroid` has all of the :class:`LinearMatroid` ones, and
82
83
- :func:`bicycle_dimension() <sage.matroids.linear_matroid.TernaryMatroid.bicycle_dimension>`
84
- :func:`character() <sage.matroids.linear_matroid.TernaryMatroid.character>`
85
86
- :class:`QuaternaryMatroid` has all of the :class:`LinearMatroid` ones, and
87
88
- :func:`bicycle_dimension() <sage.matroids.linear_matroid.QuaternaryMatroid.bicycle_dimension>`
89
90
- :class:`RegularMatroid` has all of the :class:`LinearMatroid` ones, and
91
92
- :func:`is_graphic() <sage.matroids.linear_matroid.RegularMatroid.is_graphic>`
93
94
AUTHORS:
95
96
- Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version
97
98
Methods
99
=======
100
"""
101
#*****************************************************************************
102
# Copyright (C) 2013 Rudi Pendavingh <[email protected]>
103
# Copyright (C) 2013 Stefan van Zwam <[email protected]>
104
#
105
#
106
# Distributed under the terms of the GNU General Public License (GPL)
107
# as published by the Free Software Foundation; either version 2 of
108
# the License, or (at your option) any later version.
109
# http://www.gnu.org/licenses/
110
#*****************************************************************************
111
include 'sage/misc/bitset.pxi'
112
113
from sage.matroids.matroid cimport Matroid
114
from basis_exchange_matroid cimport BasisExchangeMatroid
115
from lean_matrix cimport LeanMatrix, GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, IntegerMatrix, generic_identity
116
from set_system cimport SetSystem
117
from utilities import newlabel
118
119
from sage.matrix.matrix2 cimport Matrix
120
import sage.matrix.constructor
121
from copy import copy, deepcopy
122
from sage.rings.all import ZZ, QQ, FiniteField, GF
123
import itertools
124
from itertools import combinations
125
126
cdef bint GF2_not_defined = True
127
cdef GF2, GF2_one, GF2_zero
128
129
cdef bint GF3_not_defined = True
130
cdef GF3, GF3_one, GF3_zero, GF3_minus_one
131
132
# Implementation note: internally we use data structures from lean_matrix
133
# instead of Sage's standard Matrix datatypes. This was done so we can use
134
# highly optimized methods in critical places. Our hope is to do away with
135
# this in the future. To do so, the following needs to be done:
136
# 1. Modify the ``__init__`` methods (the lean_matrix constructors are
137
# incompatible with Sage's matrix constructors)
138
# 2. Look for all lines saying ``# Not a Sage matrix operation`` and provide
139
# alternative implementations
140
# 3. Look for all lines saying ``# Deprecated Sage matrix operation`` and
141
# provide alternative implementations
142
# Below is some code, commented out currently, to get you going.
143
144
cdef inline gauss_jordan_reduce(LeanMatrix A, columns):
145
return A.gauss_jordan_reduce(columns) # Not a Sage matrix operation
146
147
cdef inline characteristic(LeanMatrix A):
148
return A.characteristic() # Not a Sage matrix operation
149
150
# Implementation using default Sage matrices
151
152
# cdef gauss_jordan_reduce(Matrix A, columns):
153
# """
154
# Row-reduce so the lexicographically first basis indexes an identity submatrix.
155
# """
156
# cdef long r = 0
157
# cdef list P = []
158
# cdef long c, p, row
159
# for c in columns:
160
# is_pivot = False
161
# for row in xrange(r, A.nrows()):
162
# if A.get_unsafe(row, c) != 0:
163
# is_pivot = True
164
# p = row
165
# break
166
# if is_pivot:
167
# A.swap_rows_c(p, r)
168
# A.rescale_row_c(r, A.get_unsafe(r, c) ** (-1), 0)
169
# for row in xrange(A.nrows()):
170
# if row != r and A.get_unsafe(row, c) != 0:
171
# A.add_multiple_of_row_c(row, r, -A.get_unsafe(row, c), 0)
172
# P.append(c)
173
# r += 1
174
# if r == A.nrows():
175
# break
176
# return P
177
#
178
# cdef inline characteristic(LeanMatrix A):
179
# # TODO: use caching for increased speed
180
# return A.base_ring().characteristic()
181
182
cdef class LinearMatroid(BasisExchangeMatroid):
183
r"""
184
Linear matroids.
185
186
When `A` is an `r` times `E` matrix, the linear matroid `M[A]` has ground
187
set `E` and set of independent sets
188
189
`I(A) =\{F \subseteq E :` the columns of `A` indexed by `F` are linearly independent `\}`
190
191
The simplest way to create a LinearMatroid is by giving only a matrix `A`.
192
Then, the groundset defaults to ``range(A.ncols())``. Any iterable object
193
``E`` can be given as a groundset. If ``E`` is a list, then ``E[i]`` will
194
label the `i`-th column of `A`. Another possibility is to specify a
195
*reduced* matrix `B`, to create the matroid induced by `A = [ I B ]`.
196
197
INPUT:
198
199
- ``matrix`` -- (default: ``None``) a matrix whose column vectors
200
represent the matroid.
201
- ``reduced_matrix`` -- (default: ``None``) a matrix `B` such that
202
`[I\ \ B]` represents the matroid, where `I` is an identity matrix with
203
the same number of rows as `B`. Only one of ``matrix`` and
204
``reduced_matrix`` should be provided.
205
- ``groundset`` -- (default: ``None``) an iterable containing the element
206
labels. When provided, must have the correct number of elements: the
207
number of columns of ``matrix`` or the number of rows plus the number
208
of columns of ``reduced_matrix``.
209
- ``ring`` -- (default: ``None``) the desired base ring of the matrix. If
210
the base ring is different, an attempt will be made to create a new
211
matrix with the correct base ring.
212
- ``keep_initial_representation`` -- (default: ``True``) decides whether
213
or not an internal copy of the input matrix should be preserved. This
214
can help to see the structure of the matroid (e.g. in the case of
215
graphic matroids), and makes it easier to look at extensions. However,
216
the input matrix may have redundant rows, and sometimes it is desirable
217
to store only a row-reduced copy.
218
219
OUTPUT:
220
221
A ``LinearMatroid`` instance based on the data above.
222
223
.. NOTE::
224
225
The recommended way to generate a linear matroid is through the
226
:func:`Matroid() <sage.matroids.constructor.Matroid>` function. It
227
will automatically choose more optimized classes when present
228
(currently :class:`BinaryMatroid`, :class:`TernaryMatroid`,
229
:class:`QuaternaryMatroid`, :class:`RegularMatroid`). For direct
230
access to the ``LinearMatroid`` constructor, run::
231
232
sage: from sage.matroids.advanced import *
233
234
EXAMPLES::
235
236
sage: from sage.matroids.advanced import *
237
sage: A = Matrix(GF(3), 2, 4, [[1, 0, 1, 1], [0, 1, 1, 2]])
238
sage: M = LinearMatroid(A)
239
sage: M
240
Linear matroid of rank 2 on 4 elements represented over the Finite
241
Field of size 3
242
sage: sorted(M.groundset())
243
[0, 1, 2, 3]
244
sage: Matrix(M)
245
[1 0 1 1]
246
[0 1 1 2]
247
sage: M = LinearMatroid(A, 'abcd')
248
sage: sorted(M.groundset())
249
['a', 'b', 'c', 'd']
250
sage: B = Matrix(GF(3), 2, 2, [[1, 1], [1, 2]])
251
sage: N = LinearMatroid(reduced_matrix=B, groundset='abcd')
252
sage: M == N
253
True
254
"""
255
def __init__(self, matrix=None, groundset=None, reduced_matrix=None, ring=None, keep_initial_representation=True):
256
"""
257
See class definition for full documentation.
258
259
EXAMPLES::
260
261
sage: from sage.matroids.advanced import *
262
sage: LinearMatroid(matrix=Matrix(GF(5), [[1, 0, 1, 1, 1],
263
....: [0, 1, 1, 2, 3]])) # indirect doctest
264
Linear matroid of rank 2 on 5 elements represented over the Finite
265
Field of size 5
266
"""
267
basis = self._setup_internal_representation(matrix, reduced_matrix, ring, keep_initial_representation)
268
if groundset is None:
269
groundset = range(self._A.nrows() + self._A.ncols())
270
else:
271
if len(groundset) != self._A.nrows() + self._A.ncols():
272
raise ValueError("size of groundset does not match size of matrix")
273
BasisExchangeMatroid.__init__(self, groundset, [groundset[i] for i in basis])
274
self._zero = self._A.base_ring()(0)
275
self._one = self._A.base_ring()(1)
276
277
def __dealloc__(self):
278
"""
279
Deallocate the memory.
280
281
EXAMPLES::
282
283
sage: from sage.matroids.advanced import *
284
sage: M = LinearMatroid(matrix=Matrix(GF(5), [[1, 0, 1, 1, 1],
285
....: [0, 1, 1, 2, 3]])) # indirect doctest
286
sage: M = None
287
"""
288
if self._prow is not NULL:
289
sage_free(self._prow)
290
self._prow = NULL
291
292
cdef list _setup_internal_representation(self, matrix, reduced_matrix, ring, keep_initial_representation):
293
"""
294
Setup the internal representation matrix ``self._A`` and the array of row- and column indices ``self._prow``.
295
296
Return the displayed basis.
297
"""
298
cdef LeanMatrix A
299
cdef long r, c
300
cdef list P
301
if matrix is not None:
302
reduced = False
303
if not isinstance(matrix, LeanMatrix):
304
A = GenericMatrix(matrix.nrows(), matrix.ncols(), M=matrix, ring=ring)
305
else:
306
A = (<LeanMatrix>matrix).copy() # Deprecated Sage matrix operation
307
if keep_initial_representation:
308
self._representation = A.copy() # Deprecated Sage matrix operation
309
P = gauss_jordan_reduce(A, xrange(A.ncols()))
310
self._A = A.matrix_from_rows_and_columns(range(len(P)), [c for c in xrange(matrix.ncols()) if not c in P])
311
else:
312
reduced = True
313
if not isinstance(reduced_matrix, LeanMatrix):
314
self._A = GenericMatrix(reduced_matrix.nrows(), reduced_matrix.ncols(), M=reduced_matrix, ring=ring)
315
else:
316
self._A = (<LeanMatrix>reduced_matrix).copy() # Deprecated Sage matrix operation
317
P = range(self._A.nrows())
318
self._prow = <long* > sage_malloc((self._A.nrows() + self._A.ncols()) * sizeof(long))
319
if matrix is not None:
320
for r in xrange(len(P)):
321
self._prow[P[r]] = r
322
r = 0
323
for c in xrange(A.ncols()):
324
if c not in P:
325
self._prow[c] = r
326
r += 1
327
else:
328
for r from 0 <= r < self._A.nrows():
329
self._prow[r] = r
330
for r from 0 <= r < self._A.ncols():
331
self._prow[self._A.nrows() + r] = r
332
return P
333
334
cpdef _forget(self):
335
"""
336
Remove the internal representation matrix.
337
338
When calling ``Matrix(M)`` after this, the lexicographically first
339
basis will be used for the identity matrix.
340
341
EXAMPLES::
342
343
sage: from sage.matroids.advanced import *
344
sage: M = LinearMatroid(matrix=Matrix(GF(5), [[1, 1, 0, 1, 1],
345
....: [0, 1, 1, 2, 3]]))
346
sage: A = Matrix(M)
347
sage: M._forget()
348
sage: A == Matrix(M)
349
False
350
"""
351
self._representation = None
352
353
cpdef base_ring(self):
354
"""
355
Return the base ring of the matrix representing the matroid.
356
357
EXAMPLES::
358
359
sage: M = Matroid(matrix=Matrix(GF(5), [[1, 0, 1, 1, 1],
360
....: [0, 1, 1, 2, 3]]))
361
sage: M.base_ring()
362
Finite Field of size 5
363
"""
364
return self._A.base_ring()
365
366
cpdef characteristic(self):
367
"""
368
Return the characteristic of the base ring of the matrix representing
369
the matroid.
370
371
EXAMPLES::
372
373
sage: M = Matroid(matrix=Matrix(GF(5), [[1, 0, 1, 1, 1],
374
....: [0, 1, 1, 2, 3]]))
375
sage: M.characteristic()
376
5
377
"""
378
return characteristic(self._A)
379
380
cdef bint __is_exchange_pair(self, long x, long y):
381
r"""
382
Check if ``self.basis() - x + y`` is again a basis. Internal method.
383
"""
384
return self._A.is_nonzero(self._prow[x], self._prow[y]) # Not a Sage matrix operation
385
386
cdef bint __exchange(self, long x, long y):
387
"""
388
Put element indexed by ``x`` into basis, taking out element ``y``.
389
Assumptions are that this is a valid basis exchange.
390
391
.. NOTE::
392
393
Safe for noncommutative rings.
394
"""
395
cdef long px, py, r
396
px = self._prow[x]
397
py = self._prow[y]
398
piv = self._A.get_unsafe(px, py)
399
pivi = piv ** (-1)
400
self._A.rescale_row_c(px, pivi, 0)
401
self._A.set_unsafe(px, py, pivi + self._one) # pivoting without column scaling. Add extra so column does not need adjusting
402
for r in xrange(self._A.nrows()): # if A and A' are the matrices before and after pivoting, then
403
a = self._A.get_unsafe(r, py) # ker[I A] equals ker[I A'] except for the labelling of the columns
404
if a and r != px:
405
self._A.add_multiple_of_row_c(r, px, -a, 0)
406
self._A.set_unsafe(px, py, pivi)
407
self._prow[y] = px
408
self._prow[x] = py
409
BasisExchangeMatroid.__exchange(self, x, y)
410
411
cdef __exchange_value(self, long x, long y):
412
r"""
413
Return the (x, y) entry of the current representation.
414
"""
415
return self._A.get_unsafe(self._prow[x], self._prow[y])
416
417
# Sage functions
418
419
def _matrix_(self):
420
"""
421
Return a matrix representation of ``self``.
422
423
OUTPUT:
424
425
A matrix. Either this matrix is equal to the one originally supplied
426
by the user, or its displayed basis is the lexicographically least
427
basis of the matroid.
428
429
EXAMPLES::
430
431
sage: M = Matroid(matrix=Matrix(GF(5), [[1, 1, 0, 1, 1],
432
....: [0, 1, 1, 2, 3]]))
433
sage: M._matrix_()
434
[1 1 0 1 1]
435
[0 1 1 2 3]
436
sage: M._forget()
437
sage: M._matrix_()
438
[1 0 4 4 3]
439
[0 1 1 2 3]
440
"""
441
return self.representation()
442
443
def _repr_(self):
444
"""
445
Return a string representation of ``self``.
446
447
EXAMPLES::
448
449
sage: M = Matroid(matrix=Matrix(GF(5), [[1, 1, 0, 1, 1],
450
....: [0, 1, 1, 2, 3]]))
451
sage: repr(M) # indirect doctest
452
'Linear matroid of rank 2 on 5 elements represented over the
453
Finite Field of size 5'
454
"""
455
S = "Linear matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements represented over the " + repr(self.base_ring())
456
return S
457
458
# representations
459
460
cpdef representation(self, B=None, reduced=False, labels=None, order=None):
461
"""
462
Return a matrix representing the matroid.
463
464
Let `M` be a matroid on `n` elements with rank `r`. Let `E` be an
465
ordering of the groundset, as output by
466
:func:`M.groundset_list() <sage.matroids.basis_exchange_matroid.BasisExchangeMatroid.groundset_list>`.
467
A *representation* of the matroid is an `r \times n` matrix with the
468
following property. Consider column ``i`` to be labeled by ``E[i]``,
469
and denote by `A[F]` the submatrix formed by the columns labeled by
470
the subset `F \subseteq E`. Then for all `F \subseteq E`, the columns
471
of `A[F]` are linearly independent if and only if `F` is an
472
independent set in the matroid.
473
474
A *reduced representation* is a matrix `D` such that `[I\ \ D]` is a
475
representation of the matroid, where `I` is an `r \times r` identity
476
matrix. In this case, the rows of `D` are considered to be labeled by
477
the first `r` elements of the list ``E``, and the columns by the
478
remaining `n - r` elements.
479
480
INPUT:
481
482
- ``B`` -- (default: ``None``) a subset of elements. When provided,
483
the representation is such that a basis `B'` that maximally
484
intersects `B` is an identity matrix.
485
- ``reduced`` -- (default: ``False``) when ``True``, return a reduced
486
matrix `D` (so `[I\ \ D]` is a representation of the matroid).
487
Otherwise return a full representation matrix.
488
- ``labels`` -- (default: ``None``) when ``True``, return additionally
489
a list of column labels (if ``reduced=False``) or a list of row
490
labels and a list of column labels (if ``reduced=True``).
491
The default setting, ``None``, will not return the labels for a full
492
matrix, but will return the labels for a reduced matrix.
493
- ``order`` -- (default: ``None``) an ordering of the groundset
494
elements. If provided, the columns (and, in case of a reduced
495
representation, rows) will be presented in the given order.
496
497
OUTPUT:
498
499
- ``A`` -- a full or reduced representation matrix of ``self``; or
500
- ``(A, E)`` -- a full representation matrix ``A`` and a list ``E``
501
of column labels; or
502
- ``(A, R, C)`` -- a reduced representation matrix and a list ``R`` of
503
row labels and a list ``C`` of column labels.
504
505
If ``B == None`` and ``reduced == False`` and ``order == None`` then
506
this method will always output the same matrix (except when
507
``M._forget()`` is called): either the matrix used as input to create
508
the matroid, or a matrix in which the lexicographically least basis
509
corresponds to an identity. If only ``order`` is not ``None``, the
510
columns of this matrix will be permuted accordingly.
511
512
.. NOTE::
513
514
A shortcut for ``M.representation()`` is ``Matrix(M)``.
515
516
EXAMPLES::
517
518
sage: M = matroids.named_matroids.Fano()
519
sage: M.representation()
520
[1 0 0 0 1 1 1]
521
[0 1 0 1 0 1 1]
522
[0 0 1 1 1 0 1]
523
sage: Matrix(M) == M.representation()
524
True
525
sage: M.representation(labels=True)
526
(
527
[1 0 0 0 1 1 1]
528
[0 1 0 1 0 1 1]
529
[0 0 1 1 1 0 1], ['a', 'b', 'c', 'd', 'e', 'f', 'g']
530
)
531
sage: M.representation(B='efg')
532
[1 1 0 1 1 0 0]
533
[1 0 1 1 0 1 0]
534
[1 1 1 0 0 0 1]
535
sage: M.representation(B='efg', order='efgabcd')
536
[1 0 0 1 1 0 1]
537
[0 1 0 1 0 1 1]
538
[0 0 1 1 1 1 0]
539
sage: M.representation(B='abc', reduced=True)
540
(
541
[0 1 1 1]
542
[1 0 1 1]
543
[1 1 0 1], ['a', 'b', 'c'], ['d', 'e', 'f', 'g']
544
)
545
sage: M.representation(B='efg', reduced=True, labels=False,
546
....: order='gfeabcd')
547
[1 1 1 0]
548
[1 0 1 1]
549
[1 1 0 1]
550
"""
551
cdef LeanMatrix A
552
if order is None:
553
order = self.groundset_list()
554
else:
555
if not frozenset(order) == self.groundset():
556
raise ValueError("elements in argument ``order`` do not correspond to groundset of matroid.")
557
order_idx = [self._idx[e] for e in order]
558
if not reduced:
559
if B is None:
560
if self._representation is None:
561
B = set()
562
E = self.groundset_list()
563
i = 0
564
C = self.closure(B)
565
while i < len(E):
566
e = E[i]
567
if e in C:
568
i += 1
569
else:
570
B.add(e)
571
C = self.closure(B)
572
i += 1
573
self._representation = self._basic_representation(B)
574
A = self._representation
575
else:
576
if not self.groundset().issuperset(B):
577
raise ValueError("input is not a subset of the groundset.")
578
B = set(B)
579
A = self._basic_representation(B)
580
A = A.matrix_from_rows_and_columns(range(A.nrows()), order_idx)
581
if labels:
582
return (A._matrix_(), order)
583
else:
584
return A._matrix_()
585
else:
586
if B is None:
587
B = self.basis()
588
else:
589
if not self.groundset().issuperset(B):
590
raise ValueError("input is not a subset of the groundset.")
591
B = set(B)
592
A = self._reduced_representation(B)
593
R, C = self._current_rows_cols()
594
Ri = []
595
Ci = []
596
Rl = []
597
Cl = []
598
for e in order:
599
try:
600
i = R.index(e)
601
Ri.append(i)
602
Rl.append(e)
603
except ValueError:
604
Ci.append(C.index(e))
605
Cl.append(e)
606
A = A.matrix_from_rows_and_columns(Ri, Ci)
607
if labels or labels is None:
608
return (A._matrix_(), Rl, Cl)
609
else:
610
return A._matrix_()
611
612
cpdef _current_rows_cols(self, B=None):
613
"""
614
Return the current row and column labels of a reduced matrix.
615
616
INPUT:
617
618
- ``B`` -- (default: ``None``) If provided, first find a basis having
619
maximal intersection with ``B``.
620
621
OUTPUT:
622
623
- ``R`` -- A list of row indices; corresponds to the currently used
624
internal basis
625
- ``C`` -- A list of column indices; corresponds to the complement of
626
the current internal basis
627
628
EXAMPLES::
629
630
sage: M = matroids.named_matroids.Fano()
631
sage: A = M._reduced_representation('efg')
632
sage: R, C = M._current_rows_cols()
633
sage: (sorted(R), sorted(C))
634
(['e', 'f', 'g'], ['a', 'b', 'c', 'd'])
635
sage: R, C = M._current_rows_cols(B='abg')
636
sage: (sorted(R), sorted(C))
637
(['a', 'b', 'g'], ['c', 'd', 'e', 'f'])
638
639
"""
640
if B is not None:
641
self._move_current_basis(B, set())
642
basis = self.basis()
643
rows = [0] * self.full_rank()
644
for e in basis:
645
rows[self._prow[self._idx[e]]] = e
646
cols = [0] * self.full_corank()
647
for e in self.groundset() - basis:
648
cols[self._prow[self._idx[e]]] = e
649
return rows, cols
650
651
cpdef LeanMatrix _basic_representation(self, B=None):
652
"""
653
Return a basic matrix representation of the matroid.
654
655
INPUT:
656
657
- ``B`` -- (default: ``None``) a set of elements of the groundset.
658
659
OUTPUT:
660
661
A matrix `M` representing the matroid, where `M[B'] = I` for a basis
662
`B'` that maximally intersects the given set `B`.
663
If not provided, the current basis used internally is chosen for
664
`B'`. For a stable representation, use ``self.representation()``.
665
666
.. NOTE::
667
668
The method self.groundset_list() gives the labelling of the
669
columns by the elements of the matroid. The matrix returned
670
is a LeanMatrix subclass, which is intended for internal use only.
671
Use the ``representation()`` method to get a Sage matrix.
672
673
EXAMPLES::
674
675
sage: M = Matroid(reduced_matrix=Matrix(GF(7), [[1, 1, 1],
676
....: [1, 2, 3]]))
677
sage: M._basic_representation()
678
LeanMatrix instance with 2 rows and 5 columns over Finite Field of
679
size 7
680
sage: matrix(M._basic_representation([3, 4]))
681
[3 6 2 1 0]
682
[5 1 6 0 1]
683
684
"""
685
cdef LeanMatrix A
686
cdef long i
687
if B is not None:
688
self._move_current_basis(B, set())
689
basis = self.basis()
690
A = type(self._A)(self.full_rank(), self.size(), ring=self._A.base_ring())
691
i = 0
692
for e in self._E:
693
if e in basis:
694
C = self.fundamental_cocycle(basis, e)
695
for f in C:
696
A.set_unsafe(i, self._idx[f], C[f])
697
i += 1
698
return A
699
700
cpdef representation_vectors(self):
701
"""
702
Return a dictionary that associates a column vector with each element
703
of the matroid.
704
705
.. SEEALSO::
706
707
:meth:`M.representation() <LinearMatroid.representation>`
708
709
EXAMPLES::
710
711
sage: M = matroids.named_matroids.Fano()
712
sage: E = M.groundset_list()
713
sage: [M.representation_vectors()[e] for e in E]
714
[(1, 0, 0), (0, 1, 0), (0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 0),
715
(1, 1, 1)]
716
"""
717
R = self._matrix_().columns()
718
return {e: R[self._idx[e]] for e in self.groundset()}
719
720
cpdef LeanMatrix _reduced_representation(self, B=None):
721
"""
722
Return a reduced representation of the matroid, i.e. a matrix `R` such
723
that `[I\ \ R]` represents the matroid.
724
725
INPUT:
726
727
- ``B`` -- (default: ``None``) a set of elements of the groundset.
728
729
OUTPUT:
730
731
A matrix `R` forming a reduced representation of the matroid, with
732
rows labeled by a basis `B'` that maximally intersects the given set
733
`B`. If not provided, the current basis used internally labels the
734
rows.
735
736
.. NOTE::
737
738
The matrix returned is a LeanMatrix subclass, which is intended
739
for internal use only. Use the ``representation()`` method to get
740
a Sage matrix.
741
742
EXAMPLES::
743
744
sage: M = Matroid(reduced_matrix=Matrix(GF(7), [[1, 1, 1],
745
....: [1, 2, 3]]))
746
sage: M._reduced_representation()
747
LeanMatrix instance with 2 rows and 3 columns over Finite Field of
748
size 7
749
sage: matrix(M._reduced_representation([3, 4]))
750
[2 3 6]
751
[6 5 1]
752
"""
753
if B is not None:
754
self._move_current_basis(B, set())
755
return self._A.copy() # Deprecated Sage matrix operation
756
757
# (field) isomorphism
758
759
cpdef bint _is_field_isomorphism(self, LinearMatroid other, morphism): # not safe if self == other
760
"""
761
Version of :meth:`<LinearMatroid.is_field_isomorphism>` that does no
762
type checking.
763
764
INPUT:
765
766
- ``other`` -- A matroid instance, assumed to have the same base
767
ring as ``self``.
768
- ``morphism`` -- a dictionary mapping the groundset of ``self`` to
769
the groundset of ``other``.
770
771
OUTPUT:
772
773
Boolean.
774
775
.. WARNING::
776
777
This method is not safe if ``self == other``.
778
779
EXAMPLES::
780
781
sage: from sage.matroids.advanced import *
782
sage: M = matroids.named_matroids.Fano() \ ['g']
783
sage: N = BinaryMatroid(Matrix(matroids.Wheel(3)))
784
sage: morphism = {'a':0, 'b':1, 'c': 2, 'd':4, 'e':5, 'f':3}
785
sage: M._is_field_isomorphism(N, morphism)
786
True
787
"""
788
# TODO: ensure this is safe for noncommutative rings
789
global GF2, GF2_zero, GF2_one, GF2_not_defined
790
if GF2_not_defined:
791
GF2 = GF(2)
792
GF2_zero = GF2(0)
793
GF2_one = GF2(1)
794
GF2_not_defined = False
795
B = self.basis()
796
N = self.groundset() - B
797
Bo = frozenset([morphism[e] for e in B])
798
No = other.groundset() - Bo
799
if not other._is_independent(Bo):
800
return False
801
802
C = {}
803
for e in B:
804
C[e] = self._cocircuit(N | set([e]))
805
if other._cocircuit(No | set([morphism[e]])) != frozenset([morphism[f] for f in C[e]]):
806
return False
807
808
if self.base_ring() == GF2:
809
return True
810
811
self._set_current_basis(B)
812
other._set_current_basis(Bo)
813
normalization = {}
814
B = set([b for b in B if len(C[b]) > 1]) # coloops are boring
815
N = set(N)
816
while len(B) > 0:
817
found = False
818
for e in B:
819
Ce = set(C[e])
820
Ce.discard(e)
821
N2 = set(Ce - N)
822
if len(N2) > 0:
823
found = True
824
f = N2.pop()
825
normalization[e] = self._exchange_value(e, f) * normalization[f] / other._exchange_value(morphism[e], morphism[f])
826
B.discard(e)
827
for f in N2:
828
if self._exchange_value(e, f) * normalization[f] != normalization[e] * other._exchange_value(morphism[e], morphism[f]):
829
return False
830
for f in Ce & N:
831
normalization[f] = (self._one / self._exchange_value(e, f)) * normalization[e] * other._exchange_value(morphism[e], morphism[f])
832
N.discard(f)
833
break
834
if not found and len(N) > 0:
835
normalization[N.pop()] = self._one
836
return True
837
838
cpdef is_field_equivalent(self, other):
839
"""
840
Test for matroid representation equality.
841
842
Two linear matroids `M` and `N` with representation matrices `A` and
843
`B` are *field equivalent* if they have the same groundset, and the
844
identity map between the groundsets is an isomorphism between the
845
representations `A` and `B`. That is, one can be turned into the other
846
using only row operations and column scaling.
847
848
INPUT:
849
850
- ``other`` -- A matroid.
851
852
OUTPUT:
853
854
Boolean.
855
856
.. SEEALSO::
857
858
:meth:`M.equals() <sage.matroids.matroid.Matroid.equals>`,
859
:meth:`M.is_field_isomorphism() <LinearMatroid.is_field_isomorphism>`,
860
:meth:`M.is_field_isomorphic() <LinearMatroid.is_field_isomorphic>`
861
862
EXAMPLES:
863
864
A :class:`BinaryMatroid` and
865
:class:`LinearMatroid` use different
866
representations of the matroid internally, so `` == ``
867
yields ``False``, even if the matroids are equal::
868
869
sage: from sage.matroids.advanced import *
870
sage: M = matroids.named_matroids.Fano()
871
sage: M1 = LinearMatroid(Matrix(M), groundset=M.groundset_list())
872
sage: M2 = Matroid(groundset='abcdefg',
873
....: reduced_matrix=[[0, 1, 1, 1],
874
....: [1, 0, 1, 1],
875
....: [1, 1, 0, 1]], field=GF(2))
876
sage: M.equals(M1)
877
True
878
sage: M.equals(M2)
879
True
880
sage: M.is_field_equivalent(M1)
881
True
882
sage: M.is_field_equivalent(M2)
883
True
884
sage: M == M1
885
False
886
sage: M == M2
887
True
888
889
``LinearMatroid`` instances ``M`` and ``N`` satisfy ``M == N`` if the
890
representations are equivalent up to row operations and column
891
scaling::
892
893
sage: M1 = Matroid(groundset='abcd',
894
....: matrix=Matrix(GF(7), [[1, 0, 1, 1], [0, 1, 1, 2]]))
895
sage: M2 = Matroid(groundset='abcd',
896
....: matrix=Matrix(GF(7), [[1, 0, 1, 1], [0, 1, 1, 3]]))
897
sage: M3 = Matroid(groundset='abcd',
898
....: matrix=Matrix(GF(7), [[2, 6, 1, 0], [6, 1, 0, 1]]))
899
sage: M1.equals(M2)
900
True
901
sage: M1.equals(M3)
902
True
903
sage: M1 == M2
904
False
905
sage: M1 == M3
906
True
907
sage: M1.is_field_equivalent(M2)
908
False
909
sage: M1.is_field_equivalent(M3)
910
True
911
sage: M1.is_field_equivalent(M1)
912
True
913
"""
914
if self is other:
915
return True
916
if self.base_ring() != other.base_ring():
917
return False
918
if self.groundset() != other.groundset():
919
return False
920
if self.full_rank() != other.full_rank():
921
return False
922
morphism = {e: e for e in self.groundset()}
923
return self._is_field_isomorphism(other, morphism)
924
925
cpdef is_field_isomorphism(self, other, morphism):
926
"""
927
Test if a provided morphism induces a bijection between represented
928
matroids.
929
930
Two represented matroids are *field isomorphic* if the bijection
931
``morphism`` between them induces a field equivalence between their
932
representation matrices: the matrices are equal up to row operations
933
and column scaling. This implies that the matroids are isomorphic, but
934
the converse is false: two isomorphic matroids can be represented by
935
matrices that are not field equivalent.
936
937
INPUT:
938
939
- ``other`` -- A matroid.
940
- ``morphism`` -- A map from the groundset of ``self`` to the
941
groundset of ``other``. See documentation of the
942
:meth:`M.is_isomorphism() <sage.matroids.matroid.Matroid.is_isomorphism>`
943
method for more on what is accepted as input.
944
945
OUTPUT:
946
947
Boolean.
948
949
.. SEEALSO::
950
951
:meth:`M.is_isomorphism() <sage.matroids.matroid.Matroid.is_isomorphism>`,
952
:meth:`M.is_field_equivalent() <LinearMatroid.is_field_equivalent>`,
953
:meth:`M.is_field_isomorphic() <LinearMatroid.is_field_isomorphic>`
954
955
EXAMPLES::
956
957
sage: M = matroids.named_matroids.Fano()
958
sage: N = matroids.named_matroids.NonFano()
959
sage: N.is_field_isomorphism(M, {e:e for e in M.groundset()})
960
False
961
962
sage: from sage.matroids.advanced import *
963
sage: M = matroids.named_matroids.Fano() \ ['g']
964
sage: N = LinearMatroid(reduced_matrix=Matrix(GF(2),
965
....: [[-1, 0, 1], [1, -1, 0], [0, 1, -1]]))
966
sage: morphism = {'a':0, 'b':1, 'c': 2, 'd':4, 'e':5, 'f':3}
967
sage: M.is_field_isomorphism(N, morphism)
968
True
969
970
sage: M1 = Matroid(groundset=[0, 1, 2, 3], matrix=Matrix(GF(7),
971
....: [[1, 0, 1, 1], [0, 1, 1, 2]]))
972
sage: M2 = Matroid(groundset=[0, 1, 2, 3], matrix=Matrix(GF(7),
973
....: [[1, 0, 1, 1], [0, 1, 2, 1]]))
974
sage: mf1 = {0:0, 1:1, 2:2, 3:3}
975
sage: mf2 = {0:0, 1:1, 2:3, 3:2}
976
sage: M1.is_field_isomorphism(M2, mf1)
977
False
978
sage: M1.is_field_isomorphism(M2, mf2)
979
True
980
"""
981
from copy import copy
982
if self.base_ring() != other.base_ring():
983
return False
984
if self.full_rank() != other.full_rank():
985
return False
986
if self.full_corank() != other.full_corank():
987
return False
988
if not isinstance(morphism, dict):
989
mf = {}
990
try:
991
for e in self.groundset():
992
mf[e] = morphism[e]
993
except (IndexError, TypeError, ValueError):
994
try:
995
for e in self.groundset():
996
mf[e] = morphism(e)
997
except (TypeError, ValueError):
998
raise TypeError("the morphism argument does not seem to be an isomorphism.")
999
else:
1000
mf = morphism
1001
if len(self.groundset().difference(mf.keys())) > 0:
1002
raise ValueError("domain of morphism does not contain groundset of this matroid.")
1003
if len(other.groundset().difference([mf[e] for e in self.groundset()])) > 0:
1004
raise ValueError("range of morphism does not contain groundset of other matroid.")
1005
1006
if self != other:
1007
return self._is_field_isomorphism(other, mf)
1008
else:
1009
return self._is_field_isomorphism(copy(other), mf)
1010
1011
cpdef _fast_isom_test(self, other):
1012
"""
1013
Fast (field) isomorphism test for some subclasses.
1014
1015
INPUT:
1016
1017
- ``other`` -- A ``LinearMatroid`` instance, of the same subclass as
1018
``self``.
1019
1020
OUTPUT:
1021
1022
- ``None`` -- if the test is inconclusive;
1023
- ``True`` -- if the matroids were found to be field-isomorphic
1024
- ``False`` -- if the matroids were found to be non-field-isomorphic.
1025
1026
.. NOTE::
1027
1028
Intended for internal usage, in particular by the
1029
``is_field_isomorphic`` method. Matroids are assumed to be in the
1030
same subclass.
1031
1032
EXAMPLES::
1033
1034
sage: from sage.matroids.advanced import *
1035
sage: M1 = BinaryMatroid(reduced_matrix=Matrix(GF(2),
1036
....: [[1, 1, 0, 1], [1, 0, 1, 1], [0, 1, 1, 1]]))
1037
sage: M2 = LinearMatroid(reduced_matrix=Matrix(GF(2),
1038
....: [[1, 1, 0, 1], [1, 0, 1, 1], [1, 1, 0, 1]]))
1039
sage: M3 = BinaryMatroid(reduced_matrix=Matrix(GF(2),
1040
....: [[1, 1, 0, 1], [1, 0, 1, 1], [1, 1, 1, 0]]))
1041
sage: M2._fast_isom_test(M1) is None
1042
True
1043
sage: M1._fast_isom_test(M2)
1044
Traceback (most recent call last):
1045
...
1046
AttributeError: 'sage.matroids.linear_matroid.LinearMatroid'
1047
object has no attribute '_invariant'
1048
sage: M1._fast_isom_test(M3) is None
1049
True
1050
sage: Matroid(graphs.WheelGraph(6))._fast_isom_test(
1051
....: matroids.Wheel(5))
1052
True
1053
"""
1054
pass
1055
1056
def is_field_isomorphic(self, other):
1057
"""
1058
Test isomorphism between matroid representations.
1059
1060
Two represented matroids are *field isomorphic* if there is a
1061
bijection between their groundsets that induces a field equivalence
1062
between their representation matrices: the matrices are equal up to
1063
row operations and column scaling. This implies that the matroids are
1064
isomorphic, but the converse is false: two isomorphic matroids can be
1065
represented by matrices that are not field equivalent.
1066
1067
INPUT:
1068
1069
- ``other`` -- A matroid.
1070
1071
OUTPUT:
1072
1073
Boolean.
1074
1075
.. SEEALSO::
1076
1077
:meth:`M.is_isomorphic() <sage.matroids.matroid.Matroid.is_isomorphic>`,
1078
:meth:`M.is_field_isomorphism() <LinearMatroid.is_field_isomorphism>`,
1079
:meth:`M.is_field_equivalent() <LinearMatroid.is_field_equivalent>`
1080
1081
1082
EXAMPLES::
1083
1084
sage: M1 = matroids.Wheel(3)
1085
sage: M2 = matroids.CompleteGraphic(4)
1086
sage: M1.is_field_isomorphic(M2)
1087
True
1088
sage: M3 = Matroid(bases=M1.bases())
1089
sage: M1.is_field_isomorphic(M3)
1090
Traceback (most recent call last):
1091
...
1092
AttributeError: 'sage.matroids.basis_matroid.BasisMatroid' object
1093
has no attribute 'base_ring'
1094
sage: from sage.matroids.advanced import *
1095
sage: M4 = BinaryMatroid(Matrix(M1))
1096
sage: M5 = LinearMatroid(reduced_matrix=Matrix(GF(2), [[-1, 0, 1],
1097
....: [1, -1, 0], [0, 1, -1]]))
1098
sage: M4.is_field_isomorphic(M5)
1099
True
1100
1101
sage: M1 = Matroid(groundset=[0, 1, 2, 3], matrix=Matrix(GF(7),
1102
....: [[1, 0, 1, 1], [0, 1, 1, 2]]))
1103
sage: M2 = Matroid(groundset=[0, 1, 2, 3], matrix=Matrix(GF(7),
1104
....: [[1, 0, 1, 1], [0, 1, 2, 1]]))
1105
sage: M1.is_field_isomorphic(M2)
1106
True
1107
sage: M1.is_field_equivalent(M2)
1108
False
1109
1110
"""
1111
if self is other:
1112
return True
1113
if self.base_ring() != other.base_ring():
1114
return False
1115
if len(self) != len(other):
1116
return False
1117
if self.full_rank() != other.full_rank():
1118
return False
1119
if self.full_rank() == 0 or self.full_corank() == 0:
1120
return True
1121
if self.full_rank() == 1:
1122
return len(self.loops()) == len(other.loops())
1123
if self.full_corank() == 1:
1124
return len(self.coloops()) == len(other.coloops())
1125
if type(self) is type(other):
1126
T = self._fast_isom_test(other)
1127
if T is not None:
1128
return T
1129
1130
if self._weak_invariant() != other._weak_invariant():
1131
return False
1132
PS = self._weak_partition()
1133
PO = other._weak_partition()
1134
if len(PS) != len(PO):
1135
return False
1136
if len(PS) == len(self):
1137
morphism = {}
1138
for i in xrange(len(self)):
1139
morphism[min(PS[i])] = min(PO[i])
1140
return self._is_field_isomorphism(other, morphism)
1141
1142
if self._strong_invariant() != other._strong_invariant():
1143
return False
1144
PS = self._strong_partition()
1145
PO = other._strong_partition()
1146
if len(PS) != len(PO):
1147
return False
1148
if len(PS) == len(self):
1149
morphism = {}
1150
for i in xrange(len(self)):
1151
morphism[min(PS[i])] = min(PO[i])
1152
return self._is_field_isomorphism(other, morphism)
1153
1154
return self.nonbases()._equivalence(lambda sf, ot, morph: self._is_field_isomorphism(other, morph), other.nonbases(), PS, PO) is not None
1155
1156
def __richcmp__(left, right, op):
1157
r"""
1158
Compare two matroids.
1159
1160
We take a very restricted view on equality: the objects need to be of
1161
the exact same type (so no subclassing) and the internal data need to
1162
be the same. For linear matroids, in particular, this means field
1163
equivalence.
1164
1165
.. TODO::
1166
1167
In a user guide, write about "pitfalls": testing something like
1168
``M in S`` could yield ``False``, even if ``N.equals(M)`` is ``True`` for some
1169
`N` in `S`.
1170
1171
.. WARNING::
1172
1173
This method is linked to __hash__. If you override one, you MUST override the other!
1174
1175
.. SEEALSO::
1176
1177
:meth:`<LinearMatroid.is_field_equivalent>`
1178
1179
EXAMPLES:
1180
1181
See docstring for :meth:`LinearMatroid.equals>` for more::
1182
1183
sage: M1 = Matroid(groundset='abcd', matrix=Matrix(GF(7),
1184
....: [[1, 0, 1, 1], [0, 1, 1, 2]]))
1185
sage: M2 = Matroid(groundset='abcd', matrix=Matrix(GF(7),
1186
....: [[1, 0, 1, 1], [0, 1, 1, 3]]))
1187
sage: M3 = Matroid(groundset='abcd', matrix=Matrix(GF(7),
1188
....: [[2, 6, 1, 0], [6, 1, 0, 1]]))
1189
sage: M1.equals(M2)
1190
True
1191
sage: M1.equals(M3)
1192
True
1193
sage: M1 != M2 # indirect doctest
1194
True
1195
sage: M1 == M3 # indirect doctest
1196
True
1197
"""
1198
if op in [0, 1, 4, 5]: # <, <=, >, >=
1199
return NotImplemented
1200
if not isinstance(left, LinearMatroid) or not isinstance(right, LinearMatroid):
1201
return NotImplemented
1202
if left.__class__ != right.__class__: # since we have some subclasses, an extra test
1203
return NotImplemented
1204
if op == 2: # ==
1205
res = True
1206
if op == 3: # !=
1207
res = False
1208
# res gets inverted if matroids are deemed different.
1209
if left.is_field_equivalent(right):
1210
return res
1211
else:
1212
return not res
1213
1214
def __hash__(self):
1215
r"""
1216
Return an invariant of the matroid.
1217
1218
This function is called when matroids are added to a set. It is very
1219
desirable to override it so it can distinguish matroids on the same
1220
groundset, which is a very typical use case!
1221
1222
.. WARNING::
1223
1224
This method is linked to __richcmp__ (in Cython) and __cmp__ or
1225
__eq__/__ne__ (in Python). If you override one, you should (and in
1226
Cython: MUST) override the other!
1227
1228
EXAMPLES::
1229
1230
sage: M1 = Matroid(groundset='abcde', matrix=Matrix(GF(7),
1231
....: [[1, 0, 1, 1, 1], [0, 1, 1, 2, 3]]))
1232
sage: M2 = Matroid(groundset='abcde', matrix=Matrix(GF(7),
1233
....: [[0, 1, 1, 2, 3], [1, 0, 1, 1, 1]]))
1234
sage: hash(M1) == hash(M2)
1235
True
1236
sage: M2 = M1.dual()
1237
sage: hash(M1) == hash(M2)
1238
False
1239
"""
1240
return hash((self.groundset(), self.full_rank(), self._weak_invariant()))
1241
1242
# minors, dual
1243
1244
cpdef _minor(self, contractions, deletions):
1245
r"""
1246
Return a minor.
1247
1248
INPUT:
1249
1250
- ``contractions`` -- An object with Python's ``frozenset`` interface
1251
containing a subset of ``self.groundset()``.
1252
- ``deletions`` -- An object with Python's ``frozenset`` interface
1253
containing a subset of ``self.groundset()``.
1254
1255
.. NOTE::
1256
1257
This method does NOT do any checks. Besides the assumptions above,
1258
we assume the following:
1259
1260
- ``contractions`` is independent
1261
- ``deletions`` is coindependent
1262
- ``contractions`` and ``deletions`` are disjoint.
1263
1264
OUTPUT:
1265
1266
A matroid.
1267
1268
EXAMPLES::
1269
1270
sage: M = Matroid(groundset='abcdefgh', ring=GF(5),
1271
....: reduced_matrix=[[2, 1, 1, 0],
1272
....: [1, 1, 0, 1], [1, 0, 1, 1], [0, 1, 1, 2]])
1273
sage: N = M._minor(contractions=set(['a']), deletions=set([]))
1274
sage: N._minor(contractions=set([]), deletions=set(['b', 'c']))
1275
Linear matroid of rank 3 on 5 elements represented over the Finite
1276
Field of size 5
1277
"""
1278
cdef LeanMatrix M
1279
self._move_current_basis(contractions, deletions)
1280
rows = list(self.basis() - contractions)
1281
cols = list(self.cobasis() - deletions)
1282
M = type(self._A)(len(rows), len(cols), ring=self.base_ring())
1283
for i in range(len(rows)):
1284
for j in range(len(cols)):
1285
M.set_unsafe(i, j, self._exchange_value(rows[i], cols[j]))
1286
return type(self)(reduced_matrix=M, groundset=rows + cols)
1287
1288
cpdef dual(self):
1289
"""
1290
Return the dual of the matroid.
1291
1292
Let `M` be a matroid with ground set `E`. If `B` is the set of bases
1293
of `M`, then the set `\{E - b : b \in B\}` is the set of bases of
1294
another matroid, the *dual* of `M`.
1295
1296
If the matroid is represented by `[I_1\ \ A]`, then the dual is
1297
represented by `[-A^T\ \ I_2]` for appropriately sized identity
1298
matrices `I_1, I_2`.
1299
1300
OUTPUT:
1301
1302
The dual matroid.
1303
1304
EXAMPLES::
1305
1306
sage: A = Matrix(GF(7), [[1, 1, 0, 1],
1307
....: [1, 0, 1, 1],
1308
....: [0, 1, 1, 1]])
1309
sage: B = - A.transpose()
1310
sage: Matroid(reduced_matrix=A).dual() == Matroid(
1311
....: reduced_matrix=B,
1312
....: groundset=[3, 4, 5, 6, 0, 1, 2])
1313
True
1314
1315
"""
1316
cdef LeanMatrix R = -self._reduced_representation().transpose()
1317
rows, cols = self._current_rows_cols()
1318
return type(self)(reduced_matrix=R, groundset=cols + rows)
1319
1320
cpdef has_line_minor(self, k, hyperlines=None):
1321
"""
1322
Test if the matroid has a `U_{2, k}`-minor.
1323
1324
The matroid `U_{2, k}` is a matroid on `k` elements in which every
1325
subset of at most 2 elements is independent, and every subset of more
1326
than two elements is dependent.
1327
1328
The optional argument ``hyperlines`` restricts the search space: this
1329
method returns ``False`` if `si(M/F)` is isomorphic to `U_{2, l}` with
1330
`l \geq k` for some `F` in ``hyperlines``, and ``True`` otherwise.
1331
1332
INPUT:
1333
1334
- ``k`` -- the length of the line minor
1335
- ``hyperlines`` -- (default: ``None``) a set of flats of codimension
1336
2. Defaults to the set of all flats of codimension 2.
1337
1338
OUTPUT:
1339
1340
Boolean.
1341
1342
EXAMPLES::
1343
1344
sage: M = matroids.named_matroids.N1()
1345
sage: M.has_line_minor(4)
1346
True
1347
sage: M.has_line_minor(5)
1348
False
1349
sage: M.has_line_minor(k=4, hyperlines=[['a', 'b', 'c']])
1350
False
1351
sage: M.has_line_minor(k=4, hyperlines=[['a', 'b', 'c'],
1352
....: ['a', 'b', 'd' ]])
1353
True
1354
1355
"""
1356
try:
1357
if k > len(self.base_ring()) + 1:
1358
return False
1359
except TypeError:
1360
pass
1361
return Matroid.has_line_minor(self, k, hyperlines)
1362
1363
cpdef has_field_minor(self, N):
1364
"""
1365
Check if ``self`` has a minor field isomorphic to ``N``.
1366
1367
INPUT:
1368
1369
- ``N`` -- A matroid.
1370
1371
OUTPUT:
1372
1373
Boolean.
1374
1375
.. SEEALSO::
1376
1377
:meth:`M.minor() <sage.matroids.matroid.Matroid.minor>`,
1378
:meth:`M.is_field_isomorphic() <LinearMatroid.is_field_isomorphic>`
1379
1380
.. TODO::
1381
1382
This important method can (and should) be optimized considerably.
1383
See [Hlineny]_ p.1219 for hints to that end.
1384
1385
EXAMPLES::
1386
1387
sage: M = matroids.Whirl(3)
1388
sage: matroids.named_matroids.Fano().has_field_minor(M)
1389
False
1390
sage: matroids.named_matroids.NonFano().has_field_minor(M)
1391
True
1392
"""
1393
if self is N:
1394
return True
1395
if not isinstance(N, Matroid):
1396
raise ValueError("N must be a matroid.")
1397
if self.base_ring() != N.base_ring():
1398
return False
1399
rd = self.full_rank() - N.full_rank()
1400
cd = self.full_corank() - N.full_corank()
1401
if rd < 0 or cd < 0:
1402
return False
1403
1404
R = self._reduced_representation()
1405
M = type(self)(reduced_matrix=R)
1406
1407
F = M.flats(rd)
1408
G = M.dual().flats(cd)
1409
a = len(M.loops())
1410
b = len(M.coloops())
1411
for X in F:
1412
XB = M.max_independent(X)
1413
for Y in G:
1414
YB = M.max_coindependent(Y - XB)
1415
if len(YB) == cd and len((X - XB) - YB) <= a and len((Y - YB) - XB) <= b and N.is_field_isomorphic(M._minor(contractions=XB, deletions=YB)):
1416
return True
1417
return False
1418
1419
# cycles, cocycles and cross ratios
1420
1421
cpdef _exchange_value(self, e, f):
1422
"""
1423
Return the matrix entry indexed by row `e` and column `f`.
1424
1425
INPUT:
1426
1427
- ``e`` -- an element of the groundset.
1428
- ``f`` -- an element of the groundset.
1429
1430
``e`` should be in the currently active basis, and ``f`` in the
1431
currently active cobasis.
1432
1433
OUTPUT:
1434
1435
The (internal) matrix entry indexed by row `e` and column `f`.
1436
1437
.. WARNING::
1438
1439
Intended for internal use. This method does no checks of any kind.
1440
1441
EXAMPLES::
1442
1443
sage: M = Matroid(Matrix(GF(7), [[1, 0, 1, 1], [0, 1, 1, 4]]))
1444
sage: M._exchange_value(1, 3)
1445
4
1446
"""
1447
return self.__exchange_value(self._idx[e], self._idx[f])
1448
1449
cpdef fundamental_cycle(self, B, e):
1450
"""
1451
Return the fundamental cycle, relative to ``B``, containing element
1452
``e``.
1453
1454
This is the
1455
:meth:`fundamental circuit <sage.matroids.matroid.Matroid.fundamental_circuit>`
1456
together with an appropriate signing from the field, such that `Av=0`,
1457
where `A` is the representation matrix, and `v` the vector
1458
corresponding to the output.
1459
1460
INPUT:
1461
1462
- ``B`` -- a basis of the matroid
1463
- ``e`` -- an element outside the basis
1464
1465
OUTPUT:
1466
1467
A dictionary mapping elements of ``M.fundamental_circuit(B, e)`` to
1468
elements of the ring.
1469
1470
.. SEEALSO::
1471
1472
:meth:`M.fundamental_circuit() <sage.matroids.matroid.Matroid.fundamental_circuit>`
1473
1474
EXAMPLES::
1475
1476
sage: M = Matroid(Matrix(GF(7), [[1, 0, 1, 1], [0, 1, 1, 4]]))
1477
sage: v = M.fundamental_cycle([0, 1], 3)
1478
sage: [v[0], v[1], v[3]]
1479
[6, 3, 1]
1480
sage: frozenset(v.keys()) == M.fundamental_circuit([0, 1], 3)
1481
True
1482
"""
1483
if e in B or not self._is_basis(B):
1484
return None
1485
self._move_current_basis(B, set())
1486
self._move_current_basis(B, set())
1487
chain = {}
1488
chain[e] = self._one
1489
for f in B:
1490
x = self._exchange_value(f, e)
1491
if x != 0:
1492
chain[f] = -x
1493
return chain
1494
1495
cpdef fundamental_cocycle(self, B, e):
1496
"""
1497
Return the fundamental cycle, relative to ``B``, containing element
1498
``e``.
1499
1500
This is the
1501
:meth:`fundamental cocircuit <sage.matroids.matroid.Matroid.fundamental_cocircuit>`
1502
together with an appropriate signing from the field, such that `Av=0`,
1503
where `A` is a representation matrix of the dual, and `v` the vector
1504
corresponding to the output.
1505
1506
INPUT:
1507
1508
- ``B`` -- a basis of the matroid
1509
- ``e`` -- an element of the basis
1510
1511
OUTPUT:
1512
1513
A dictionary mapping elements of ``M.fundamental_cocircuit(B, e)`` to
1514
elements of the ring.
1515
1516
.. SEEALSO::
1517
1518
:meth:`M.fundamental_cocircuit() <sage.matroids.matroid.Matroid.fundamental_cocircuit>`
1519
1520
EXAMPLES::
1521
1522
sage: M = Matroid(Matrix(GF(7), [[1, 0, 1, 1], [0, 1, 1, 4]]))
1523
sage: v = M.fundamental_cocycle([0, 1], 0)
1524
sage: [v[0], v[2], v[3]]
1525
[1, 1, 1]
1526
sage: frozenset(v.keys()) == M.fundamental_cocircuit([0, 1], 0)
1527
True
1528
"""
1529
if e not in B or not self._is_basis(B):
1530
return None
1531
self._move_current_basis(B, set())
1532
cochain = {}
1533
cochain[e] = self._one
1534
for f in self.groundset() - set(B):
1535
x = self._exchange_value(e, f)
1536
if x != 0:
1537
cochain[f] = x
1538
return cochain
1539
1540
cpdef _line_ratios(self, F):
1541
"""
1542
Return the set of nonzero ratios of column entries after contracting
1543
a rank-`r-2` flat ``F``.
1544
1545
.. WARNING::
1546
1547
Intended for internal use. Does no checking.
1548
1549
EXAMPLES::
1550
1551
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1, 1],
1552
....: [0, 1, 0, 1, 2, 4], [0, 0, 1, 3, 2, 5]]))
1553
sage: sorted(M._line_ratios(set([2])))
1554
[1, 2, 4]
1555
sage: sorted(M._line_ratios([0]))
1556
[1, 5]
1557
"""
1558
self._move_current_basis(F, set())
1559
X = self.basis().difference(F)
1560
a = min(X)
1561
b = max(X)
1562
rat = set()
1563
for c in self.cobasis():
1564
s = self._exchange_value(a, c)
1565
if s != 0:
1566
t = self._exchange_value(b, c)
1567
if t != 0:
1568
rat.add(s * (t ** (-1)))
1569
return rat
1570
1571
cpdef _line_length(self, F):
1572
"""
1573
Return ``len(M.contract(F).simplify())``, where ``F`` is assumed to be
1574
a flat of rank 2 less than the matroid.
1575
1576
.. WARNING::
1577
1578
Intended for internal use. Does no checking.
1579
1580
EXAMPLES::
1581
1582
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1, 1],
1583
....: [0, 1, 0, 1, 2, 4], [0, 0, 1, 3, 2, 5]]))
1584
sage: M._line_length([2])
1585
5
1586
sage: M._line_length([0])
1587
4
1588
"""
1589
return 2 + len(self._line_ratios(F))
1590
1591
cpdef _line_cross_ratios(self, F):
1592
"""
1593
Return the set of cross ratios of column entries after contracting a
1594
rank-`r-2` flat ``F``.
1595
1596
Note that these are only the ordered cross ratios!
1597
1598
.. WARNING::
1599
1600
Intended for internal use. Does no checking.
1601
1602
EXAMPLES::
1603
1604
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1, 1],
1605
....: [0, 1, 0, 1, 2, 4], [0, 0, 1, 3, 2, 5]]))
1606
sage: sorted(M._line_cross_ratios(set([2])))
1607
[2, 4]
1608
sage: sorted(M._line_cross_ratios([0]))
1609
[5]
1610
"""
1611
cr = set()
1612
rat = self._line_ratios(F)
1613
while len(rat) != 0:
1614
r1 = rat.pop()
1615
for r2 in rat:
1616
cr.add(r2 / r1)
1617
return cr
1618
1619
cpdef cross_ratios(self, hyperlines=None):
1620
r"""
1621
Return the set of cross ratios that occur in this linear matroid.
1622
1623
Consider the following matrix with columns labeled by
1624
`\{a, b, c, d\}`.
1625
1626
.. MATH::
1627
1628
\begin{matrix}
1629
1 & 0 & 1 & 1\\
1630
0 & 1 & x & 1
1631
\end{matrix}
1632
1633
The cross ratio of the ordered tuple `(a, b, c, d)` equals `x`. The
1634
set of all cross ratios of a matroid is the set of cross ratios of all
1635
such minors.
1636
1637
INPUT:
1638
1639
- ``hyperlines`` -- (optional) a set of flats of the matroid, of rank
1640
`r - 2`, where `r` is the rank of the matroid. If not given, then
1641
``hyperlines`` defaults to all such flats.
1642
1643
OUTPUT:
1644
1645
A list of all cross ratios of this linearly represented matroid that
1646
occur in rank-2 minors that arise by contracting a flat ``F`` in
1647
``hyperlines`` (so by default, those are all cross ratios).
1648
1649
.. SEEALSO::
1650
1651
:meth:`M.cross_ratio() <LinearMatroid.cross_ratio>`
1652
1653
EXAMPLES::
1654
1655
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1, 1],
1656
....: [0, 1, 0, 1, 2, 4],
1657
....: [0, 0, 1, 3, 2, 5]]))
1658
sage: sorted(M.cross_ratios())
1659
[2, 3, 4, 5, 6]
1660
sage: M = matroids.CompleteGraphic(5)
1661
sage: M.cross_ratios()
1662
set([])
1663
"""
1664
if hyperlines is None:
1665
hyperlines = self.flats(self.full_rank() - 2)
1666
CR = set()
1667
for F in hyperlines:
1668
CR |= self._line_cross_ratios(F)
1669
CR2 = set()
1670
while len(CR) != 0:
1671
cr = CR.pop()
1672
asc = set([cr, cr ** (-1), -cr + 1, (-cr + 1) ** (-1), cr / (cr - 1), (cr - 1) / cr])
1673
CR2.update(asc)
1674
CR.difference_update(asc)
1675
return CR2
1676
1677
cpdef cross_ratio(self, F, a, b, c, d):
1678
r"""
1679
Return the cross ratio of the four ordered points ``a, b, c, d``
1680
after contracting a flat ``F`` of codimension 2.
1681
1682
Consider the following matrix with columns labeled by
1683
`\{a, b, c, d\}`.
1684
1685
.. MATH::
1686
1687
\begin{bmatrix}
1688
1 & 0 & 1 & 1\\
1689
0 & 1 & x & 1
1690
\end{bmatrix}
1691
1692
The cross ratio of the ordered tuple `(a, b, c, d)` equals `x`. This
1693
method looks at such minors where ``F`` is a flat to be contracted,
1694
and all remaining elements other than ``a, b, c, d`` are deleted.
1695
1696
INPUT:
1697
1698
- ``F`` -- A flat of codimension 2
1699
- ``a, b, c, d`` -- elements of the groundset
1700
1701
OUTPUT:
1702
1703
The cross ratio of the four points on the line obtained by
1704
contracting ``F``.
1705
1706
EXAMPLES::
1707
1708
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1, 1],
1709
....: [0, 1, 0, 1, 2, 4],
1710
....: [0, 0, 1, 3, 2, 6]]))
1711
sage: M.cross_ratio([0], 1, 2, 3, 5)
1712
4
1713
1714
sage: M = Matroid(ring=GF(7), matrix=[[1, 0, 1, 1], [0, 1, 1, 1]])
1715
sage: M.cross_ratio(set(), 0, 1, 2, 3)
1716
Traceback (most recent call last):
1717
...
1718
ValueError: points a, b, c, d do not form a 4-point line in M/F
1719
1720
"""
1721
F = frozenset(F)
1722
if not F.issubset(self.groundset()):
1723
raise ValueError("set F must be subset of the groundset")
1724
if not self.groundset().issuperset([a, b, c, d]):
1725
raise ValueError("variables a, b, c, d need to be elements of the matroid")
1726
if self._closure(F | set([a, b])) != self.groundset():
1727
raise ValueError("set F must be a flat; F with a, b must span the matroid")
1728
self._move_current_basis(set([a, b]), set([c, d]))
1729
cr1 = self._exchange_value(a, c) * self._exchange_value(b, d)
1730
cr2 = self._exchange_value(a, d) * self._exchange_value(b, c)
1731
if cr1 == 0 or cr2 == 0 or cr1 == cr2:
1732
raise ValueError("points a, b, c, d do not form a 4-point line in M/F")
1733
return cr1 / cr2
1734
1735
cpdef _line_cross_ratio_test(self, F, x, fundamentals):
1736
r"""
1737
Check whether the cross ratios involving a fixed element in a fixed
1738
rank-2 minor are in a specified subset.
1739
1740
INPUT:
1741
1742
- ``F`` -- a flat of codimension 2
1743
- ``x`` -- an element outside ``F``
1744
- ``fundamentals`` -- a set of fundamental elements.
1745
1746
OUTPUT:
1747
1748
``True`` if all cross ratios involving ``x`` are in
1749
``fundamentals``; ``False`` otherwise.
1750
1751
.. NOTE::
1752
1753
This method is intended for checking extensions of a matroid, so
1754
it is assumed that the cross ratios of `(M/F)-x` are all in the
1755
desired subset. Moreover, the set of cross ratios is closed under
1756
reordering of the elements, i.e. if `x` is in ``fundamentals``
1757
then also `1/x, 1-x, 1/(1-x), x/(x-1), (x-1)/x` are in it.
1758
1759
.. WARNING::
1760
1761
Intended for internal use. No checks whatsoever on validity of
1762
input.
1763
1764
EXAMPLES::
1765
1766
sage: M = Matroid(ring=QQ, reduced_matrix=[[1, 1, 1, 0],
1767
....: [1, 1, 0, 1], [1, 0, 1, 1]])
1768
sage: M._line_cross_ratio_test(set([0]), 6, set([1]))
1769
True
1770
sage: M._line_cross_ratio_test(set([4]), 6, set([1]))
1771
False
1772
sage: M._line_cross_ratio_test(set([4]), 6, set([1, 2, 1/2, -1]))
1773
True
1774
"""
1775
self._move_current_basis(F, set([x]))
1776
X = self.basis() - F
1777
a = min(X)
1778
b = max(X)
1779
s = self._exchange_value(a, x)
1780
t = self._exchange_value(b, x)
1781
if s == 0 or t == 0:
1782
return True
1783
try:
1784
r = s / t
1785
for c in self.cobasis(): # Only need to check 2x2 submatrices relative to a fixed basis, because of our assumptions
1786
s = self._exchange_value(a, c)
1787
t = self._exchange_value(b, c)
1788
if s != 0 and t != 0:
1789
if not s / t / r in fundamentals:
1790
return False
1791
except ZeroDivisionError:
1792
return False
1793
return True
1794
1795
cpdef _cross_ratio_test(self, x, fundamentals, hyperlines=None):
1796
r"""
1797
Test if the cross ratios using a given element of this linear matroid
1798
are contained in a given set of fundamentals.
1799
1800
INPUT:
1801
1802
- ``x`` -- an element of the ground set
1803
- ``fundamentals`` -- a subset of the base ring
1804
- ``hyperlines`` (optional) -- a set of flats of rank=full_rank-2
1805
1806
OUTPUT:
1807
1808
Boolean. ``True`` if each cross ratio using ``x`` is an element of
1809
``fundamentals``. If ``hyperlines`` is specified, then the method
1810
tests if each cross ratio in a minor that arises by contracting a flat
1811
``F`` in ``hyperlines`` and uses ``x`` is in ``fundamentals``. If
1812
``hyperlines`` is not specified, all flats of codimension 2 are
1813
tested.
1814
1815
.. NOTE::
1816
1817
This method is intended for checking extensions of a matroid, so
1818
it is assumed that the cross ratios of `M/F\setminus x` are all in
1819
the desired subset. Moreover, the set of fundamentals is closed
1820
under reordering of the elements, i.e. if `x \in`
1821
``fundamentals`` then also `1/x, 1-x, 1/(1-x), x/(x-1), (x-1)/x`
1822
are in it.
1823
1824
EXAMPLES::
1825
1826
sage: M = Matroid(ring=QQ, reduced_matrix=[[1, 1, 1, 0],
1827
....: [1, 1, 0, 1], [1, 0, 1, 1]])
1828
sage: M._cross_ratio_test(6, set([1]))
1829
False
1830
sage: M._cross_ratio_test(6, set([1, 2, 1/2, -1]))
1831
True
1832
1833
"""
1834
if hyperlines is None:
1835
hyperlines = [F for F in self.flats(self.full_rank() - 2) if self._line_length(F) > 2]
1836
if self.rank(self.groundset() - set([x])) < self.full_rank():
1837
return True
1838
for F in hyperlines:
1839
if not self._line_cross_ratio_test(F, x, fundamentals):
1840
return False
1841
return True
1842
1843
# linear extension
1844
cpdef linear_extension(self, element, chain=None, col=None):
1845
r"""
1846
Return a linear extension of this matroid.
1847
1848
A *linear extension* of the represented matroid `M` by element `e` is
1849
a matroid represented by `[A\ \ b]`, where `A` is a representation
1850
matrix of `M` and `b` is a new column labeled by `e`.
1851
1852
INPUT:
1853
1854
- ``element`` -- the name of the new element.
1855
- ``col`` -- (default: ``None``) a column to be appended to
1856
``self.representation()``. Can be any iterable.
1857
- ``chain`` -- (default: ``None``) a dictionary that maps elements of
1858
the ground set to elements of the base ring.
1859
1860
OUTPUT:
1861
1862
A linear matroid `N = M([A\ \ b])`, where `A` is a matrix such that
1863
the current matroid is `M[A]`, and `b` is either given by ``col`` or
1864
is a weighted combination of columns of `A`, the weigths being given
1865
by ``chain``.
1866
1867
.. SEEALSO::
1868
1869
:meth:`M.extension() <sage.matroids.matroid.Matroid.extension>`.
1870
1871
EXAMPLES::
1872
1873
sage: M = Matroid(ring=GF(2), matrix=[[1, 1, 0, 1, 0, 0],
1874
....: [1, 0, 1, 0, 1, 0],
1875
....: [0, 1, 1, 0, 0, 1],
1876
....: [0, 0, 0, 1, 1, 1]])
1877
sage: M.linear_extension(6, {0:1, 5: 1}).representation()
1878
[1 1 0 1 0 0 1]
1879
[1 0 1 0 1 0 1]
1880
[0 1 1 0 0 1 1]
1881
[0 0 0 1 1 1 1]
1882
sage: M.linear_extension(6, col=[0, 1, 1, 1]).representation()
1883
[1 1 0 1 0 0 0]
1884
[1 0 1 0 1 0 1]
1885
[0 1 1 0 0 1 1]
1886
[0 0 0 1 1 1 1]
1887
1888
"""
1889
cdef LeanMatrix cl
1890
cdef long i
1891
if element in self.groundset():
1892
raise ValueError("extension element is already in groundset")
1893
if self._representation is not None and col is not None:
1894
R = self.base_ring()
1895
cl = type(self._representation)(self._representation.nrows(), 1, ring=R)
1896
i = 0
1897
for x in col:
1898
if i == self._representation.nrows():
1899
raise ValueError("provided column has too many entries")
1900
cl.set_unsafe(i, 0, R(x))
1901
i += 1
1902
if i < self._representation.nrows():
1903
raise ValueError("provided column has too few entries")
1904
E = self._E + (element,)
1905
return type(self)(matrix=self._representation.augment(cl), groundset=E)
1906
elif col is not None:
1907
raise ValueError("can only specify column relative to fixed representation. Run self._matrix_() first.")
1908
else:
1909
if not isinstance(chain, dict):
1910
raise TypeError("chain argument needs to be a dictionary")
1911
return self._linear_extensions(element, [chain])[0]
1912
1913
cpdef linear_coextension(self, element, cochain=None, row=None):
1914
r"""
1915
Return a linear coextension of this matroid.
1916
1917
A *linear coextension* of the represented matroid `M` by element `e`
1918
is a matroid represented by
1919
1920
.. MATH::
1921
1922
\begin{bmatrix}
1923
A & 0\\
1924
-c & 1
1925
\end{bmatrix},
1926
1927
where `A` is a representation matrix of `M`, `c` is a new row, and the
1928
last column is labeled by `e`.
1929
1930
This is the dual method of
1931
:meth:`M.linear_extension() <LinearMatroid.linear_extension>`.
1932
1933
INPUT:
1934
1935
- ``element`` -- the name of the new element.
1936
- ``row`` -- (default: ``None``) a row to be appended to
1937
``self.representation()``. Can be any iterable.
1938
- ``cochain`` -- (default: ``None``) a dictionary that maps elements
1939
of the ground set to elements of the base ring.
1940
1941
OUTPUT:
1942
1943
A linear matroid `N = M([A 0; -c 1])`, where `A` is a matrix such that
1944
the current matroid is `M[A]`, and `c` is either given by ``row``
1945
(relative to ``self.representation()``) or has nonzero entries given
1946
by ``cochain``.
1947
1948
.. NOTE::
1949
1950
The minus sign is to ensure this method commutes with dualizing.
1951
See the last example.
1952
1953
.. SEEALSO::
1954
1955
:meth:`M.coextension() <sage.matroids.matroid.Matroid.coextension>`,
1956
:meth:`M.linear_extension() <LinearMatroid.linear_extension>`,
1957
:meth:`M.dual() <LinearMatroid.dual>`
1958
1959
EXAMPLES::
1960
1961
sage: M = Matroid(ring=GF(2), matrix=[[1, 1, 0, 1, 0, 0],
1962
....: [1, 0, 1, 0, 1, 0],
1963
....: [0, 1, 1, 0, 0, 1],
1964
....: [0, 0, 0, 1, 1, 1]])
1965
sage: M.linear_coextension(6, {0:1, 5: 1}).representation()
1966
[1 1 0 1 0 0 0]
1967
[1 0 1 0 1 0 0]
1968
[0 1 1 0 0 1 0]
1969
[0 0 0 1 1 1 0]
1970
[1 0 0 0 0 1 1]
1971
sage: M.linear_coextension(6, row=[0,1,1,1,0,1]).representation()
1972
[1 1 0 1 0 0 0]
1973
[1 0 1 0 1 0 0]
1974
[0 1 1 0 0 1 0]
1975
[0 0 0 1 1 1 0]
1976
[0 1 1 1 0 1 1]
1977
1978
Coextending commutes with dualizing::
1979
1980
sage: M = matroids.named_matroids.NonFano()
1981
sage: chain = {'a': 1, 'b': -1, 'f': 1}
1982
sage: M1 = M.linear_coextension('x', chain)
1983
sage: M2 = M.dual().linear_extension('x', chain)
1984
sage: M1 == M2.dual()
1985
True
1986
"""
1987
cdef LeanMatrix col
1988
cdef LeanMatrix rw
1989
cdef long i
1990
if element in self.groundset():
1991
raise ValueError("extension element is already in groundset")
1992
if self._representation is not None and row is not None:
1993
R = self.base_ring()
1994
rw = type(self._representation)(1, self._representation.ncols(), ring=R)
1995
i = 0
1996
for x in row:
1997
if i == self._representation.ncols():
1998
raise ValueError("provided row has too many entries")
1999
rw.set_unsafe(0, i, -R(x))
2000
i += 1
2001
if i < self._representation.ncols():
2002
raise ValueError("provided row has too few entries")
2003
E = self._E + (element,)
2004
col = type(self._representation)(self._representation.nrows() + 1, 1, ring=self.base_ring())
2005
col.set_unsafe(self._representation.nrows(), 0, self._one)
2006
return type(self)(matrix=self._representation.stack(rw).augment(col), groundset=E)
2007
elif row is not None:
2008
raise ValueError("can only specify row relative to fixed representation. Run self.representation() first.")
2009
else:
2010
if not isinstance(cochain, dict):
2011
raise TypeError("cochain argument needs to be a dictionary")
2012
return self._linear_coextensions(element, [cochain])[0]
2013
2014
cpdef _linear_extensions(self, element, chains):
2015
r"""
2016
Return the linear extensions of this matroid representation specified
2017
by the given chains.
2018
2019
This is an internal method that does no checking on the input.
2020
2021
INPUT:
2022
2023
- ``element`` -- the name of the new element.
2024
- ``chains`` -- a list of dictionaries, each of which maps elements of
2025
the ground set to elements of the base ring.
2026
2027
OUTPUT:
2028
2029
A list of linear matroids `N = M([A b])`, where `A` is a matrix such
2030
that the current matroid is `M[A]`, and `b` is a weighted combination
2031
of columns of `A`, the weigths being given by the elements of
2032
``chains``.
2033
2034
EXAMPLES::
2035
2036
sage: M = Matroid(ring=GF(2), matrix=[[1, 1, 0, 1, 0, 0],
2037
....: [1, 0, 1, 0, 1, 0], [0, 1, 1, 0, 0, 1], [0, 0, 0, 1, 1, 1]])
2038
sage: M._linear_extensions(6, [{0:1, 5: 1}])[0].representation()
2039
[1 1 0 1 0 0 1]
2040
[1 0 1 0 1 0 1]
2041
[0 1 1 0 0 1 1]
2042
[0 0 0 1 1 1 1]
2043
"""
2044
cdef long i, j
2045
cdef LeanMatrix M
2046
ext = []
2047
if self._representation is None:
2048
M = type(self._A)(self.full_rank(), self.size() + 1, self._basic_representation())
2049
else:
2050
M = type(self._A)(self._representation.nrows(), self.size() + 1, self._representation)
2051
E = self._E + (element,)
2052
D = {}
2053
for i from 0 <= i < self.size():
2054
D[E[i]] = i
2055
for chain in chains:
2056
for i from 0 <= i < M.nrows():
2057
a = self._zero
2058
for e in chain:
2059
a += M.get_unsafe(i, D[e]) * chain[e]
2060
M.set_unsafe(i, self.size(), a)
2061
ext.append(type(self)(matrix=M, groundset=E))
2062
return ext
2063
2064
cpdef _linear_coextensions(self, element, cochains):
2065
r"""
2066
Return the linear coextensions of this matroid representation
2067
specified by the given cochains.
2068
2069
Internal method that does no typechecking.
2070
2071
INPUT:
2072
2073
- ``element`` -- the name of the new element.
2074
- ``cochains`` -- a list of dictionaries, each of which maps elements
2075
of the ground set to elements of the base ring.
2076
2077
OUTPUT:
2078
2079
A list of linear matroids `N = M([A 0; -c 1])`, where `A` is a matrix
2080
such that the current matroid is `M[A]`, and `c` has nonzero entries
2081
given by ``cochain``.
2082
2083
EXAMPLES::
2084
2085
sage: M = Matroid(ring=GF(2), matrix=[[1, 1, 0, 1, 0, 0],
2086
....: [1, 0, 1, 0, 1, 0], [0, 1, 1, 0, 0, 1], [0, 0, 0, 1, 1, 1]])
2087
sage: M._linear_coextensions(6, [{0:1, 5: 1}])[0].representation()
2088
[1 1 0 1 0 0 0]
2089
[1 0 1 0 1 0 0]
2090
[0 1 1 0 0 1 0]
2091
[0 0 0 1 1 1 0]
2092
[1 0 0 0 0 1 1]
2093
"""
2094
cdef long i, j
2095
cdef LeanMatrix M
2096
coext = []
2097
if self._representation is None:
2098
M = type(self._A)(self.full_rank() + 1, self.size() + 1, self._basic_representation())
2099
else:
2100
M = type(self._A)(self._representation.nrows() + 1, self.size() + 1, self._representation)
2101
M.set_unsafe(M.nrows() - 1, M.ncols() - 1, self._one)
2102
E = self._E + (element,)
2103
D = {}
2104
for i from 0 <= i < self.size():
2105
D[E[i]] = i
2106
for cochain in cochains:
2107
for i from 0 <= i < M.ncols() - 1:
2108
M.set_unsafe(M.nrows() - 1, i, 0)
2109
for e in cochain:
2110
M.set_unsafe(M.nrows() - 1, D[e], -cochain[e])
2111
coext.append(type(self)(matrix=M, groundset=E))
2112
return coext
2113
2114
cdef _extend_chains(self, C, f, fundamentals=None):
2115
r"""
2116
Extend a list of chains for ``self / f`` to a list of chains for
2117
``self``.
2118
2119
See :meth:`linear_extension_chains` for full documentation.
2120
"""
2121
# assumes connected self, non-loop f, chains with basic support
2122
R = self.base_ring()
2123
res = []
2124
for c in C:
2125
if len(set(c.keys())) == 0:
2126
values = [self._one]
2127
else:
2128
if fundamentals is None:
2129
values = R
2130
else: # generate only chains that satisfy shallow test for 'crossratios in fundamentals'
2131
T = set(c.keys())
2132
if not self._is_independent(T | set([f])):
2133
raise ValueError("_extend_chains can only extend chains with basic support")
2134
self._move_current_basis(T | set([f]), set())
2135
B = self.basis()
2136
mult = {f: self._one}
2137
mult2 = {}
2138
todo = set([f])
2139
todo2 = set()
2140
while len(todo) > 0 or len(todo2) > 0:
2141
while len(todo) > 0:
2142
r = todo.pop()
2143
cocirc = self.fundamental_cocycle(B, r)
2144
for s, v in cocirc.iteritems():
2145
if s != r and s not in mult2:
2146
mult2[s] = mult[r] * v
2147
todo2.add(s)
2148
while len(todo2) > 0:
2149
s = todo2.pop()
2150
circ = self.fundamental_cycle(B, s)
2151
for t, w in circ.iteritems():
2152
if t != s and t not in mult:
2153
mult[t] = mult2[s] / w
2154
if t not in T:
2155
todo.add(t)
2156
T2 = set(mult.keys()) & T
2157
t = T2.pop()
2158
m = -mult[t] * c[t]
2159
values = set([fund * m for fund in fundamentals])
2160
while len(T2) > 0:
2161
t = T2.pop()
2162
m = -mult[t] * c[t]
2163
values &= set([fund * m for fund in fundamentals])
2164
for x in values:
2165
if x != 0:
2166
cp = c.copy()
2167
cp[f] = x
2168
res.append(cp)
2169
res.append(c)
2170
ne = newlabel(self._E)
2171
if fundamentals is not None and self.full_rank() > 1:
2172
hyperlines = [F for F in self.flats(self.full_rank() - 2) if self._line_length(F) > 2]
2173
res = [chain for chain in res if len(chain) < 2 or self._linear_extensions(ne, [chain])[0]._cross_ratio_test(ne, fundamentals, hyperlines)]
2174
return res
2175
2176
cpdef _linear_extension_chains(self, F, fundamentals=None): # assumes independent F
2177
r"""
2178
Create a list of chains that determine single-element extensions of
2179
this linear matroid representation.
2180
2181
.. WARNING::
2182
2183
Intended for internal use; does no input checking.
2184
2185
INPUT:
2186
2187
- ``F`` -- an independent set of elements.
2188
- ``fundamentals`` -- (default: ``None``) a set elements of the base
2189
ring.
2190
2191
OUTPUT:
2192
2193
A list of chains, so each single-element extension of this linear
2194
matroid, with support contained in ``F``, is given by one of these
2195
chains.
2196
2197
EXAMPLES::
2198
2199
sage: M = Matroid(reduced_matrix=Matrix(GF(2), [[1, 1, 0],
2200
....: [1, 0, 1], [0, 1, 1]]))
2201
sage: len(M._linear_extension_chains(F=set([0, 1, 2])))
2202
8
2203
sage: M._linear_extension_chains(F=set())
2204
[{}]
2205
sage: M._linear_extension_chains(F=set([1]))
2206
[{}, {1: 1}]
2207
sage: len(M._linear_extension_chains(F=set([0, 1])))
2208
4
2209
sage: N = Matroid(ring=QQ, reduced_matrix=[[1, 1, 0],
2210
....: [1, 0, 1], [0, 1, 1]])
2211
sage: N._linear_extension_chains(F=set([0, 1]),
2212
....: fundamentals=set([1, -1, 1/2, 2]))
2213
[{0: 1}, {}, {0: 1, 1: 1}, {0: -1, 1: 1}, {1: 1}]
2214
"""
2215
2216
if len(F) == 0:
2217
return [{}]
2218
if len(F) == 1:
2219
return [{}, {min(F): self._one}]
2220
C = self.components()
2221
if len(C) == 1:
2222
for f in F:
2223
sf = set([f])
2224
ff = self._closure(sf)
2225
M = self._minor(contractions=sf, deletions=ff - sf)
2226
if M.is_connected():
2227
break
2228
FM = F & M.groundset()
2229
chains = M._linear_extension_chains(FM, fundamentals)
2230
chains = self._extend_chains(chains, f, fundamentals)
2231
else:
2232
comp_chains = {} # make chains for each component
2233
for comp in C:
2234
FM = F & comp
2235
A = self._max_independent(self.groundset() - comp)
2236
B = self.groundset() - (comp | A)
2237
M = self._minor(deletions=B, contractions=A)
2238
M._forget()
2239
comp_chains[comp] = M._linear_extension_chains(FM, fundamentals)
2240
2241
chains = [{}] # make cartesian product of component chains
2242
for comp in comp_chains:
2243
new_chains = []
2244
for c in chains:
2245
for d in comp_chains[comp]:
2246
c_new = copy(c)
2247
c_new.update(d)
2248
new_chains.append(c_new)
2249
chains = new_chains
2250
return chains
2251
2252
cpdef linear_extension_chains(self, F=None, simple=False, fundamentals=None):
2253
r"""
2254
Create a list of chains that determine the single-element extensions
2255
of this linear matroid representation.
2256
2257
A *chain* is a dictionary, mapping elements from the groundset to
2258
elements of the base ring, indicating a linear combination of columns
2259
to form the new column. Think of chains as vectors, only independent
2260
of representation.
2261
2262
INPUT:
2263
2264
- ``F`` -- (default: ``self.groundset()``) a subset of the groundset.
2265
- ``simple`` -- (default: ``False``) a boolean variable.
2266
- ``fundamentals`` -- (default: ``None``) a set elements of the base
2267
ring.
2268
2269
OUTPUT:
2270
2271
A list of chains, so each single-element extension of this linear
2272
matroid representation is given by one of these chains.
2273
2274
If one or more of the above inputs is given, the list is restricted to
2275
chains
2276
2277
- so that the support of each chain lies in ``F``, if given
2278
- so that the chain does not generate a parallel extension or loop, if
2279
``simple = True``
2280
- so that in the extension generated by this chain, the cross ratios
2281
are restricted to ``fundamentals``, if given.
2282
2283
.. SEEALSO::
2284
2285
:meth:`M.linear_extension() <LinearMatroid.linear_extension>`,
2286
:meth:`M.linear_extensions() <LinearMatroid.linear_extensions>`,
2287
:meth:`M.cross_ratios() <LinearMatroid.cross_ratios>`
2288
2289
EXAMPLES::
2290
2291
sage: M = Matroid(reduced_matrix=Matrix(GF(2),
2292
....: [[1, 1, 0], [1, 0, 1], [0, 1, 1]]))
2293
sage: len(M.linear_extension_chains())
2294
8
2295
sage: len(M.linear_extension_chains(F=[0, 1]))
2296
4
2297
sage: len(M.linear_extension_chains(F=[0, 1], simple=True))
2298
0
2299
sage: M.linear_extension_chains(F=[0, 1, 2], simple=True)
2300
[{0: 1, 1: 1, 2: 1}]
2301
sage: N = Matroid(ring=QQ,
2302
....: reduced_matrix=[[-1, -1, 0], [1, 0, -1], [0, 1, 1]])
2303
sage: N.linear_extension_chains(F=[0, 1], simple=True,
2304
....: fundamentals=set([1, -1, 1/2, 2]))
2305
[{0: 1, 1: 1}, {0: -1/2, 1: 1}, {0: -2, 1: 1}]
2306
"""
2307
if F is None:
2308
FI = self.basis()
2309
else:
2310
FI = self.max_independent(F)
2311
M = self._minor(contractions=set(), deletions=self.loops())
2312
M._forget() # this is necessary for testing the crossratios of the extension
2313
# --> skips gauss-jordan reduction when taking minors of M
2314
# TODO: maybe make this an optional argument for _minor?
2315
# TODO: make sure the _representation isn't accidentally recreated anywhere (currently this only happens when self.representation() is called)
2316
chains = M._linear_extension_chains(FI, fundamentals)
2317
2318
if simple: # filter out chains that produce a parallel element,
2319
par = [] # test uses that each supp(chain) is independent
2320
self._move_current_basis(FI, set())
2321
B = self.basis()
2322
for e in self.groundset() - B:
2323
C = self.fundamental_cycle(B, e)
2324
C.pop(e)
2325
par.append(C)
2326
simple_chains = []
2327
for c in chains:
2328
if len(c) < 2:
2329
continue
2330
parallel = False
2331
for p in par:
2332
if set(p.keys()) == set(c.keys()):
2333
parallel = True
2334
e = min(p)
2335
ratio = c[e] / p[e]
2336
for f, w in p.iteritems():
2337
if c[f] / w != ratio:
2338
parallel = False
2339
break
2340
if parallel:
2341
break
2342
if not parallel:
2343
simple_chains.append(c)
2344
chains = simple_chains
2345
return chains
2346
2347
cpdef linear_coextension_cochains(self, F=None, cosimple=False, fundamentals=None):
2348
r"""
2349
Create a list of cochains that determine the single-element
2350
coextensions of this linear matroid representation.
2351
2352
A cochain is a dictionary, mapping elements from the groundset to
2353
elements of the base ring. If `A` represents the current matroid, then
2354
the coextension is given by `N = M([A 0; -c 1])`, with the entries of
2355
`c` given by the cochain. Note that the matroid does not change when
2356
row operations are carried out on `A`.
2357
2358
INPUT:
2359
2360
- ``F`` -- (default: ``self.groundset()``) a subset of the groundset.
2361
- ``cosimple`` -- (default: ``False``) a boolean variable.
2362
- ``fundamentals`` -- (default: ``None``) a set elements of the base
2363
ring.
2364
2365
OUTPUT:
2366
2367
A list of cochains, so each single-element coextension of this linear
2368
matroid representation is given by one of these cochains.
2369
2370
If one or more of the above inputs is given, the list is restricted to
2371
chains
2372
2373
- so that the support of each cochain lies in ``F``, if given
2374
- so that the cochain does not generate a series extension or coloop,
2375
if ``cosimple = True``
2376
- so that in the coextension generated by this cochain, the cross
2377
ratios are restricted to ``fundamentals``, if given.
2378
2379
.. SEEALSO::
2380
2381
:meth:`M.linear_coextension() <LinearMatroid.linear_coextension>`,
2382
:meth:`M.linear_coextensions() <LinearMatroid.linear_coextensions>`,
2383
:meth:`M.cross_ratios() <LinearMatroid.cross_ratios>`
2384
2385
EXAMPLES::
2386
2387
sage: M = Matroid(reduced_matrix=Matrix(GF(2),
2388
....: [[1, 1, 0], [1, 0, 1], [0, 1, 1]]))
2389
sage: len(M.linear_coextension_cochains())
2390
8
2391
sage: len(M.linear_coextension_cochains(F=[0, 1]))
2392
4
2393
sage: len(M.linear_coextension_cochains(F=[0, 1], cosimple=True))
2394
0
2395
sage: M.linear_coextension_cochains(F=[3, 4, 5], cosimple=True)
2396
[{3: 1, 4: 1, 5: 1}]
2397
sage: N = Matroid(ring=QQ,
2398
....: reduced_matrix=[[-1, -1, 0], [1, 0, -1], [0, 1, 1]])
2399
sage: N.linear_coextension_cochains(F=[0, 1], cosimple=True,
2400
....: fundamentals=set([1, -1, 1/2, 2]))
2401
[{0: 2, 1: 1}, {0: 1/2, 1: 1}, {0: -1, 1: 1}]
2402
"""
2403
return self.dual().linear_extension_chains(F=F, simple=cosimple, fundamentals=fundamentals)
2404
2405
cpdef linear_extensions(self, element=None, F=None, simple=False, fundamentals=None):
2406
r"""
2407
Create a list of linear matroids represented by single-element
2408
extensions of this linear matroid representation.
2409
2410
INPUT:
2411
2412
- ``element`` -- (default: ``None``) the name of the new element of
2413
the groundset.
2414
- ``F`` -- (default: ``None``) a subset of the ground set.
2415
- ``simple`` -- (default: ``False``) a boolean variable.
2416
- ``fundamentals`` -- (default: ``None``) a set elements of the base
2417
ring.
2418
2419
OUTPUT:
2420
2421
A list of linear matroids represented by single-element extensions of
2422
this linear matroid representation.
2423
2424
If one or more of the above inputs is given, the list is restricted to
2425
matroids
2426
2427
- so that the new element is spanned by ``F``, if given
2428
- so that the new element is not a loop or in a parallel pair, if
2429
``simple=True``
2430
- so that in the representation of the extension, the cross ratios are
2431
restricted to ``fundamentals``, if given. Note that it is assumed
2432
that the cross ratios of the input matroid already satisfy this
2433
condition.
2434
2435
.. SEEALSO::
2436
2437
:meth:`M.linear_extension() <LinearMatroid.linear_extension>`,
2438
:meth:`M.linear_extension_chains() <LinearMatroid.linear_extension_chains>`,
2439
:meth:`M.cross_ratios() <LinearMatroid.cross_ratios>`
2440
2441
EXAMPLES::
2442
2443
sage: M = Matroid(ring=GF(2),
2444
....: reduced_matrix=[[-1, 0, 1], [1, -1, 0], [0, 1, -1]])
2445
sage: len(M.linear_extensions())
2446
8
2447
sage: S = M.linear_extensions(simple=True)
2448
sage: S
2449
[Binary matroid of rank 3 on 7 elements, type (3, 0)]
2450
sage: S[0].is_field_isomorphic(matroids.named_matroids.Fano())
2451
True
2452
sage: M = Matroid(ring=QQ,
2453
....: reduced_matrix=[[1, 0, 1], [1, 1, 0], [0, 1, 1]])
2454
sage: S = M.linear_extensions(simple=True,
2455
....: fundamentals=[1, -1, 1/2, 2])
2456
sage: len(S)
2457
7
2458
sage: any(N.is_isomorphic(matroids.named_matroids.NonFano())
2459
....: for N in S)
2460
True
2461
sage: len(M.linear_extensions(simple=True,
2462
....: fundamentals=[1, -1, 1/2, 2], F=[0, 1]))
2463
1
2464
"""
2465
if element is None:
2466
element = newlabel(self.groundset())
2467
else:
2468
if element in self.groundset():
2469
raise ValueError("cannot extend by element already in groundset")
2470
chains = self.linear_extension_chains(F, simple=simple, fundamentals=fundamentals)
2471
return self._linear_extensions(element, chains)
2472
2473
cpdef linear_coextensions(self, element=None, F=None, cosimple=False, fundamentals=None):
2474
r"""
2475
Create a list of linear matroids represented by single-element
2476
coextensions of this linear matroid representation.
2477
2478
INPUT:
2479
2480
- ``element`` -- (default: ``None``) the name of the new element of
2481
the groundset.
2482
- ``F`` -- (default: ``None``) a subset of the ground set.
2483
- ``cosimple`` -- (default: ``False``) a boolean variable.
2484
- ``fundamentals`` -- (default: ``None``) a set elements of the base
2485
ring.
2486
2487
OUTPUT:
2488
2489
A list of linear matroids represented by single-element coextensions
2490
of this linear matroid representation.
2491
2492
If one or more of the above inputs is given, the list is restricted to
2493
coextensions
2494
2495
- so that the new element lies in the cospan of ``F``, if given.
2496
- so that the new element is no coloop and is not in series with
2497
another element, if ``cosimple = True``.
2498
- so that in the representation of the coextension, the cross ratios
2499
are restricted to ``fundamentals``, if given. Note that it is
2500
assumed that the cross ratios of the input matroid already satisfy
2501
this condition.
2502
2503
.. SEEALSO::
2504
2505
:meth:`M.linear_coextension() <LinearMatroid.linear_coextension>`,
2506
:meth:`M.linear_coextension_cochains() <LinearMatroid.linear_coextension_cochains>`,
2507
:meth:`M.cross_ratios() <LinearMatroid.cross_ratios>`
2508
2509
EXAMPLES::
2510
2511
sage: M = Matroid(ring=GF(2),
2512
....: reduced_matrix=[[-1, 0, 1], [1, -1, 0], [0, 1, -1]])
2513
sage: len(M.linear_coextensions())
2514
8
2515
sage: S = M.linear_coextensions(cosimple=True)
2516
sage: S
2517
[Binary matroid of rank 4 on 7 elements, type (3, 7)]
2518
sage: F7 = matroids.named_matroids.Fano()
2519
sage: S[0].is_field_isomorphic(F7.dual())
2520
True
2521
sage: M = Matroid(ring=QQ,
2522
....: reduced_matrix=[[1, 0, 1], [1, 1, 0], [0, 1, 1]])
2523
sage: S = M.linear_coextensions(cosimple=True,
2524
....: fundamentals=[1, -1, 1/2, 2])
2525
sage: len(S)
2526
7
2527
sage: NF7 = matroids.named_matroids.NonFano()
2528
sage: any(N.is_isomorphic(NF7.dual()) for N in S)
2529
True
2530
sage: len(M.linear_coextensions(cosimple=True,
2531
....: fundamentals=[1, -1, 1/2, 2],
2532
....: F=[3, 4]))
2533
1
2534
"""
2535
if element is None:
2536
element = newlabel(self.groundset())
2537
else:
2538
if element in self.groundset():
2539
raise ValueError("cannot extend by element already in groundset")
2540
cochains = self.linear_coextension_cochains(F, cosimple=cosimple, fundamentals=fundamentals)
2541
return self._linear_coextensions(element, cochains)
2542
2543
cpdef is_valid(self):
2544
r"""
2545
Test if the data represent an actual matroid.
2546
2547
Since this matroid is linear, we test the representation matrix.
2548
2549
OUTPUT:
2550
2551
- ``True`` if the matrix is over a field.
2552
- ``True`` if the matrix is over a ring and all cross ratios are
2553
invertible.
2554
- ``False`` otherwise.
2555
2556
.. NOTE::
2557
2558
This function does NOT test if the cross ratios are contained in
2559
the appropriate set of fundamentals. To that end, use
2560
2561
``M.cross_ratios().issubset(F)``
2562
2563
where ``F`` is the set of fundamentals.
2564
2565
.. SEEALSO::
2566
2567
:meth:`M.cross_ratios() <LinearMatroid.cross_ratios>`
2568
2569
EXAMPLES::
2570
2571
sage: M = Matroid(ring=QQ, reduced_matrix=Matrix(ZZ,
2572
....: [[1, 0, 1], [1, 1, 0], [0, 1, 1]]))
2573
sage: M.is_valid()
2574
True
2575
sage: from sage.matroids.advanced import * # LinearMatroid
2576
sage: M = LinearMatroid(ring=ZZ, reduced_matrix=Matrix(ZZ,
2577
....: [[1, 0, 1], [1, 1, 0], [0, 1, 1]]))
2578
sage: M.is_valid()
2579
False
2580
"""
2581
if self.base_ring().is_field():
2582
return True
2583
try:
2584
CR = self.cross_ratios()
2585
except (ArithmeticError, TypeError, ValueError):
2586
return False
2587
for x in CR:
2588
if not x ** (-1) in self.base_ring():
2589
return False
2590
return True
2591
2592
# Copying, loading, saving
2593
2594
def __copy__(self):
2595
"""
2596
Create a shallow copy.
2597
2598
EXAMPLES::
2599
2600
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
2601
....: [0, 0, 1, 1, 3]]))
2602
sage: N = copy(M) # indirect doctest
2603
sage: M == N
2604
True
2605
"""
2606
cdef LinearMatroid N
2607
if self._representation is not None:
2608
N = LinearMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True)
2609
else:
2610
rows, cols = self._current_rows_cols()
2611
N = LinearMatroid(groundset=rows + cols, reduced_matrix=self._A)
2612
N.rename(getattr(self, '__custom_name'))
2613
return N
2614
2615
def __deepcopy__(self, memo):
2616
"""
2617
Create a deep copy.
2618
2619
EXAMPLES::
2620
2621
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
2622
....: [0, 0, 1, 1, 3]]))
2623
sage: N = deepcopy(M) # indirect doctest
2624
sage: M == N
2625
True
2626
"""
2627
cdef LinearMatroid N
2628
if self._representation is not None:
2629
N = LinearMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True)
2630
else:
2631
rows, cols = self._current_rows_cols()
2632
N = LinearMatroid(groundset=deepcopy(rows + cols, memo), reduced_matrix=deepcopy(self._A, memo))
2633
N.rename(deepcopy(getattr(self, '__custom_name'), memo))
2634
return N
2635
2636
def __reduce__(self):
2637
"""
2638
Save the matroid for later reloading.
2639
2640
OUTPUT:
2641
2642
A tuple ``(unpickle_lean_linear_matroid, (version, data))``, where
2643
``unpickle_lean_linear_matroid`` is the name of a function that, when
2644
called with ``(version, data)``, produces a matroid isomorphic to
2645
``self``. ``version`` is an integer (currently 0) and ``data`` is a
2646
tuple ``(A, E, reduced, name)`` where ``A`` is the representation
2647
matrix, ``E`` is the groundset of the matroid, ``reduced`` is a
2648
boolean indicating whether ``A`` is a reduced matrix, and ``name`` is
2649
a custom name.
2650
2651
.. WARNING::
2652
2653
Users should never call this function directly.
2654
2655
EXAMPLES::
2656
2657
sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
2658
....: [0, 0, 1, 1, 3]]))
2659
sage: M == loads(dumps(M)) # indirect doctest
2660
True
2661
sage: M.rename("U35")
2662
sage: loads(dumps(M))
2663
U35
2664
sage: M = Matroid(Matrix(GF(7), [[1, 0, 1], [1, 0, 1]]))
2665
sage: N = loads(dumps(M))
2666
sage: N.representation()
2667
[1 0 1]
2668
[1 0 1]
2669
"""
2670
import sage.matroids.unpickling
2671
cdef LeanMatrix A
2672
version = 0
2673
if self._representation is not None:
2674
A = self._representation
2675
gs = self._E
2676
reduced = False
2677
else:
2678
A = self._reduced_representation()
2679
rows, cols = self._current_rows_cols()
2680
gs = rows + cols
2681
reduced = True
2682
data = (A, gs, reduced, getattr(self, '__custom_name'))
2683
return sage.matroids.unpickling.unpickle_linear_matroid, (version, data)
2684
2685
# Binary matroid
2686
2687
cdef class BinaryMatroid(LinearMatroid):
2688
r"""
2689
Binary matroids.
2690
2691
A binary matroid is a linear matroid represented over the finite field
2692
with two elements. See :class:`LinearMatroid` for a definition.
2693
2694
The simplest way to create a ``BinaryMatroid`` is by giving only a matrix
2695
`A`. Then, the groundset defaults to ``range(A.ncols())``. Any iterable
2696
object `E` can be given as a groundset. If `E` is a list, then ``E[i]``
2697
will label the `i`-th column of `A`. Another possibility is to specify a
2698
*reduced* matrix `B`, to create the matroid induced by `A = [ I B ]`.
2699
2700
INPUT:
2701
2702
- ``matrix`` -- (default: ``None``) a matrix whose column vectors
2703
represent the matroid.
2704
- ``reduced_matrix`` -- (default: ``None``) a matrix `B` such that
2705
`[I\ \ B]` represents the matroid, where `I` is an identity matrix with
2706
the same number of rows as `B`. Only one of ``matrix`` and
2707
``reduced_matrix`` should be provided.
2708
- ``groundset`` -- (default: ``None``) an iterable containing the element
2709
labels. When provided, must have the correct number of elements: the
2710
number of columns of ``matrix`` or the number of rows plus the number
2711
of columns of ``reduced_matrix``.
2712
- ``ring`` -- (default: ``None``) ignored.
2713
- ``keep_initial_representation`` -- (default: ``True``) decides whether
2714
or not an internal copy of the input matrix should be preserved. This
2715
can help to see the structure of the matroid (e.g. in the case of
2716
graphic matroids), and makes it easier to look at extensions. However,
2717
the input matrix may have redundant rows, and sometimes it is desirable
2718
to store only a row-reduced copy.
2719
- ``basis`` -- (default: ``None``) When provided, this is an ordered
2720
subset of ``groundset``, such that the submatrix of ``matrix`` indexed
2721
by ``basis`` is an identity matrix. In this case, no row reduction takes
2722
place in the initialization phase.
2723
2724
OUTPUT:
2725
2726
A :class:`BinaryMatroid` instance based on the data above.
2727
2728
.. NOTE::
2729
2730
An indirect way to generate a binary matroid is through the
2731
:func:`Matroid() <sage.matroids.constructor.Matroid>` function. This
2732
is usually the preferred way, since it automatically chooses between
2733
:class:`BinaryMatroid` and other classes. For direct access to the
2734
``BinaryMatroid`` constructor, run::
2735
2736
sage: from sage.matroids.advanced import *
2737
2738
EXAMPLES::
2739
2740
sage: A = Matrix(GF(2), 2, 4, [[1, 0, 1, 1], [0, 1, 1, 1]])
2741
sage: M = Matroid(A)
2742
sage: M
2743
Binary matroid of rank 2 on 4 elements, type (0, 6)
2744
sage: sorted(M.groundset())
2745
[0, 1, 2, 3]
2746
sage: Matrix(M)
2747
[1 0 1 1]
2748
[0 1 1 1]
2749
sage: M = Matroid(matrix=A, groundset='abcd')
2750
sage: sorted(M.groundset())
2751
['a', 'b', 'c', 'd']
2752
sage: B = Matrix(GF(2), 2, 2, [[1, 1], [1, 1]])
2753
sage: N = Matroid(reduced_matrix=B, groundset='abcd')
2754
sage: M == N
2755
True
2756
"""
2757
def __init__(self, matrix=None, groundset=None, reduced_matrix=None, ring=None, keep_initial_representation=True, basis=None):
2758
"""
2759
See class definition for full documentation.
2760
2761
.. NOTE::
2762
2763
The extra argument ``basis``, when provided, is an ordered list of
2764
elements of the groundset, ordered such that they index a standard
2765
identity matrix within ``matrix``.
2766
2767
EXAMPLES::
2768
2769
sage: from sage.matroids.advanced import *
2770
sage: BinaryMatroid(matrix=Matrix(GF(5), [[1, 0, 1, 1, 1],
2771
....: [0, 1, 1, 2, 3]])) # indirect doctest
2772
Binary matroid of rank 2 on 5 elements, type (1, 7)
2773
"""
2774
cdef BinaryMatrix A
2775
cdef long r, c
2776
cdef list P
2777
global GF2, GF2_zero, GF2_one, GF2_not_defined
2778
if GF2_not_defined:
2779
GF2 = GF(2)
2780
GF2_zero = GF2(0)
2781
GF2_one = GF2(1)
2782
GF2_not_defined = False
2783
2784
# Setup representation; construct displayed basis
2785
if matrix is not None:
2786
A = BinaryMatrix(matrix.nrows(), matrix.ncols(), M=matrix)
2787
if keep_initial_representation:
2788
self._representation = A.copy() # Deprecated Sage matrix operation
2789
if basis is None:
2790
P = gauss_jordan_reduce(A, xrange(A.ncols()))
2791
A.resize(len(P)) # Not a Sage matrix operation
2792
self._A = A
2793
else:
2794
A = BinaryMatrix(reduced_matrix.nrows(), reduced_matrix.ncols(), M=reduced_matrix)
2795
P = range(A.nrows())
2796
self._A = A.prepend_identity() # Not a Sage matrix operation
2797
2798
# Setup groundset, BasisExchangeMatroid data
2799
if groundset is None:
2800
groundset = range(self._A.ncols())
2801
else:
2802
if len(groundset) != self._A.ncols():
2803
raise ValueError("size of groundset does not match size of matrix")
2804
if basis is None:
2805
bas = [groundset[i] for i in P]
2806
else:
2807
bas = basis
2808
BasisExchangeMatroid.__init__(self, groundset, bas)
2809
2810
# Setup index of displayed basis
2811
self._prow = <long* > sage_malloc((self._A.ncols()) * sizeof(long))
2812
for c in xrange(self._A.ncols()):
2813
self._prow[c] = -1
2814
if matrix is not None:
2815
if basis is None:
2816
for r in xrange(len(P)):
2817
self._prow[P[r]] = r
2818
else:
2819
for r in xrange(self._A.nrows()):
2820
self._prow[self._idx[basis[r]]] = r
2821
else:
2822
for r from 0 <= r < self._A.nrows():
2823
self._prow[r] = r
2824
2825
L = []
2826
for i from 0 <= i < self._A.ncols():
2827
L.append(self._prow[i])
2828
self._zero = GF2_zero
2829
self._one = GF2_one
2830
2831
cpdef base_ring(self):
2832
"""
2833
Return the base ring of the matrix representing the matroid,
2834
in this case `\GF{2}`.
2835
2836
EXAMPLES::
2837
2838
sage: M = matroids.named_matroids.Fano()
2839
sage: M.base_ring()
2840
Finite Field of size 2
2841
"""
2842
global GF2
2843
return GF2
2844
2845
cpdef characteristic(self):
2846
"""
2847
Return the characteristic of the base ring of the matrix representing
2848
the matroid, in this case `2`.
2849
2850
EXAMPLES::
2851
2852
sage: M = matroids.named_matroids.Fano()
2853
sage: M.characteristic()
2854
2
2855
"""
2856
return 2
2857
2858
cdef bint __is_exchange_pair(self, long x, long y):
2859
r"""
2860
Check if ``self.basis() - x + y`` is again a basis. Internal method.
2861
"""
2862
return (<BinaryMatrix>self._A).get(self._prow[x], y) # Not a Sage matrix operation
2863
2864
cdef bint __exchange(self, long x, long y):
2865
r"""
2866
Replace ``self.basis() with ``self.basis() - x + y``. Internal method, does no checks.
2867
"""
2868
cdef long p = self._prow[x]
2869
self._A.pivot(p, y) # Not a Sage matrix operation
2870
self._prow[y] = p
2871
BasisExchangeMatroid.__exchange(self, x, y)
2872
2873
cdef __fundamental_cocircuit(self, bitset_t C, long x):
2874
r"""
2875
Fill bitset `C` with the incidence vector of the `B`-fundamental cocircuit using ``x``. Internal method using packed elements.
2876
"""
2877
bitset_copy(C, (<BinaryMatrix>self._A)._M[self._prow[x]])
2878
2879
cdef __exchange_value(self, long x, long y):
2880
r"""
2881
Return the (x, y) entry of the current representation.
2882
"""
2883
if (<BinaryMatrix>self._A).get(self._prow[x], y): # Not a Sage matrix operation
2884
return self._one
2885
else:
2886
return self._zero
2887
2888
def _repr_(self):
2889
"""
2890
Return a string representation of ``self``.
2891
2892
The type consists of :meth:`BinaryMatroid.bicycle_dimension` and
2893
:meth:`BinaryMatroid.brown_invariant`.
2894
2895
EXAMPLES::
2896
2897
sage: M = matroids.named_matroids.Fano()
2898
sage: M.rename()
2899
sage: repr(M) # indirect doctest
2900
'Binary matroid of rank 3 on 7 elements, type (3, 0)'
2901
"""
2902
S = "Binary matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements, type (" + str(self.bicycle_dimension()) + ', ' + str(self.brown_invariant()) + ')'
2903
return S
2904
2905
cpdef _current_rows_cols(self, B=None):
2906
"""
2907
Return the current row and column labels of a reduced matrix.
2908
2909
INPUT:
2910
2911
- ``B`` -- (default: ``None``) If provided, first find a basis having
2912
maximal intersection with ``B``.
2913
2914
OUTPUT:
2915
2916
- ``R`` -- A list of row indices; corresponds to the currently used
2917
internal basis
2918
- ``C`` -- A list of column indices; corresponds to the complement of
2919
the current internal basis
2920
2921
EXAMPLES::
2922
2923
sage: M = matroids.named_matroids.Fano()
2924
sage: A = M._reduced_representation('efg')
2925
sage: R, C = M._current_rows_cols()
2926
sage: (sorted(R), sorted(C))
2927
(['e', 'f', 'g'], ['a', 'b', 'c', 'd'])
2928
sage: R, C = M._current_rows_cols(B='abg')
2929
sage: (sorted(R), sorted(C))
2930
(['a', 'b', 'g'], ['c', 'd', 'e', 'f'])
2931
2932
"""
2933
if B is not None:
2934
self._move_current_basis(B, set())
2935
basis = self.basis()
2936
rows = [0] * self.full_rank()
2937
cols = [0] * self.full_corank()
2938
c = 0
2939
for e in self._E:
2940
if e in basis:
2941
rows[self._prow[self._idx[e]]] = e
2942
else:
2943
cols[c] = e
2944
c += 1
2945
return rows, cols
2946
2947
cpdef LeanMatrix _basic_representation(self, B=None):
2948
"""
2949
Return a basic matrix representation of the matroid.
2950
2951
INPUT:
2952
2953
- ``B`` -- (default: ``None``) a set of elements of the groundset.
2954
2955
OUTPUT:
2956
2957
A matrix `M` representing the matroid, where `M[B'] = I` for a basis
2958
`B'` that maximally intersects the given set `B`. If not provided, the
2959
current basis used internally is chosen for `B'`. For a stable
2960
representation, use ``self.representation()``.
2961
2962
.. NOTE::
2963
2964
The method self.groundset_list() gives the labelling of the
2965
columns by the elements of the matroid. The matrix returned
2966
is a LeanMatrix subclass, which is intended for internal use
2967
only. Use the ``representation()`` method to get a Sage matrix.
2968
2969
EXAMPLES::
2970
2971
sage: M = matroids.named_matroids.Fano()
2972
sage: M._basic_representation()
2973
3 x 7 BinaryMatrix
2974
[1000111]
2975
[0101011]
2976
[0011101]
2977
sage: matrix(M._basic_representation('efg'))
2978
[1 1 0 1 1 0 0]
2979
[1 0 1 1 0 1 0]
2980
[1 1 1 0 0 0 1]
2981
"""
2982
if B is not None:
2983
self._move_current_basis(B, set())
2984
return self._A.copy() # Deprecated Sage matrix operation
2985
2986
cpdef LeanMatrix _reduced_representation(self, B=None):
2987
"""
2988
Return a reduced representation of the matroid, i.e. a matrix `R` such
2989
that `[I\ \ R]` represents the matroid.
2990
2991
INPUT:
2992
2993
- ``B`` -- (default: ``None``) a set of elements of the groundset.
2994
2995
OUTPUT:
2996
2997
A matrix `R` forming a reduced representation of the matroid, with
2998
rows labeled by a basis `B'` that maximally intersects the given set
2999
`B`. If not provided, the current basis used internally labels the
3000
rows.
3001
3002
.. NOTE::
3003
3004
The matrix returned is a LeanMatrix subclass, which is intended
3005
for internal use only. Use the ``representation()`` method to get
3006
a Sage matrix.
3007
3008
EXAMPLES::
3009
3010
sage: M = matroids.named_matroids.Fano()
3011
sage: M._reduced_representation()
3012
3 x 4 BinaryMatrix
3013
[0111]
3014
[1011]
3015
[1101]
3016
sage: matrix(M._reduced_representation('efg'))
3017
[1 1 0 1]
3018
[1 0 1 1]
3019
[1 1 1 0]
3020
"""
3021
if B is not None:
3022
self._move_current_basis(B, set())
3023
rows, cols = self._current_rows_cols()
3024
return self._A.matrix_from_rows_and_columns(range(self.full_rank()), [self._idx[e] for e in cols])
3025
3026
# isomorphism
3027
3028
cpdef _is_isomorphic(self, other):
3029
"""
3030
Test if ``self`` is isomorphic to ``other``.
3031
3032
Internal version that performs no checks on input.
3033
3034
INPUT:
3035
3036
- ``other`` -- A matroid.
3037
3038
OUTPUT:
3039
3040
Boolean.
3041
3042
EXAMPLES::
3043
3044
sage: M1 = matroids.named_matroids.Fano()
3045
sage: M2 = Matroid(ring=GF(2),
3046
....: reduced_matrix=[[1, 0, 1, 1], [0, 1, 1, 1], [1, 1, 0, 1]])
3047
sage: M1._is_isomorphic(M2)
3048
True
3049
3050
sage: M1 = matroids.named_matroids.Fano().delete('a')
3051
sage: M2 = matroids.Whirl(3)
3052
sage: M1._is_isomorphic(M2)
3053
False
3054
sage: M1._is_isomorphic(matroids.Wheel(3))
3055
True
3056
"""
3057
if type(other) == BinaryMatroid:
3058
return self.is_field_isomorphic(other)
3059
else:
3060
return LinearMatroid._is_isomorphic(self, other)
3061
3062
# invariants
3063
cpdef _make_invariant(self):
3064
"""
3065
Create an invariant.
3066
3067
Internal method; see ``_invariant`` for full documentation.
3068
3069
EXAMPLES::
3070
3071
sage: M = matroids.named_matroids.Fano()
3072
sage: M._invariant() # indirect doctest
3073
(3, 0, 7, 0, 0, 0, 0, 0)
3074
"""
3075
cdef BinaryMatrix B
3076
cdef long r, d, i, j
3077
if self._b_invariant is not None:
3078
return
3079
B = (<BinaryMatrix>self._A).copy() # Deprecated Sage matrix operation
3080
r = B.nrows()
3081
b = 0
3082
d = 0
3083
i = 0
3084
U = set()
3085
while i + d < r:
3086
for j in xrange(i, r - d):
3087
if B.row_len(j) % 2 == 1: # Not a Sage matrix operation
3088
B.swap_rows_c(i, j)
3089
break
3090
if B.row_len(i) % 2 == 1: # Not a Sage matrix operation
3091
for j in xrange(i + 1, r - d):
3092
if B.row_inner_product(i, j): # Not a Sage matrix operation
3093
B.add_multiple_of_row_c(j, i, 1, 0)
3094
if B.row_len(i) % 4 == 1: # Not a Sage matrix operation
3095
b += 1
3096
else:
3097
b -= 1
3098
U.add(i)
3099
i += 1
3100
else:
3101
for j in xrange(i + 1, r - d):
3102
if B.row_inner_product(i, j): # Not a Sage matrix operation
3103
B.swap_rows_c(i + 1, j)
3104
break
3105
if i + 1 < r - d:
3106
if B.row_inner_product(i, i + 1): # Not a Sage matrix operation
3107
for j in xrange(i + 2, r):
3108
if B.row_inner_product(i, j): # Not a Sage matrix operation
3109
B.add_multiple_of_row_c(j, i + 1, 1, 0)
3110
if B.row_inner_product(i + 1, j): # Not a Sage matrix operation
3111
B.add_multiple_of_row_c(j, i, 1, 0)
3112
if B.row_len(i) % 4 == 2 and B.row_len(i + 1) % 4 == 2: # Not a Sage matrix operation
3113
b += 4
3114
i += 2
3115
else:
3116
d += 1
3117
B.swap_rows_c(i, r - d)
3118
else:
3119
d += 1
3120
3121
doubly_even = True
3122
for i in xrange(r - d, r):
3123
if B.row_len(i) % 4 == 2: # Not a Sage matrix operation
3124
doubly_even = False
3125
break
3126
if doubly_even:
3127
b2 = b % 8
3128
else:
3129
b2 = None
3130
3131
Fm = B.row_union(range(r - d, r)) # Not a Sage matrix operation
3132
Fp = [i for i in B.row_sum(U) if i not in Fm] # Not a Sage matrix operation
3133
F0 = [i for i in range(len(self)) if i not in (Fm + Fp)]
3134
3135
BT = B.transpose()
3136
self._b_projection = BT._matrix_times_matrix_((B._matrix_times_matrix_(BT))._matrix_times_matrix_(B))
3137
P = [F0, Fp]
3138
p = []
3139
for a in xrange(2):
3140
for b in xrange(a + 1):
3141
x = 0
3142
for i in P[a]:
3143
for j in P[b]:
3144
if self._b_projection.get(i, j) != 0: # Not a Sage matrix operation
3145
x += 1
3146
p.append(x)
3147
if d > 0:
3148
F = F0 + Fp
3149
self._b_projection = self._b_projection.matrix_from_rows_and_columns(F, F)
3150
self._b_invariant = tuple([d, b2, len(Fm), len(F0), len(Fp), p[0], p[1], p[2]])
3151
self._b_partition = tuple([Fm, F0, Fp])
3152
3153
cpdef _invariant(self):
3154
r"""
3155
Return a matroid invariant.
3156
3157
See [Pen12]_ for more information.
3158
3159
OUTPUT:
3160
3161
A tuple ``(d, b, Lm, L0, Lp, p0, p1, p2)``, with the following
3162
interpretation:
3163
3164
- ``d`` is the :meth:`bicycle dimension <BinaryMatroid.bicycle_dimension>`.
3165
- ``b`` is the :meth:`Brown invariant <BinaryMatroid.brown_invariant>`.
3166
- ``(Lm, L0, Lp)`` is the triple of lengths of the principal tripartition.
3167
- ``(p0, p1, p2)`` are the counts of edges in a characteristic graph
3168
of the matroid, whose vertices are the union of ``F_-`` and ``F_0``
3169
from the principal tripartition.
3170
3171
EXAMPLES::
3172
3173
sage: from sage.matroids.advanced import *
3174
sage: M = BinaryMatroid(matroids.AG(2, 5).representation())
3175
sage: M._invariant()
3176
(2, 1, 24, 0, 1, 0, 0, 1)
3177
"""
3178
if self._b_invariant is None:
3179
self._make_invariant()
3180
return self._b_invariant
3181
3182
cpdef bicycle_dimension(self):
3183
r"""
3184
Return the bicycle dimension of the binary matroid.
3185
3186
The *bicycle dimension* of a linear subspace `V` is
3187
`\dim(V\cap V^\perp)`. The bicycle dimension of a matroid equals the
3188
bicycle dimension of its cocycle-space, and is an invariant for binary
3189
matroids. See [Pen12]_, [GR01]_ for more information.
3190
3191
OUTPUT:
3192
3193
Integer.
3194
3195
EXAMPLES::
3196
3197
sage: M = matroids.named_matroids.Fano()
3198
sage: M.bicycle_dimension()
3199
3
3200
"""
3201
if self._b_invariant is None:
3202
self._make_invariant()
3203
return self._b_invariant[0]
3204
3205
cpdef brown_invariant(self):
3206
r"""
3207
Return the value of Brown's invariant for the binary matroid.
3208
3209
For a binary space `V`, consider the sum
3210
`B(V):=\sum_{v\in V} i^{|v|}`, where `|v|` denotes the number of
3211
nonzero entries of a binary vector `v`. The value of the Tutte
3212
Polynomial in the point `(-i, i)` can be expressed in terms of
3213
`B(V)`, see [Pen12]_. If `|v|` equals `2` modulo 4 for some
3214
`v\in V\cap V^\perp`, then `B(V)=0`. In this case, Browns invariant is
3215
not defined. Otherwise, `B(V)=\sqrt{2}^k \exp(\sigma \pi i/4)` for
3216
some integers `k, \sigma`. In that case, `k` equals the bycycle
3217
dimension of `V`, and Browns invariant for `V` is defined as `\sigma`
3218
modulo `8`.
3219
3220
The Brown invariant of a binary matroid equals the Brown invariant of
3221
its cocycle-space.
3222
3223
OUTPUT:
3224
3225
Integer.
3226
3227
EXAMPLES::
3228
3229
sage: M = matroids.named_matroids.Fano()
3230
sage: M.brown_invariant()
3231
0
3232
sage: M = Matroid(Matrix(GF(2), 3, 8, [[1, 0, 0, 1, 1, 1, 1, 1],
3233
....: [0, 1, 0, 1, 1, 0, 0, 0],
3234
....: [0, 0, 1, 0, 0, 1, 1, 0]]))
3235
sage: M.brown_invariant() is None
3236
True
3237
"""
3238
if self._b_invariant is None:
3239
self._make_invariant()
3240
return self._b_invariant[1]
3241
3242
cpdef _principal_tripartition(self):
3243
r"""
3244
Return the principal tripartition of the binary matroid.
3245
3246
The principal tripartition is a partition `(F_{-1}, F_0, F_{1})` of
3247
the ground set. A defining property is the following. It is
3248
straightforward that if the bicycle dimension of a matroid `M` is `k`,
3249
then the bicycle dimension of `M\setminus e' is one of `k-1, k, k + 1`
3250
for each element `e` of `M`. Then if `F_i` denotes the set of elements
3251
such that the bicycle dimension of `M\setminus e` is `k + i`, we
3252
obtain the principal tripartition `(F_{-1}, F_0, F_{1})` of `M`.
3253
See [Pen12]_ and [GR01]_.
3254
3255
OUTPUT:
3256
3257
``(F_{-1}, F_0, F_{1})``, the principal tripartition of the matroid.
3258
3259
EXAMPLES::
3260
3261
sage: M = matroids.named_matroids.S8()
3262
sage: for F in M._principal_tripartition(): print sorted(F)
3263
['a', 'b', 'c', 'e', 'f', 'g']
3264
['d']
3265
['h']
3266
sage: M.bicycle_dimension()
3267
2
3268
sage: for i in [-1, 0, 1]: print sorted([e for e in M.groundset() if (M\e).bicycle_dimension() == 2 + i])
3269
['a', 'b', 'c', 'e', 'f', 'g']
3270
['d']
3271
['h']
3272
"""
3273
if self._b_invariant is None:
3274
self._make_invariant()
3275
P = self._b_partition
3276
return frozenset([self._E[e] for e in P[0]]), frozenset([self._E[e] for e in P[1]]), frozenset([self._E[e] for e in P[2]])
3277
3278
cpdef BinaryMatrix _projection(self):
3279
"""
3280
Return the projection matrix onto the row space.
3281
3282
This projection is determined modulo the bicycle space. See [Pen12]_.
3283
3284
INPUT:
3285
3286
- Nothing
3287
3288
OUTPUT:
3289
3290
A binary matrix `P`, so that the `e`-th column of `P` is the
3291
incidence vector of a cocycle `C` such that `C-e` is a cycle. Such a
3292
`C` is determined up to taking the symmetric difference with bicycles.
3293
We output the restriction of `P` to rows and columns that are not in
3294
any bicycle.
3295
3296
EXAMPLES::
3297
3298
sage: from sage.matroids.advanced import *
3299
sage: M = BinaryMatroid(matrix(matroids.named_matroids.R12()))
3300
sage: M._projection()
3301
12 x 12 BinaryMatrix
3302
[001110111000]
3303
[001101110100]
3304
[111011100010]
3305
[110111010001]
3306
[101100001011]
3307
[011100000111]
3308
[111000101110]
3309
[110100011101]
3310
[100010110011]
3311
[010001110011]
3312
[001011101110]
3313
[000111011101]
3314
3315
"""
3316
if self._b_invariant is None:
3317
self._make_invariant()
3318
return self._b_projection
3319
3320
cpdef BinaryMatrix _projection_partition(self):
3321
"""
3322
Return the equitable partition of the graph whose incidence matrix is
3323
the projection matrix of this matroid.
3324
3325
See method ``._projection()``.
3326
3327
INPUT:
3328
3329
- Nothing
3330
3331
OUTPUT:
3332
3333
An ordered partition.
3334
3335
sage: from sage.matroids.advanced import *
3336
sage: M = matroids.named_matroids.R12()
3337
sage: N = BinaryMatroid(reduced_matrix=M.representation(reduced=True,
3338
....: labels=False), groundset='abcdefghijkl')
3339
sage: N._projection_partition()
3340
2 x 12 BinaryMatrix
3341
[110011001100]
3342
[001100110011]
3343
"""
3344
if self._eq_part is None:
3345
if self._b_invariant is None:
3346
self._make_invariant()
3347
self._eq_part = self._b_projection.equitable_partition() # Not a Sage matrix operation
3348
return self._eq_part
3349
3350
cpdef _fast_isom_test(self, other):
3351
r"""
3352
Run a quick test to see if two binary matroids are isomorphic.
3353
3354
The test is based on comparing strong invariants. See [Pen12]_ for a
3355
full account of these invariants.
3356
3357
INPUT:
3358
3359
- ``other`` -- a binary matroid.
3360
3361
OUTPUT:
3362
3363
- ``True``, if ``self`` is isomorphic to ``other``;
3364
- ``False``, if ``self`` is not isomorphic to ``other``;
3365
- ``None``, if this test is inconclusive
3366
3367
EXAMPLES::
3368
3369
sage: M = matroids.named_matroids.S8()
3370
sage: N = matroids.named_matroids.S8()
3371
sage: M._fast_isom_test(N) is None
3372
True
3373
"""
3374
if self._invariant() != other._invariant():
3375
return False
3376
q = self._projection().is_isomorphic(other._projection(), self._projection_partition(), other._projection_partition()) # Not a Sage matrix operation
3377
if self.bicycle_dimension() == 0:
3378
return q
3379
if not q:
3380
return False
3381
3382
# minors, dual
3383
3384
cpdef _minor(self, contractions, deletions):
3385
r"""
3386
Return a minor.
3387
3388
INPUT:
3389
3390
- ``contractions`` -- An object with Python's ``frozenset`` interface
3391
containing a subset of ``self.groundset()``.
3392
- ``deletions`` -- An object with Python's ``frozenset`` interface
3393
containing a subset of ``self.groundset()``.
3394
3395
.. NOTE::
3396
3397
This method does NOT do any checks. Besides the assumptions above,
3398
we assume the following:
3399
3400
- ``contractions`` is independent
3401
- ``deletions`` is coindependent
3402
- ``contractions`` and ``deletions`` are disjoint.
3403
3404
OUTPUT:
3405
3406
A matroid.
3407
3408
EXAMPLES::
3409
3410
sage: M = matroids.named_matroids.Fano()
3411
sage: N = M._minor(contractions=set(['a']), deletions=set([]))
3412
sage: N._minor(contractions=set([]), deletions=set(['b', 'c']))
3413
Binary matroid of rank 2 on 4 elements, type (0, 6)
3414
"""
3415
self._move_current_basis(contractions, deletions)
3416
bas = list(self.basis() - contractions)
3417
R = [self._prow[self._idx[b]] for b in bas]
3418
C = [c for c in range(len(self._E)) if self._E[c] not in deletions | contractions]
3419
return BinaryMatroid(matrix=(<BinaryMatrix>self._A).matrix_from_rows_and_columns(R, C), groundset=[self._E[c] for c in C], basis=bas)
3420
3421
# graphicness test
3422
cpdef is_graphic(self):
3423
"""
3424
Test if the binary matroid is graphic.
3425
3426
A matroid is *graphic* if there exists a graph whose edge set equals
3427
the groundset of the matroid, such that a subset of elements of the
3428
matroid is independent if and only if the corresponding subgraph is
3429
acyclic.
3430
3431
OUTPUT:
3432
3433
Boolean.
3434
3435
EXAMPLES::
3436
3437
sage: R10 = matroids.named_matroids.R10()
3438
sage: M = Matroid(ring=GF(2), reduced_matrix=R10.representation(
3439
....: reduced=True, labels=False))
3440
sage: M.is_graphic()
3441
False
3442
sage: K5 = matroids.CompleteGraphic(5)
3443
sage: M = Matroid(ring=GF(2), reduced_matrix=K5.representation(
3444
....: reduced=True, labels=False))
3445
sage: M.is_graphic()
3446
True
3447
sage: M.dual().is_graphic()
3448
False
3449
3450
.. ALGORITHM:
3451
3452
In a recent paper, Geelen and Gerards [GG12]_ reduced the problem to
3453
testing if a system of linear equations has a solution. While not the
3454
fastest method, and not necessarily constructive (in the presence of
3455
2-separations especially), it is easy to implement.
3456
"""
3457
global GF2
3458
B= self.basis()
3459
Bl = [e for e in B]
3460
C = [self._cocircuit((self.groundset() - B) | set([e])) for e in Bl]
3461
3462
c = 1
3463
col = {}
3464
for e in xrange(len(B)):
3465
for f in range(len(B)):
3466
if e is not f:
3467
col[e, f] = c
3468
c += 1
3469
M = {}
3470
r = 0
3471
for e in xrange(len(B)):
3472
for f in xrange(e):
3473
for g in xrange(f):
3474
if not C[e].issuperset(C[f] & C[g]):
3475
M[(r, col[e, f])] = 1
3476
M[(r, col[e, g])] = 1
3477
M[(r, 0)] = 0
3478
r += 1
3479
if not C[f].issuperset(C[e] & C[g]):
3480
M[(r, col[f, e])] = 1
3481
M[(r, col[f, g])] = 1
3482
M[(r, 0)] = 0
3483
r += 1
3484
if not C[g].issuperset(C[e] & C[f]):
3485
M[(r, col[g, e])] = 1
3486
M[(r, col[g, f])] = 1
3487
M[(r, 0)] = 0
3488
r += 1
3489
if len(C[e] & C[f] & C[g]) > 0:
3490
M[(r, col[e, f])] = 1
3491
M[(r, col[e, g])] = 1
3492
M[(r, col[f, e])] = 1
3493
M[(r, col[f, g])] = 1
3494
M[(r, col[g, e])] = 1
3495
M[(r, col[g, f])] = 1
3496
M[(r, 0)] = 1
3497
r += 1
3498
m = sage.matrix.constructor.Matrix(GF2, r, c, M)
3499
# now self is graphic iff there is a binary vector x so that M*x = 0 and x_0 = 1, so:
3500
return BinaryMatroid(m).corank(frozenset([0])) > 0
3501
3502
cpdef is_valid(self):
3503
r"""
3504
Test if the data obey the matroid axioms.
3505
3506
Since this is a linear matroid over the field `\GF{2}`, this is always
3507
the case.
3508
3509
OUTPUT:
3510
3511
``True``.
3512
3513
EXAMPLES::
3514
3515
sage: M = Matroid(Matrix(GF(2), [[]]))
3516
sage: M.is_valid()
3517
True
3518
"""
3519
return True
3520
3521
def __copy__(self):
3522
"""
3523
Create a shallow copy.
3524
3525
EXAMPLES::
3526
3527
sage: M = Matroid(Matrix(GF(2), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
3528
....: [0, 0, 1, 1, 3]]))
3529
sage: N = copy(M) # indirect doctest
3530
sage: M == N
3531
True
3532
"""
3533
cdef BinaryMatroid N
3534
cdef list basis
3535
if self._representation is not None:
3536
N = BinaryMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True)
3537
else:
3538
basis = [0] * self.full_rank()
3539
for e in self.basis():
3540
basis[self._prow[self._idx[e]]] = e
3541
N = BinaryMatroid(groundset=self._E, matrix=self._A, basis=basis)
3542
N.rename(getattr(self, '__custom_name'))
3543
return N
3544
3545
def __deepcopy__(self, memo):
3546
"""
3547
Create a deep copy.
3548
3549
EXAMPLES::
3550
3551
sage: M = Matroid(Matrix(GF(2), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
3552
....: [0, 0, 1, 1, 3]]))
3553
sage: N = deepcopy(M) # indirect doctest
3554
sage: M == N
3555
True
3556
"""
3557
from copy import deepcopy
3558
cdef BinaryMatroid N
3559
cdef list basis
3560
if self._representation is not None:
3561
N = BinaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True)
3562
else:
3563
basis = [0] * self.full_rank()
3564
for e in self.basis():
3565
basis[self._prow[self._idx[e]]] = e
3566
N = BinaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._A, memo), basis=deepcopy(basis, memo))
3567
N.rename(deepcopy(getattr(self, '__custom_name'), memo))
3568
return N
3569
3570
def __reduce__(self):
3571
"""
3572
Save the matroid for later reloading.
3573
3574
OUTPUT:
3575
3576
A tuple ``(unpickle_binary_matroid, (version, data))``, where
3577
``unpickle_binary_matroid`` is the name of a function that, when
3578
called with ``(version, data)``, produces a matroid isomorphic to
3579
``self``. ``version`` is an integer (currently 0) and ``data`` is a
3580
tuple ``(A, E, B, name)`` where ``A`` is the representation
3581
matrix, ``E`` is the groundset of the matroid, ``B`` is the currently
3582
displayed basis, and ``name`` is a custom name.
3583
3584
.. WARNING::
3585
3586
Users should never call this function directly.
3587
3588
EXAMPLES::
3589
3590
sage: M = Matroid(Matrix(GF(2), [[1, 0, 0, 1], [0, 1, 0, 1],
3591
....: [0, 0, 1, 1]]))
3592
sage: M == loads(dumps(M)) # indirect doctest
3593
True
3594
sage: M.rename("U34")
3595
sage: loads(dumps(M))
3596
U34
3597
sage: M = Matroid(Matrix(GF(2), [[1, 0, 1], [1, 0, 1]]))
3598
sage: loads(dumps(M)).representation()
3599
[1 0 1]
3600
[1 0 1]
3601
"""
3602
import sage.matroids.unpickling
3603
version = 0
3604
cdef list basis = [0] * self.full_rank()
3605
if self._representation is not None:
3606
A = self._representation
3607
gs = self._E
3608
basis = None
3609
else:
3610
A = self._A
3611
for e in self.basis():
3612
basis[self._prow[self._idx[e]]] = e
3613
rows, cols = self._current_rows_cols()
3614
gs = rows + cols
3615
data = (A, gs, basis, getattr(self, '__custom_name'))
3616
return sage.matroids.unpickling.unpickle_binary_matroid, (version, data)
3617
3618
cdef class TernaryMatroid(LinearMatroid):
3619
r"""
3620
Ternary matroids.
3621
3622
A ternary matroid is a linear matroid represented over the finite field
3623
with three elements. See :class:`LinearMatroid` for a definition.
3624
3625
The simplest way to create a ``TernaryMatroid`` is by giving only a
3626
matrix `A`. Then, the groundset defaults to ``range(A.ncols())``. Any
3627
iterable object `E` can be given as a groundset. If `E` is a list, then
3628
``E[i]`` will label the `i`-th column of `A`. Another possibility is to
3629
specify a 'reduced' matrix `B`, to create the matroid induced by
3630
`A = [I\ \ B]`.
3631
3632
INPUT:
3633
3634
- ``matrix`` -- (default: ``None``) a matrix whose column vectors
3635
represent the matroid.
3636
- ``reduced_matrix`` -- (default: ``None``) a matrix `B` such that
3637
`[I\ \ B]` represents the matroid, where `I` is an identity matrix with
3638
the same number of rows as `B`. Only one of ``matrix`` and
3639
``reduced_matrix`` should be provided.
3640
- ``groundset`` -- (default: ``None``) an iterable containing the element
3641
labels. When provided, must have the correct number of elements: the
3642
number of columns of ``matrix`` or the number of rows plus the number
3643
of columns of ``reduced_matrix``.
3644
- ``ring`` -- (default: ``None``) ignored.
3645
- ``keep_initial_representation`` -- (default: ``True``) boolean. Decides
3646
whether or not an internal copy of the input matrix should be preserved.
3647
This can help to see the structure of the matroid (e.g. in the case of
3648
graphic matroids), and makes it easier to look at extensions. However,
3649
the input matrix may have redundant rows, and sometimes it is desirable
3650
to store only a row-reduced copy.
3651
- ``basis`` -- (default: ``None``) when provided, this is an ordered
3652
subset of ``groundset``, such that the submatrix of ``matrix`` indexed
3653
by ``basis`` is an identity matrix. In this case, no row reduction takes
3654
place in the initialization phase.
3655
3656
OUTPUT:
3657
3658
A ``TernaryMatroid`` instance based on the data above.
3659
3660
.. NOTE::
3661
3662
The recommended way to generate a ternary matroid is through the
3663
:func:`Matroid() <sage.matroids.constructor.Matroid>` function. This
3664
is usually the preferred way, since it automatically chooses between
3665
``TernaryMatroid`` and other classes. For direct access to the
3666
``TernaryMatroid`` constructor, run::
3667
3668
sage: from sage.matroids.advanced import *
3669
3670
EXAMPLES::
3671
3672
sage: A = Matrix(GF(3), 2, 4, [[1, 0, 1, 1], [0, 1, 1, 1]])
3673
sage: M = Matroid(A)
3674
sage: M
3675
Ternary matroid of rank 2 on 4 elements, type 0-
3676
sage: sorted(M.groundset())
3677
[0, 1, 2, 3]
3678
sage: Matrix(M)
3679
[1 0 1 1]
3680
[0 1 1 1]
3681
sage: M = Matroid(matrix=A, groundset='abcd')
3682
sage: sorted(M.groundset())
3683
['a', 'b', 'c', 'd']
3684
sage: B = Matrix(GF(2), 2, 2, [[1, 1], [1, 1]])
3685
sage: N = Matroid(ring=GF(3), reduced_matrix=B, groundset='abcd')
3686
sage: M == N
3687
True
3688
"""
3689
def __init__(self, matrix=None, groundset=None, reduced_matrix=None, ring=None, keep_initial_representation=True, basis=None):
3690
"""
3691
See class definition for full documentation.
3692
3693
.. NOTE::
3694
3695
The extra argument ``basis``, when provided, is an ordered list of
3696
elements of the groundset, ordered such that they index a standard
3697
identity matrix within ``matrix``.
3698
3699
EXAMPLES::
3700
3701
sage: from sage.matroids.advanced import *
3702
sage: TernaryMatroid(matrix=Matrix(GF(5), [[1, 0, 1, 1, 1],
3703
....: [0, 1, 1, 2, 3]])) # indirect doctest
3704
Ternary matroid of rank 2 on 5 elements, type 1+
3705
"""
3706
cdef TernaryMatrix A
3707
cdef long r, c
3708
cdef list P
3709
global GF3, GF3_zero, GF3_one, GF3_minus_one, GF3_not_defined
3710
if GF3_not_defined:
3711
GF3 = GF(3)
3712
GF3_zero = GF3(0)
3713
GF3_one = GF3(1)
3714
GF3_minus_one = GF3(2)
3715
GF3_not_defined = False
3716
3717
# Setup representation; construct displayed basis
3718
if matrix is not None:
3719
A = TernaryMatrix(matrix.nrows(), matrix.ncols(), M=matrix)
3720
if keep_initial_representation:
3721
self._representation = A.copy() # Deprecated Sage matrix operation
3722
if basis is None:
3723
P = gauss_jordan_reduce(A, xrange(A.ncols()))
3724
A.resize(len(P)) # Not a Sage matrix operation
3725
self._A = A
3726
else:
3727
A = TernaryMatrix(reduced_matrix.nrows(), reduced_matrix.ncols(), M=reduced_matrix)
3728
P = range(A.nrows())
3729
self._A = A.prepend_identity() # Not a Sage matrix operation
3730
3731
# Setup groundset, BasisExchangeMatroid data
3732
if groundset is None:
3733
groundset = range(self._A.ncols())
3734
else:
3735
if len(groundset) != self._A.ncols():
3736
raise ValueError("size of groundset does not match size of matrix")
3737
if basis is None:
3738
bas = [groundset[i] for i in P]
3739
else:
3740
bas = basis
3741
BasisExchangeMatroid.__init__(self, groundset, bas)
3742
3743
# Setup index of displayed basis
3744
self._prow = <long* > sage_malloc((self._A.ncols()) * sizeof(long))
3745
for c in xrange(self._A.ncols()):
3746
self._prow[c] = -1
3747
if matrix is not None:
3748
if basis is None:
3749
for r in xrange(len(P)):
3750
self._prow[P[r]] = r
3751
else:
3752
for r in xrange(self._A.nrows()):
3753
self._prow[self._idx[basis[r]]] = r
3754
else:
3755
for r from 0 <= r < self._A.nrows():
3756
self._prow[r] = r
3757
3758
L = []
3759
for i from 0 <= i < self._A.ncols():
3760
L.append(self._prow[i])
3761
self._zero = GF3_zero
3762
self._one = GF3_one
3763
self._two = GF3_minus_one
3764
3765
cpdef base_ring(self):
3766
"""
3767
Return the base ring of the matrix representing the matroid, in this
3768
case `\GF{3}`.
3769
3770
EXAMPLES::
3771
3772
sage: M = matroids.named_matroids.NonFano()
3773
sage: M.base_ring()
3774
Finite Field of size 3
3775
"""
3776
global GF3
3777
return GF3
3778
3779
cpdef characteristic(self):
3780
"""
3781
Return the characteristic of the base ring of the matrix representing
3782
the matroid, in this case `3`.
3783
3784
EXAMPLES::
3785
3786
sage: M = matroids.named_matroids.NonFano()
3787
sage: M.characteristic()
3788
3
3789
"""
3790
return 3
3791
3792
cdef bint __is_exchange_pair(self, long x, long y):
3793
r"""
3794
Check if ``self.basis() - x + y`` is again a basis. Internal method.
3795
"""
3796
return (<TernaryMatrix>self._A).get(self._prow[x], y) # Not a Sage matrix operation
3797
3798
cdef bint __exchange(self, long x, long y):
3799
r"""
3800
Replace ``self.basis() with ``self.basis() - x + y``. Internal method, does no checks.
3801
"""
3802
cdef long p = self._prow[x]
3803
self._A.pivot(p, y) # Not a Sage matrix operation
3804
self._prow[y] = p
3805
BasisExchangeMatroid.__exchange(self, x, y)
3806
3807
cdef __fundamental_cocircuit(self, bitset_t C, long x):
3808
r"""
3809
Fill bitset `C` with the incidence vector of the `B`-fundamental cocircuit using ``x``. Internal method using packed elements.
3810
"""
3811
bitset_copy(C, (<TernaryMatrix>self._A)._M0[self._prow[x]])
3812
3813
cdef __exchange_value(self, long x, long y):
3814
r"""
3815
Return the (x, y) entry of the current representation.
3816
"""
3817
cdef long t = (<TernaryMatrix>self._A).get(self._prow[x], y) # Not a Sage matrix operation
3818
if t == 0:
3819
return self._zero
3820
if t == 1:
3821
return self._one
3822
if t == -1:
3823
return self._two
3824
3825
def _repr_(self):
3826
"""
3827
Return a string representation of ``self``.
3828
3829
The type consists of the ``bicycle_dimension`` and the ``character``.
3830
3831
EXAMPLES::
3832
3833
sage: M = matroids.named_matroids.NonFano()
3834
sage: M.rename()
3835
sage: repr(M) # indirect doctest
3836
'Ternary matroid of rank 3 on 7 elements, type 0-'
3837
"""
3838
S = "Ternary matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements, type " + str(self.bicycle_dimension())
3839
if self.character() == 1:
3840
S = S + '+'
3841
else:
3842
S = S + '-'
3843
return S
3844
3845
cpdef _current_rows_cols(self, B=None):
3846
"""
3847
Return the current row and column labels of a reduced matrix.
3848
3849
INPUT:
3850
3851
- ``B`` -- (default: ``None``) If provided, first find a basis having
3852
maximal intersection with ``B``.
3853
3854
OUTPUT:
3855
3856
- ``R`` -- A list of row indices; corresponds to the currently used
3857
internal basis
3858
- ``C`` -- A list of column indices; corresponds to the complement of
3859
the current internal basis
3860
3861
EXAMPLES::
3862
3863
sage: M = matroids.named_matroids.NonFano()
3864
sage: A = M._reduced_representation('efg')
3865
sage: R, C = M._current_rows_cols()
3866
sage: (sorted(R), sorted(C))
3867
(['e', 'f', 'g'], ['a', 'b', 'c', 'd'])
3868
sage: R, C = M._current_rows_cols(B='abg')
3869
sage: (sorted(R), sorted(C))
3870
(['a', 'b', 'g'], ['c', 'd', 'e', 'f'])
3871
3872
"""
3873
if B is not None:
3874
self._move_current_basis(B, set())
3875
basis = self.basis()
3876
rows = [0] * self.full_rank()
3877
cols = [0] * self.full_corank()
3878
c = 0
3879
for e in self._E:
3880
if e in basis:
3881
rows[self._prow[self._idx[e]]] = e
3882
else:
3883
cols[c] = e
3884
c += 1
3885
return rows, cols
3886
3887
cpdef LeanMatrix _basic_representation(self, B=None):
3888
"""
3889
Return a basic matrix representation of the matroid.
3890
3891
INPUT:
3892
3893
- ``B`` -- (default: ``None``) a set of elements of the groundset.
3894
3895
OUTPUT:
3896
3897
A matrix `M` representing the matroid, where `M[B'] = I` for a basis
3898
`B'` that maximally intersects the given set `B`. If not provided, the
3899
current basis used internally is chosen for `B'`. For a stable
3900
representation, use ``self.representation()``.
3901
3902
.. NOTE::
3903
3904
The method self.groundset_list() gives the labelling of the
3905
columns by the elements of the matroid. The matrix returned is a
3906
LeanMatrix subclass, which is intended for internal use only. Use
3907
the ``representation()`` method to get a Sage matrix.
3908
3909
EXAMPLES::
3910
3911
sage: M = matroids.named_matroids.NonFano()
3912
sage: M._basic_representation()
3913
3 x 7 TernaryMatrix
3914
[+000+++]
3915
[0+0+0++]
3916
[00+++0+]
3917
sage: matrix(M._basic_representation('efg'))
3918
[1 2 0 2 1 0 0]
3919
[1 0 2 2 0 1 0]
3920
[2 1 1 2 0 0 1]
3921
"""
3922
if B is not None:
3923
self._move_current_basis(B, set())
3924
return self._A.copy() # Deprecated Sage matrix operation
3925
3926
cpdef LeanMatrix _reduced_representation(self, B=None):
3927
"""
3928
Return a reduced representation of the matroid, i.e. a matrix `R`
3929
such that `[I\ \ R]` represents the matroid.
3930
3931
INPUT:
3932
3933
- ``B`` -- (default: ``None``) a set of elements of the groundset.
3934
3935
OUTPUT:
3936
3937
A matrix `R` forming a reduced representation of the matroid, with
3938
rows labeled by a basis `B'` that maximally intersects the given set
3939
`B`. If not provided, the current basis used internally labels the
3940
rows.
3941
3942
.. NOTE::
3943
3944
The matrix returned is a LeanMatrix subclass, which is intended
3945
for internal use only. Use the ``representation()`` method to get
3946
a Sage matrix.
3947
3948
EXAMPLES::
3949
3950
sage: M = matroids.named_matroids.NonFano()
3951
sage: M._reduced_representation()
3952
3 x 4 TernaryMatrix
3953
[0+++]
3954
[+0++]
3955
[++0+]
3956
sage: matrix(M._reduced_representation('efg'))
3957
[1 2 0 2]
3958
[1 0 2 2]
3959
[2 1 1 2]
3960
"""
3961
if B is not None:
3962
self._move_current_basis(B, set())
3963
rows, cols = self._current_rows_cols()
3964
return self._A.matrix_from_rows_and_columns(range(self.full_rank()), [self._idx[e] for e in cols])
3965
3966
# isomorphism
3967
3968
cpdef _is_isomorphic(self, other):
3969
"""
3970
Test if ``self`` is isomorphic to ``other``. Internal version that
3971
performs no checks on input.
3972
3973
INPUT:
3974
3975
- ``other`` -- A matroid.
3976
3977
OUTPUT:
3978
3979
Boolean.
3980
3981
EXAMPLES::
3982
3983
sage: M1 = matroids.named_matroids.NonFano().delete('a')
3984
sage: M2 = matroids.Whirl(3)
3985
sage: M1._is_isomorphic(M2)
3986
True
3987
3988
sage: M2 = matroids.Wheel(3)
3989
sage: M1._is_isomorphic(M2)
3990
False
3991
"""
3992
if type(other) == TernaryMatroid:
3993
return self.is_field_isomorphic(other)
3994
else:
3995
return LinearMatroid._is_isomorphic(self, other)
3996
3997
# invariants
3998
3999
cpdef _make_invariant(self):
4000
"""
4001
Create an invariant.
4002
4003
Internal method; see ``_invariant`` for full documentation.
4004
4005
EXAMPLES::
4006
4007
sage: M = matroids.named_matroids.NonFano()
4008
sage: M._invariant() # indirect doctest
4009
(0, 2, 0, 4, 3, 0, 12, 12, 3, 0, 0, 0)
4010
"""
4011
cdef TernaryMatrix T
4012
cdef long i, j, d, r, x, y
4013
global GF3
4014
4015
if self._t_invariant is not None:
4016
return
4017
T = (<TernaryMatrix>self._A).copy() # Deprecated Sage matrix operation
4018
r = T.nrows()
4019
d = 0
4020
c = self._one
4021
i = 0
4022
while i < r - d:
4023
for j in xrange(i, r - d):
4024
if T.row_inner_product(j, j) != 0: # Not a Sage matrix operation
4025
if j > i:
4026
T.swap_rows_c(i, j)
4027
break
4028
if T.row_inner_product(i, j) != 0: # Not a Sage matrix operation
4029
if j > i:
4030
T.add_multiple_of_row_c(i, j, 1, 0)
4031
break
4032
x = T.row_inner_product(i, i) # Not a Sage matrix operation
4033
if x == 0:
4034
d += 1
4035
T.swap_rows_c(i, r - d)
4036
else:
4037
c = c * GF3(x)
4038
for j in xrange(i + 1, r - d):
4039
y = T.row_inner_product(i, j) # Not a Sage matrix operation
4040
if y == 0:
4041
continue
4042
if x == y:
4043
T.row_subs(j, i) # Not a Sage matrix operation
4044
else:
4045
T.add_multiple_of_row_c(j, i, 1, 0)
4046
i += 1
4047
4048
TT = T.transpose()
4049
self._t_projection = TT._matrix_times_matrix_((T._matrix_times_matrix_(TT))._matrix_times_matrix_(T))
4050
F = frozenset()
4051
for i in xrange(r - d, r):
4052
F = F | frozenset(T.nonzero_positions_in_row(i))
4053
Fa = frozenset([j for j in xrange(len(self)) if self._t_projection.get(j, j) == 0]) - F # Not a Sage matrix operation
4054
Fb = frozenset([j for j in xrange(len(self)) if self._t_projection.get(j, j) == 1]) - F # Not a Sage matrix operation
4055
Fc = frozenset([j for j in xrange(len(self)) if self._t_projection.get(j, j) == -1]) - F # Not a Sage matrix operation
4056
4057
P = [Fa, Fb, Fc]
4058
p = []
4059
for a in xrange(3):
4060
for b in xrange(a + 1):
4061
x = 0
4062
for i in P[a]:
4063
for j in P[b]:
4064
if self._t_projection.get(i, j) != 0: # Not a Sage matrix operation
4065
x += 1
4066
p.append(x)
4067
4068
self._t_partition = tuple([F, Fa, Fb, Fc])
4069
self._t_invariant = tuple([d, c, len(F), len(Fa), len(Fb), len(Fc), p[0], p[1], p[2], p[3], p[4], p[5]])
4070
4071
cpdef _invariant(self):
4072
r"""
4073
Return a matroid invariant.
4074
4075
See [Pen12]_ for more information.
4076
4077
OUTPUT:
4078
4079
A tuple ``(d, c, L, La, Lb, Lc, p0, p1, p2, p3, p4, p5)``, with the
4080
following interpretation:
4081
4082
- ``d`` is the bicycle dimension.
4083
- ``c`` is the character.
4084
- ``(L, La, Lb, Lc)`` is the triple of lengths of the principal
4085
quadripartition.
4086
- ``(p0, ..., p5)`` counts of edges in a characteristic graph of the
4087
matroid whose vertex set is the ground set of the matroid,
4088
restricted to the sets in the principal quadripartition.
4089
4090
EXAMPLES::
4091
4092
sage: M = matroids.named_matroids.NonFano()
4093
sage: M._invariant()
4094
(0, 2, 0, 4, 3, 0, 12, 12, 3, 0, 0, 0)
4095
"""
4096
if self._t_invariant is None:
4097
self._make_invariant()
4098
return self._t_invariant
4099
4100
cpdef bicycle_dimension(self):
4101
"""
4102
Return the bicycle dimension of the ternary matroid.
4103
4104
The bicycle dimension of a linear subspace `V` is
4105
`\dim(V\cap V^\perp)`. The bicycle dimension of a matroid equals the
4106
bicycle dimension of its rowspace, and is a matroid invariant.
4107
See [Pen12]_.
4108
4109
OUTPUT:
4110
4111
Integer.
4112
4113
EXAMPLES::
4114
4115
sage: M = matroids.named_matroids.NonFano()
4116
sage: M.bicycle_dimension()
4117
0
4118
"""
4119
if self._t_invariant is None:
4120
self._make_invariant()
4121
return self._t_invariant[0]
4122
4123
cpdef character(self):
4124
r"""
4125
Return the character of the ternary matroid.
4126
4127
For a linear subspace `V` over `GF(3)` with orthogonal basis
4128
`q_1, \ldots, q_k` the character equals the product of `|q_i|`
4129
modulo 3, where the product ranges over the `i` such that `|q_i|`
4130
is not divisible by 3. The character does not depend on the choice of
4131
the orthogonal basis. The character of a ternary matroid equals the
4132
character of its cocycle-space, and is an invariant for ternary
4133
matroids. See [Pen12]_.
4134
4135
OUTPUT:
4136
4137
Integer.
4138
4139
EXAMPLES::
4140
4141
sage: M = matroids.named_matroids.NonFano()
4142
sage: M.character()
4143
2
4144
"""
4145
if self._t_invariant is None:
4146
self._make_invariant()
4147
return self._t_invariant[1]
4148
4149
cpdef _principal_quadripartition(self):
4150
r"""
4151
Return an ordered partition of the ground set.
4152
4153
The partition groups each element `e` of the ground set
4154
according to the bicycle dimension and the character of `M/e`, where
4155
`M` is the present matroid.
4156
4157
EXAMPLES::
4158
4159
sage: M = matroids.named_matroids.N1()
4160
sage: print M
4161
N1: Ternary matroid of rank 5 on 10 elements, type 0+
4162
sage: P = M._principal_quadripartition()
4163
sage: for e in sorted(P[0]): print e, M/e
4164
sage: for e in sorted(P[1]): print e, M/e
4165
a Ternary matroid of rank 4 on 9 elements, type 1-
4166
b Ternary matroid of rank 4 on 9 elements, type 1-
4167
e Ternary matroid of rank 4 on 9 elements, type 1-
4168
f Ternary matroid of rank 4 on 9 elements, type 1-
4169
sage: for e in sorted(P[2]): print e, M/e
4170
d Ternary matroid of rank 4 on 9 elements, type 0-
4171
i Ternary matroid of rank 4 on 9 elements, type 0-
4172
sage: for e in sorted(P[3]): print e, M/e
4173
c Ternary matroid of rank 4 on 9 elements, type 0+
4174
g Ternary matroid of rank 4 on 9 elements, type 0+
4175
h Ternary matroid of rank 4 on 9 elements, type 0+
4176
j Ternary matroid of rank 4 on 9 elements, type 0+
4177
4178
4179
"""
4180
if self._t_invariant is None:
4181
self._make_invariant()
4182
return tuple([[self._E[j] for j in self._t_partition[0]], [self._E[j] for j in self._t_partition[1]], [self._E[j] for j in self._t_partition[2]], [self._E[j] for j in self._t_partition[3]]])
4183
4184
cpdef TernaryMatrix _projection(self):
4185
"""
4186
Return the projection matrix onto the row space.
4187
4188
This projection is determined modulo the bicycle space. See [Pen12]_.
4189
4190
INPUT:
4191
4192
- Nothing
4193
4194
OUTPUT:
4195
4196
A ternary matrix `P`, so that the `e`-th column of `P` is the signed
4197
incidence vector of a cocycle `C` such that `C-e` is a cycle.
4198
The bicycles `e`-th column is thus determined up to the bicycle
4199
space. We output the restriction of `P` to rows and columns that are
4200
not in any bicycle.
4201
4202
EXAMPLES::
4203
4204
sage: from sage.matroids.advanced import *
4205
sage: M = TernaryMatroid(matrix(matroids.named_matroids.R12()))
4206
sage: M._projection()
4207
12 x 12 TernaryMatrix
4208
[++00-0--0+++]
4209
[+-+000+0+-+0]
4210
[0+-0-00+-+0+]
4211
[000000000000]
4212
[-0-0-0+-+00+]
4213
[000000000000]
4214
[-+00+0000+--]
4215
[-0+0-00-+0-+]
4216
[0+-0+00++++-]
4217
[+-+000+0+-+0]
4218
[++0000--++00]
4219
[+0+0+0-+-00-]
4220
4221
"""
4222
if self._t_invariant is None:
4223
self._make_invariant()
4224
return self._t_projection
4225
4226
cpdef _fast_isom_test(self, other):
4227
r"""
4228
Run a quick test to see if two ternary matroids are isomorphic.
4229
4230
The test is based on comparing strong invariants, including bicycle
4231
dimension, character, and the principal quadripartition.
4232
See also [Pen12]_ .
4233
4234
INPUT:
4235
4236
- ``other`` -- a ternary matroid.
4237
4238
OUTPUT:
4239
4240
- ``True``, if ``self`` is isomorphic to ``other``;
4241
- ``False``, if ``self`` is not isomorphic to ``other``;
4242
- ``None``, if the test is inconclusive
4243
4244
EXAMPLES::
4245
4246
sage: M = matroids.named_matroids.T8()
4247
sage: N = matroids.named_matroids.P8()
4248
sage: M._fast_isom_test(N)
4249
False
4250
"""
4251
if self._invariant() != other._invariant():
4252
return False
4253
return None
4254
4255
# minors, dual
4256
4257
cpdef _minor(self, contractions, deletions):
4258
r"""
4259
Return a minor.
4260
4261
INPUT:
4262
4263
- ``contractions`` -- An object with Python's ``frozenset`` interface
4264
containing a subset of ``self.groundset()``.
4265
- ``deletions`` -- An object with Python's ``frozenset`` interface
4266
containing a subset of ``self.groundset()``.
4267
4268
.. NOTE::
4269
4270
This method does NOT do any checks. Besides the assumptions above,
4271
we assume the following:
4272
4273
- ``contractions`` is independent
4274
- ``deletions`` is coindependent
4275
- ``contractions`` and ``deletions`` are disjoint.
4276
4277
OUTPUT:
4278
4279
A matroid.
4280
4281
EXAMPLES::
4282
4283
sage: M = matroids.named_matroids.P8()
4284
sage: N = M._minor(contractions=set(['a']), deletions=set([]))
4285
sage: N._minor(contractions=set([]), deletions=set(['b', 'c']))
4286
Ternary matroid of rank 3 on 5 elements, type 0-
4287
"""
4288
self._move_current_basis(contractions, deletions)
4289
bas = list(self.basis() - contractions)
4290
R = [self._prow[self._idx[b]] for b in bas]
4291
C = [c for c in range(len(self._E)) if self._E[c] not in deletions | contractions]
4292
return TernaryMatroid(matrix=(<TernaryMatrix>self._A).matrix_from_rows_and_columns(R, C), groundset=[self._E[c] for c in C], basis=bas)
4293
4294
cpdef is_valid(self):
4295
r"""
4296
Test if the data obey the matroid axioms.
4297
4298
Since this is a linear matroid over the field `\GF{3}`, this is always
4299
the case.
4300
4301
OUTPUT:
4302
4303
``True``.
4304
4305
EXAMPLES::
4306
4307
sage: M = Matroid(Matrix(GF(3), [[]]))
4308
sage: M.is_valid()
4309
True
4310
"""
4311
return True
4312
4313
def __copy__(self):
4314
"""
4315
Create a shallow copy.
4316
4317
EXAMPLES::
4318
4319
sage: M = Matroid(Matrix(GF(3), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
4320
....: [0, 0, 1, 1, 3]]))
4321
sage: N = copy(M) # indirect doctest
4322
sage: M == N
4323
True
4324
"""
4325
cdef TernaryMatroid N
4326
cdef list basis
4327
if self._representation is not None:
4328
N = TernaryMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True)
4329
else:
4330
basis = [0] * self.full_rank()
4331
for e in self.basis():
4332
basis[self._prow[self._idx[e]]] = e
4333
N = TernaryMatroid(groundset=self._E, matrix=self._A, basis=basis)
4334
N.rename(getattr(self, '__custom_name'))
4335
return N
4336
4337
def __deepcopy__(self, memo):
4338
"""
4339
Create a deep copy.
4340
4341
EXAMPLES::
4342
4343
sage: M = Matroid(Matrix(GF(3), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2],
4344
....: [0, 0, 1, 1, -1]]))
4345
sage: N = deepcopy(M) # indirect doctest
4346
sage: M == N
4347
True
4348
"""
4349
from copy import deepcopy
4350
cdef TernaryMatroid N
4351
cdef list basis
4352
if self._representation is not None:
4353
N = TernaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True)
4354
else:
4355
basis = [0] * self.full_rank()
4356
for e in self.basis():
4357
basis[self._prow[self._idx[e]]] = e
4358
N = TernaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._A, memo), basis=deepcopy(basis, memo))
4359
N.rename(deepcopy(getattr(self, '__custom_name'), memo))
4360
return N
4361
4362
def __reduce__(self):
4363
"""
4364
Save the matroid for later reloading.
4365
4366
OUTPUT:
4367
4368
A tuple ``(unpickle_ternary_matroid, (version, data))``, where
4369
``unpickle_ternary_matroid`` is the name of a function that, when
4370
called with ``(version, data)``, produces a matroid isomorphic to
4371
``self``. ``version`` is an integer (currently 0) and ``data`` is a
4372
tuple ``(A, E, B, name)`` where ``A`` is the representation
4373
matrix, ``E`` is the groundset of the matroid, ``B`` is the currently
4374
displayed basis, and ``name`` is a custom name.
4375
4376
.. WARNING::
4377
4378
Users should never call this function directly.
4379
4380
EXAMPLES::
4381
4382
sage: from sage.matroids.advanced import *
4383
sage: M = TernaryMatroid(Matrix(GF(3), [[1, 0, 0, 1],
4384
....: [0, 1, 0, 1], [0, 0, 1, 1]]))
4385
sage: M == loads(dumps(M)) # indirect doctest
4386
True
4387
sage: M.rename("U34")
4388
sage: loads(dumps(M))
4389
U34
4390
sage: M = TernaryMatroid(Matrix(GF(3), [[1, 0, 1], [1, 0, 1]]))
4391
sage: loads(dumps(M)).representation()
4392
[1 0 1]
4393
[1 0 1]
4394
"""
4395
import sage.matroids.unpickling
4396
version = 0
4397
cdef list basis = [0] * self.full_rank()
4398
if self._representation is not None:
4399
A = self._representation
4400
gs = self._E
4401
basis = None
4402
else:
4403
A = self._A
4404
for e in self.basis():
4405
basis[self._prow[self._idx[e]]] = e
4406
rows, cols = self._current_rows_cols()
4407
gs = rows + cols
4408
data = (A, gs, basis, getattr(self, '__custom_name'))
4409
return sage.matroids.unpickling.unpickle_ternary_matroid, (version, data)
4410
4411
# Quaternary Matroids
4412
4413
cdef class QuaternaryMatroid(LinearMatroid):
4414
r"""
4415
Quaternary matroids.
4416
4417
A quaternary matroid is a linear matroid represented over the finite field
4418
with four elements. See :class:`LinearMatroid` for a definition.
4419
4420
The simplest way to create a ``QuaternaryMatroid`` is by giving only a
4421
matrix `A`. Then, the groundset defaults to ``range(A.ncols())``. Any
4422
iterable object `E` can be given as a groundset. If `E` is a list, then
4423
``E[i]`` will label the `i`-th column of `A`. Another possibility is to
4424
specify a 'reduced' matrix `B`, to create the matroid induced by
4425
`A = [I\ \ B]`.
4426
4427
INPUT:
4428
4429
- ``matrix`` -- (default: ``None``) a matrix whose column vectors
4430
represent the matroid.
4431
- ``reduced_matrix`` -- (default: ``None``) a matrix `B` such that
4432
`[I\ \ B]` represents the matroid, where `I` is an identity matrix with
4433
the same number of rows as `B`. Only one of ``matrix`` and
4434
``reduced_matrix`` should be provided.
4435
- ``groundset`` -- (default: ``None``) an iterable containing the element
4436
labels. When provided, must have the correct number of elements:
4437
the number of columns of ``matrix`` or the number of rows plus the
4438
number of columns of ``reduced_matrix``.
4439
- ``ring`` -- (default: ``None``) must be a copy of `\GF{4}`.
4440
- ``keep_initial_representation`` -- (default: ``True``) boolean. Decides
4441
whether or not an internal copy of the input matrix should be preserved.
4442
This can help to see the structure of the matroid (e.g. in the case of
4443
graphic matroids), and makes it easier to look at extensions. However,
4444
the input matrix may have redundant rows, and sometimes it is desirable
4445
to store only a row-reduced copy.
4446
- ``basis`` -- (default: ``None``) When provided, this is an ordered
4447
subset of ``groundset``, such that the submatrix of ``matrix`` indexed
4448
by ``basis`` is an identity matrix. In this case, no row reduction takes
4449
place in the initialization phase.
4450
4451
OUTPUT:
4452
4453
A ``QuaternaryMatroid`` instance based on the data above.
4454
4455
.. NOTE::
4456
4457
The recommended way to generate a quaternary matroid is through the
4458
:func:`Matroid() <sage.matroids.constructor.Matroid>` function. This
4459
is usually the preferred way, since it automatically chooses between
4460
``QuaternaryMatroid`` and other classes. For direct access to the
4461
``QuaternaryMatroid`` constructor, run::
4462
4463
sage: from sage.matroids.advanced import *
4464
4465
EXAMPLES::
4466
4467
sage: GF4 = GF(4, 'x')
4468
sage: x = GF4.gens()[0]
4469
sage: A = Matrix(GF4, 2, 4, [[1, 0, 1, 1], [0, 1, 1, x]])
4470
sage: M = Matroid(A)
4471
sage: M
4472
Quaternary matroid of rank 2 on 4 elements
4473
sage: sorted(M.groundset())
4474
[0, 1, 2, 3]
4475
sage: Matrix(M)
4476
[1 0 1 1]
4477
[0 1 1 x]
4478
sage: M = Matroid(matrix=A, groundset='abcd')
4479
sage: sorted(M.groundset())
4480
['a', 'b', 'c', 'd']
4481
sage: GF4p = GF(4, 'y')
4482
sage: y = GF4p.gens()[0]
4483
sage: B = Matrix(GF4p, 2, 2, [[1, 1], [1, y]])
4484
sage: N = Matroid(reduced_matrix=B, groundset='abcd')
4485
sage: M == N
4486
False
4487
"""
4488
def __init__(self, matrix=None, groundset=None, reduced_matrix=None, ring=None, keep_initial_representation=True, basis=None):
4489
"""
4490
See class definition for full documentation.
4491
4492
.. NOTE::
4493
4494
The extra argument ``basis``, when provided, is an ordered list of
4495
elements of the groundset, ordered such that they index a standard
4496
identity matrix within ``matrix``.
4497
4498
EXAMPLES::
4499
4500
sage: from sage.matroids.advanced import *
4501
sage: QuaternaryMatroid(matrix=Matrix(GF(4, 'x'),
4502
....: [[1, 0, 1, 1, 1], [0, 1, 1, 1, 1]])) # indirect doctest
4503
Quaternary matroid of rank 2 on 5 elements
4504
"""
4505
cdef QuaternaryMatrix A
4506
cdef long r, c
4507
cdef list P
4508
4509
# Setup representation; construct displayed basis
4510
if matrix is not None:
4511
A = QuaternaryMatrix(matrix.nrows(), matrix.ncols(), M=matrix, ring=ring)
4512
if keep_initial_representation:
4513
self._representation = A.copy() # Deprecated Sage matrix operation
4514
if basis is None:
4515
P = gauss_jordan_reduce(A, xrange(A.ncols()))
4516
A.resize(len(P)) # Not a Sage matrix operation
4517
self._A = A
4518
else:
4519
A = QuaternaryMatrix(reduced_matrix.nrows(), reduced_matrix.ncols(), M=reduced_matrix, ring=ring)
4520
P = range(A.nrows())
4521
self._A = A.prepend_identity() # Not a Sage matrix operation
4522
4523
# Setup groundset, BasisExchangeMatroid data
4524
if groundset is None:
4525
groundset = range(self._A.ncols())
4526
else:
4527
if len(groundset) != self._A.ncols():
4528
raise ValueError("size of groundset does not match size of matrix")
4529
if basis is None:
4530
bas = [groundset[i] for i in P]
4531
else:
4532
bas = basis
4533
BasisExchangeMatroid.__init__(self, groundset, bas)
4534
4535
# Setup index of displayed basis
4536
self._prow = <long* > sage_malloc((self._A.ncols()) * sizeof(long))
4537
for c in xrange(self._A.ncols()):
4538
self._prow[c] = -1
4539
if matrix is not None:
4540
if basis is None:
4541
for r in xrange(len(P)):
4542
self._prow[P[r]] = r
4543
else:
4544
for r in xrange(self._A.nrows()):
4545
self._prow[self._idx[basis[r]]] = r
4546
else:
4547
for r from 0 <= r < self._A.nrows():
4548
self._prow[r] = r
4549
4550
L = []
4551
for i from 0 <= i < self._A.ncols():
4552
L.append(self._prow[i])
4553
self._zero = (<QuaternaryMatrix>self._A)._zero
4554
self._one = (<QuaternaryMatrix>self._A)._one
4555
self._x_zero = (<QuaternaryMatrix>self._A)._x_zero
4556
self._x_one = (<QuaternaryMatrix>self._A)._x_one
4557
4558
cpdef base_ring(self):
4559
"""
4560
Return the base ring of the matrix representing the matroid, in this
4561
case `\GF{4}`.
4562
4563
EXAMPLES::
4564
4565
sage: M = Matroid(ring=GF(4, 'y'), reduced_matrix=[[1, 0, 1],
4566
....: [0, 1, 1]])
4567
sage: M.base_ring()
4568
Finite Field in y of size 2^2
4569
"""
4570
return (<QuaternaryMatrix>self._A).base_ring()
4571
4572
cpdef characteristic(self):
4573
"""
4574
Return the characteristic of the base ring of the matrix representing
4575
the matroid, in this case `2`.
4576
4577
EXAMPLES::
4578
4579
sage: M = Matroid(ring=GF(4, 'y'), reduced_matrix=[[1, 0, 1],
4580
....: [0, 1, 1]])
4581
sage: M.characteristic()
4582
2
4583
"""
4584
return 2
4585
4586
cdef bint __is_exchange_pair(self, long x, long y):
4587
r"""
4588
Check if ``self.basis() - x + y`` is again a basis. Internal method.
4589
"""
4590
return (<QuaternaryMatrix>self._A).get(self._prow[x], y) # Not a Sage matrix operation
4591
4592
cdef bint __exchange(self, long x, long y):
4593
r"""
4594
Replace ``self.basis() with ``self.basis() - x + y``. Internal method, does no checks.
4595
"""
4596
cdef long p = self._prow[x]
4597
self._A.pivot(p, y) # Not a Sage matrix operation
4598
self._prow[y] = p
4599
BasisExchangeMatroid.__exchange(self, x, y)
4600
4601
cdef __fundamental_cocircuit(self, bitset_t C, long x):
4602
r"""
4603
Fill bitset `C` with the incidence vector of the `B`-fundamental cocircuit using ``x``. Internal method using packed elements.
4604
"""
4605
bitset_union(C, (<QuaternaryMatrix>self._A)._M0[self._prow[x]], (<QuaternaryMatrix>self._A)._M1[self._prow[x]])
4606
4607
cdef __exchange_value(self, long x, long y):
4608
r"""
4609
Return the (x, y) entry of the current representation.
4610
"""
4611
return (<QuaternaryMatrix>self._A).get(self._prow[x], y) # Not a Sage matrix operation
4612
4613
def _repr_(self):
4614
"""
4615
Return a string representation of ``self``.
4616
4617
EXAMPLES::
4618
4619
sage: M = Matroid(ring=GF(4, 'x'), matrix=[[1, 0, 1], [0, 1, 1]])
4620
sage: M.rename()
4621
sage: repr(M) # indirect doctest
4622
'Quaternary matroid of rank 2 on 3 elements'
4623
"""
4624
S = "Quaternary matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements"
4625
return S
4626
4627
cpdef _current_rows_cols(self, B=None):
4628
"""
4629
Return the current row and column labels of a reduced matrix.
4630
4631
INPUT:
4632
4633
- ``B`` -- (default: ``None``) If provided, first find a basis having
4634
maximal intersection with ``B``.
4635
4636
OUTPUT:
4637
4638
- ``R`` -- A list of row indices; corresponds to the currently used
4639
internal basis
4640
- ``C`` -- A list of column indices; corresponds to the complement of
4641
the current internal basis
4642
4643
EXAMPLES::
4644
4645
sage: M = matroids.named_matroids.Q10()
4646
sage: A = M._reduced_representation('efghi')
4647
sage: R, C = M._current_rows_cols()
4648
sage: (sorted(R), sorted(C))
4649
(['e', 'f', 'g', 'h', 'i'], ['a', 'b', 'c', 'd', 'j'])
4650
sage: R, C = M._current_rows_cols(B='abcde')
4651
sage: (sorted(R), sorted(C))
4652
(['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h', 'i', 'j'])
4653
4654
"""
4655
if B is not None:
4656
self._move_current_basis(B, set())
4657
basis = self.basis()
4658
rows = [0] * self.full_rank()
4659
cols = [0] * self.full_corank()
4660
c = 0
4661
for e in self._E:
4662
if e in basis:
4663
rows[self._prow[self._idx[e]]] = e
4664
else:
4665
cols[c] = e
4666
c += 1
4667
return rows, cols
4668
4669
cpdef LeanMatrix _basic_representation(self, B=None):
4670
"""
4671
Return a basic matrix representation of the matroid.
4672
4673
INPUT:
4674
4675
- ``B`` -- (default: ``None``) a set of elements of the groundset.
4676
4677
OUTPUT:
4678
4679
A matrix `M` representing the matroid, where `M[B'] = I` for a basis
4680
`B'` that maximally intersects the given set `B`. If not provided, the
4681
current basis used internally is chosen for `B'`. For a stable
4682
representation, use ``self.representation()``.
4683
4684
.. NOTE::
4685
4686
The method self.groundset_list() gives the labelling of the
4687
columns by the elements of the matroid. The matrix returned
4688
is a LeanMatrix subclass, which is intended for internal use only.
4689
Use the ``representation()`` method to get a Sage matrix.
4690
4691
EXAMPLES::
4692
4693
sage: M = matroids.named_matroids.Q10()
4694
sage: M._basic_representation()
4695
5 x 10 QuaternaryMatrix
4696
[100001x00y]
4697
[01000y1x00]
4698
[001000y1x0]
4699
[0001000y1x]
4700
[00001x00y1]
4701
sage: matrix(M._basic_representation('efghi'))
4702
[ 1 0 x + 1 1 0 1 0 0 0 1]
4703
[ x x + 1 0 0 0 0 0 1 0 1]
4704
[ 0 0 x x + 1 0 0 1 0 0 1]
4705
[ 1 x 0 1 0 0 0 0 1 1]
4706
[ 1 1 1 1 1 0 0 0 0 0]
4707
"""
4708
if B is not None:
4709
self._move_current_basis(B, set())
4710
return self._A.copy() # Deprecated Sage matrix operation
4711
4712
cpdef LeanMatrix _reduced_representation(self, B=None):
4713
"""
4714
Return a reduced representation of the matroid, i.e. a matrix `R` such
4715
that `[I\ \ R]` represents the matroid.
4716
4717
INPUT:
4718
4719
- ``B`` -- (default: ``None``) a set of elements of the groundset.
4720
4721
OUTPUT:
4722
4723
A matrix `R` forming a reduced representation of the matroid, with
4724
rows labeled by a basis `B'` that maximally intersects the given set
4725
`B`. If not provided, the current basis used internally labels the
4726
rows.
4727
4728
.. NOTE::
4729
4730
The matrix returned is a LeanMatrix subclass, which is intended
4731
for internal use only. Use the ``representation()`` method to get
4732
a Sage matrix.
4733
4734
EXAMPLES::
4735
4736
sage: M = matroids.named_matroids.Q10()
4737
sage: M._reduced_representation()
4738
5 x 5 QuaternaryMatrix
4739
[1x00y]
4740
[y1x00]
4741
[0y1x0]
4742
[00y1x]
4743
[x00y1]
4744
sage: matrix(M._reduced_representation('efghi'))
4745
[ 1 0 x + 1 1 1]
4746
[ x x + 1 0 0 1]
4747
[ 0 0 x x + 1 1]
4748
[ 1 x 0 1 1]
4749
[ 1 1 1 1 0]
4750
"""
4751
if B is not None:
4752
self._move_current_basis(B, set())
4753
rows, cols = self._current_rows_cols()
4754
return self._A.matrix_from_rows_and_columns(range(self.full_rank()), [self._idx[e] for e in cols])
4755
4756
cpdef _make_invariant(self):
4757
"""
4758
Create an invariant.
4759
4760
Internal method; see ``_invariant`` for full documentation.
4761
4762
EXAMPLES::
4763
4764
sage: M = matroids.named_matroids.Q10()
4765
sage: M._invariant() # indirect doctest
4766
(0, 0, 5, 5, 20, 10, 25)
4767
"""
4768
cdef QuaternaryMatrix Q, QT
4769
cdef long i, j, d, r
4770
4771
if self._q_invariant is not None:
4772
return
4773
Q = (<QuaternaryMatrix>self._A).copy() # Deprecated Sage matrix operation
4774
r = Q.nrows()
4775
d = 0
4776
i = 0
4777
while i < r - d:
4778
for j in xrange(i, r - d):
4779
if Q.row_inner_product(j, j) != 0: # Not a Sage matrix operation
4780
if j > i:
4781
Q.swap_rows_c(i, j)
4782
break
4783
y = Q.row_inner_product(i, j) # Not a Sage matrix operation
4784
if y != 0:
4785
if j > i:
4786
Q.add_multiple_of_row_c(i, j, Q._x_zero * y, 0)
4787
break
4788
x = Q.row_inner_product(i, i) # Not a Sage matrix operation
4789
if x == 0:
4790
d += 1
4791
Q.swap_rows_c(i, r - d)
4792
else:
4793
for j in xrange(i + 1, r - d):
4794
y = Q.row_inner_product(j, i) # Not a Sage matrix operation
4795
if y == 0:
4796
continue
4797
Q.add_multiple_of_row_c(j, i, y, 0)
4798
i += 1
4799
4800
QT = Q.transpose()
4801
QT.conjugate() # Not a Sage matrix operation
4802
self._q_projection = QT._matrix_times_matrix_((Q._matrix_times_matrix_(QT))._matrix_times_matrix_(Q))
4803
F = frozenset()
4804
for i in xrange(r - d, r):
4805
F = F | frozenset(Q.nonzero_positions_in_row(i))
4806
Fa = frozenset([j for j in xrange(len(self)) if self._q_projection.get(j, j) == 0]) - F # Not a Sage matrix operation
4807
Fb = frozenset([j for j in xrange(len(self)) if self._q_projection.get(j, j) == 1]) - F # Not a Sage matrix operation
4808
4809
P = [Fa, Fb]
4810
p = []
4811
for a in xrange(2):
4812
for b in xrange(a + 1):
4813
x = 0
4814
for i in P[a]:
4815
for j in P[b]:
4816
if self._q_projection.get(i, j) != 0: # Not a Sage matrix operation
4817
x += 1
4818
p.append(x)
4819
4820
self._q_partition = tuple([F, Fa, Fb])
4821
self._q_invariant = tuple([d, len(F), len(Fa), len(Fb), p[0], p[1], p[2]])
4822
4823
cpdef _invariant(self):
4824
r"""
4825
Return a matroid invariant.
4826
4827
See [Pen12]_ for more information.
4828
4829
OUTPUT:
4830
4831
A tuple ``(d, Lm, L0, Lp, p0, p1, p2)``, with the following
4832
interpretation:
4833
4834
- ``d`` is the bicycle dimension.
4835
- ``(Lm, L0, Lp)`` is the triple of lengths of the principal
4836
tripartition.
4837
- ``(p0, p1, p2)`` counts of edges in a characteristic graph of the
4838
matroid, whose vertices are the union of ``F_-`` and ``F_0`` from
4839
the principal tripartition.
4840
4841
EXAMPLES::
4842
4843
sage: M = matroids.named_matroids.Q10()
4844
sage: M._invariant()
4845
(0, 0, 5, 5, 20, 10, 25)
4846
4847
"""
4848
if self._q_invariant is None:
4849
self._make_invariant()
4850
return self._q_invariant
4851
4852
cpdef bicycle_dimension(self):
4853
"""
4854
Return the bicycle dimension of the quaternary matroid.
4855
4856
The bicycle dimension of a linear subspace `V` is
4857
`\dim(V\cap V^\perp)`. We use the inner product
4858
`< v, w >=v_1 w_1^* + ... + v_n w_n^*`, where `w_i^*` is obtained from
4859
`w_i` by applying the unique nontrivial field automorphism of
4860
`\GF{4}`.
4861
4862
The bicycle dimension of a matroid equals the bicycle dimension of its
4863
rowspace, and is a matroid invariant. See [Pen12]_.
4864
4865
OUTPUT:
4866
4867
Integer.
4868
4869
EXAMPLES::
4870
4871
sage: M = matroids.named_matroids.Q10()
4872
sage: M.bicycle_dimension()
4873
0
4874
"""
4875
if self._q_invariant is None:
4876
self._make_invariant()
4877
return self._q_invariant[0]
4878
4879
cpdef _principal_tripartition(self):
4880
r"""
4881
Return the principal tripartition of the quaternary matroid.
4882
4883
The principal tripartition is a partition `(F_{-1}, F_0, F_{1})` of
4884
the ground set. A defining property is the following. It is
4885
straightforward that if the bicycle dimension of a matroid `M` is `k`,
4886
then the bicycle dimension of `M\setminus e' is one of `k-1, k, k + 1`
4887
for each element `e` of `M`. Then if `F_i` denotes the set of elements
4888
such that the bicycle dimension of `M\setminus e` is `k + i`, we
4889
obtain the principal tripartition `(F_{-1}, F_0, F_{1})` of `M`.
4890
See [Pen12]_, [GR01]_.
4891
4892
OUTPUT:
4893
4894
``(F_{-1}, F_0, F_{1})``, the principal tripartition of the matroid.
4895
4896
EXAMPLES::
4897
4898
sage: M = matroids.named_matroids.Q10()\'a'
4899
sage: for F in M._principal_tripartition(): print sorted(F)
4900
['b', 'c', 'd', 'e', 'h', 'i']
4901
['f', 'g', 'j']
4902
[]
4903
sage: M.bicycle_dimension()
4904
1
4905
sage: for i in [-1, 0, 1]: print sorted([e for e in M.groundset() if (M\e).bicycle_dimension() == 1 + i])
4906
['b', 'c', 'd', 'e', 'h', 'i']
4907
['f', 'g', 'j']
4908
[]
4909
"""
4910
if self._q_invariant is None:
4911
self._make_invariant()
4912
P = self._q_partition
4913
return frozenset([self._E[e] for e in P[0]]), frozenset([self._E[e] for e in P[1]]), frozenset([self._E[e] for e in P[2]])
4914
4915
cpdef _fast_isom_test(self, other):
4916
r"""
4917
Run a quick test to see if two quaternary matroids are isomorphic.
4918
4919
The test is based on comparing the invariants returned by
4920
self._invariant().
4921
4922
INPUT:
4923
4924
- ``other`` -- a quaternary matroid.
4925
4926
OUTPUT:
4927
4928
- ``True``, if ``self`` is isomorphic to ``other``;
4929
- ``False``, if ``self`` is not isomorphic to ``other``;
4930
- ``None``, if this test is inconclusive
4931
4932
EXAMPLES::
4933
4934
sage: M = matroids.named_matroids.Q10()\'a'
4935
sage: N = matroids.named_matroids.Q10()\'b'
4936
sage: M._fast_isom_test(N) is None
4937
True
4938
"""
4939
if self._invariant() != other._invariant():
4940
return False
4941
4942
# minors, dual
4943
4944
cpdef _minor(self, contractions, deletions):
4945
r"""
4946
Return a minor.
4947
4948
INPUT:
4949
4950
- ``contractions`` -- An object with Python's ``frozenset`` interface
4951
containing a subset of ``self.groundset()``.
4952
- ``deletions`` -- An object with Python's ``frozenset`` interface
4953
containing a subset of ``self.groundset()``.
4954
4955
.. NOTE::
4956
4957
This method does NOT do any checks. Besides the assumptions above,
4958
we assume the following:
4959
4960
- ``contractions`` is independent
4961
- ``deletions`` is coindependent
4962
- ``contractions`` and ``deletions`` are disjoint.
4963
4964
OUTPUT:
4965
4966
A matroid.
4967
4968
EXAMPLES::
4969
4970
sage: M = matroids.named_matroids.Q10()
4971
sage: N = M._minor(contractions=set(['a']), deletions=set([]))
4972
sage: N._minor(contractions=set([]), deletions=set(['b', 'c']))
4973
Quaternary matroid of rank 4 on 7 elements
4974
"""
4975
self._move_current_basis(contractions, deletions)
4976
bas = list(self.basis() - contractions)
4977
R = [self._prow[self._idx[b]] for b in bas]
4978
C = [c for c in range(len(self._E)) if self._E[c] not in deletions | contractions]
4979
return QuaternaryMatroid(matrix=(<QuaternaryMatrix>self._A).matrix_from_rows_and_columns(R, C), groundset=[self._E[c] for c in C], basis=bas)
4980
4981
cpdef is_valid(self):
4982
r"""
4983
Test if the data obey the matroid axioms.
4984
4985
Since this is a linear matroid over the field `\GF{4}`, this is always
4986
the case.
4987
4988
OUTPUT:
4989
4990
``True``.
4991
4992
EXAMPLES::
4993
4994
sage: M = Matroid(Matrix(GF(4, 'x'), [[]]))
4995
sage: M.is_valid()
4996
True
4997
"""
4998
return True
4999
5000
def __copy__(self):
5001
"""
5002
Create a shallow copy.
5003
5004
EXAMPLES::
5005
5006
sage: M = Matroid(Matrix(GF(4, 'x'), [[1, 0, 0, 1, 1],
5007
....: [0, 1, 0, 1, 2], [0, 0, 1, 1, 3]]))
5008
sage: N = copy(M) # indirect doctest
5009
sage: M == N
5010
True
5011
"""
5012
cdef QuaternaryMatroid N
5013
cdef list basis
5014
if self._representation is not None:
5015
N = QuaternaryMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True)
5016
else:
5017
basis = [0] * self.full_rank()
5018
for e in self.basis():
5019
basis[self._prow[self._idx[e]]] = e
5020
N = QuaternaryMatroid(groundset=self._E, matrix=self._A, basis=basis)
5021
N.rename(getattr(self, '__custom_name'))
5022
return N
5023
5024
def __deepcopy__(self, memo):
5025
"""
5026
Create a deep copy.
5027
5028
EXAMPLES::
5029
5030
sage: M = Matroid(Matrix(GF(4, 'x'), [[1, 0, 0, 1, 1],
5031
....: [0, 1, 0, 1, 2], [0, 0, 1, 1, -1]]))
5032
sage: N = deepcopy(M) # indirect doctest
5033
sage: M == N
5034
True
5035
"""
5036
from copy import deepcopy
5037
cdef QuaternaryMatroid N
5038
cdef list basis
5039
if self._representation is not None:
5040
N = QuaternaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True)
5041
else:
5042
basis = [0] * self.full_rank()
5043
for e in self.basis():
5044
basis[self._prow[self._idx[e]]] = e
5045
N = QuaternaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._A, memo), basis=deepcopy(basis, memo))
5046
N.rename(deepcopy(getattr(self, '__custom_name'), memo))
5047
return N
5048
5049
def __reduce__(self):
5050
"""
5051
Save the matroid for later reloading.
5052
5053
OUTPUT:
5054
5055
A tuple ``(unpickle_quaternary_matroid, (version, data))``, where
5056
``unpickle_quaternary_matroid`` is the name of a function that,
5057
when called with ``(version, data)``, produces a matroid isomorphic to
5058
``self``. ``version`` is an integer (currently 0) and ``data`` is a
5059
tuple ``(A, E, B, name)`` where ``A`` is the representation
5060
matrix, ``E`` is the groundset of the matroid, ``B`` is the currently
5061
displayed basis, and ``name`` is a custom name.
5062
5063
.. WARNING::
5064
5065
Users should never call this function directly.
5066
5067
EXAMPLES::
5068
5069
sage: M = Matroid(Matrix(GF(4, 'x'), [[1, 0, 0, 1], [0, 1, 0, 1],
5070
....: [0, 0, 1, 1]]))
5071
sage: M == loads(dumps(M)) # indirect doctest
5072
True
5073
sage: M.rename("U34")
5074
sage: loads(dumps(M))
5075
U34
5076
"""
5077
import sage.matroids.unpickling
5078
version = 0
5079
cdef list basis = [0] * self.full_rank()
5080
if self._representation is not None:
5081
A = self._representation
5082
gs = self._E
5083
basis = None
5084
else:
5085
A = self._A
5086
for e in self.basis():
5087
basis[self._prow[self._idx[e]]] = e
5088
rows, cols = self._current_rows_cols()
5089
gs = rows + cols
5090
data = (A, gs, basis, getattr(self, '__custom_name'))
5091
return sage.matroids.unpickling.unpickle_quaternary_matroid, (version, data)
5092
5093
# Regular Matroids
5094
5095
cdef class RegularMatroid(LinearMatroid):
5096
r"""
5097
Regular matroids.
5098
5099
A regular matroid is a linear matroid represented over the integers by a
5100
totally unimodular matrix.
5101
5102
The simplest way to create a ``RegularMatroid`` is by giving only a matrix
5103
`A`. Then, the groundset defaults to ``range(A.ncols())``.
5104
Any iterable object `E` can be given as a groundset. If `E` is a list, then ``E[i]`` will label the `i`-th column of `A`.
5105
Another possibility is to specify a 'reduced' matrix `B`, to create the matroid induced by `A = [ I B ]`.
5106
5107
INPUT:
5108
5109
- ``matrix`` -- (default: ``None``) a matrix whose column vectors
5110
represent the matroid.
5111
- ``reduced_matrix`` -- (default: ``None``) a matrix `B` such that
5112
`[I\ \ B]` represents the matroid, where `I` is an identity matrix with
5113
the same number of rows as `B`. Only one of ``matrix`` and
5114
``reduced_matrix`` should be provided.
5115
- ``groundset`` -- (default: ``None``) an iterable containing the element
5116
labels. When provided, must have the correct number of elements: the
5117
number of columns of ``matrix`` or the number of rows plus the number of
5118
columns of ``reduced_matrix``.
5119
- ``ring`` -- (default: ``None``) ignored.
5120
- ``keep_initial_representation`` -- (default: ``True``) boolean. Decides
5121
whether or not an internal copy of the input matrix should be preserved.
5122
This can help to see the structure of the matroid (e.g. in the case of
5123
graphic matroids), and makes it easier to look at extensions. However,
5124
the input matrix may have redundant rows, and sometimes it is desirable
5125
to store only a row-reduced copy.
5126
- ``basis`` -- (default: ``None``) when provided, this is an ordered
5127
subset of ``groundset``, such that the submatrix of ``matrix`` indexed
5128
by ``basis`` is an identity matrix. In this case, no row reduction takes
5129
place in the initialization phase.
5130
5131
OUTPUT:
5132
5133
A ``RegularMatroid`` instance based on the data above.
5134
5135
.. NOTE::
5136
5137
The recommended way to generate a regular matroid is through the
5138
:func:`Matroid() <sage.matroids.constructor.Matroid>` function. This
5139
is usually the preferred way, since it automatically chooses between
5140
``RegularMatroid`` and other classes. Moreover, it will test whether
5141
the input actually yields a regular matroid, unlike this class.
5142
For direct access to the ``RegularMatroid`` constructor, run::
5143
5144
sage: from sage.matroids.advanced import *
5145
5146
.. WARNING::
5147
5148
No checks are performed to ensure the input data form an actual regular
5149
matroid! If not, the behavior is unpredictable, and the internal
5150
representation can get corrupted. If in doubt, run
5151
:meth:`self.is_valid() <RegularMatroid.is_valid>` to ensure the data
5152
are as desired.
5153
5154
EXAMPLES::
5155
5156
sage: A = Matrix(ZZ, 2, 4, [[1, 0, 1, 1], [0, 1, 1, 1]])
5157
sage: M = Matroid(A, regular=True)
5158
sage: M
5159
Regular matroid of rank 2 on 4 elements with 5 bases
5160
sage: sorted(M.groundset())
5161
[0, 1, 2, 3]
5162
sage: Matrix(M)
5163
[1 0 1 1]
5164
[0 1 1 1]
5165
sage: M = Matroid(matrix=A, groundset='abcd', regular=True)
5166
sage: sorted(M.groundset())
5167
['a', 'b', 'c', 'd']
5168
"""
5169
def __init__(self, matrix=None, groundset=None, reduced_matrix=None, ring=None, keep_initial_representation=True):
5170
"""
5171
See class definition for full documentation.
5172
5173
.. NOTE::
5174
5175
The extra argument ``basis``, when provided, is an ordered list of
5176
elements of the groundset, ordered such that they index a standard
5177
identity matrix within ``matrix``.
5178
5179
EXAMPLES::
5180
5181
sage: from sage.matroids.advanced import *
5182
sage: RegularMatroid(matrix=Matrix(ZZ, [[1, 0, 1, 1, 1],
5183
....: [0, 1, 1, 1, 1]])) # indirect doctest
5184
Regular matroid of rank 2 on 5 elements with 7 bases
5185
"""
5186
LinearMatroid.__init__(self, matrix, groundset, reduced_matrix, ring=ZZ, keep_initial_representation=keep_initial_representation)
5187
5188
cdef list _setup_internal_representation(self, matrix, reduced_matrix, ring, keep_initial_representation):
5189
"""
5190
Setup the internal representation matrix ``self._A`` and the array of
5191
row- and column indices ``self._prow``.
5192
5193
Return the displayed basis.
5194
"""
5195
cdef IntegerMatrix A
5196
cdef long r, c
5197
cdef list P
5198
if matrix is not None:
5199
reduced = False
5200
if not isinstance(matrix, IntegerMatrix):
5201
A = IntegerMatrix(matrix.nrows(), matrix.ncols(), M=matrix)
5202
else:
5203
A = (<IntegerMatrix>matrix).copy() # Deprecated Sage matrix operation
5204
if keep_initial_representation:
5205
self._representation = A.copy() # Deprecated Sage matrix operation
5206
P = gauss_jordan_reduce(A, xrange(A.ncols()))
5207
self._A = A.matrix_from_rows_and_columns(range(len(P)), [c for c in xrange(matrix.ncols()) if not c in P])
5208
else:
5209
reduced = True
5210
if not isinstance(reduced_matrix, IntegerMatrix):
5211
self._A = IntegerMatrix(reduced_matrix.nrows(), reduced_matrix.ncols(), M=reduced_matrix)
5212
else:
5213
self._A = (<IntegerMatrix>reduced_matrix).copy() # Deprecated Sage matrix operation
5214
P = range(self._A.nrows())
5215
self._prow = <long* > sage_malloc((self._A.nrows() + self._A.ncols()) * sizeof(long))
5216
if matrix is not None:
5217
for r in xrange(len(P)):
5218
self._prow[P[r]] = r
5219
r = 0
5220
for c in xrange(A.ncols()):
5221
if c not in P:
5222
self._prow[c] = r
5223
r += 1
5224
else:
5225
for r from 0 <= r < self._A.nrows():
5226
self._prow[r] = r
5227
for r from 0 <= r < self._A.ncols():
5228
self._prow[self._A.nrows() + r] = r
5229
return P
5230
5231
cpdef base_ring(self):
5232
"""
5233
Return the base ring of the matrix representing the matroid, in this
5234
case `\ZZ`.
5235
5236
EXAMPLES::
5237
5238
sage: M = matroids.named_matroids.R10()
5239
sage: M.base_ring()
5240
Integer Ring
5241
"""
5242
return ZZ
5243
5244
cpdef characteristic(self):
5245
"""
5246
Return the characteristic of the base ring of the matrix representing
5247
the matroid, in this case `0`.
5248
5249
EXAMPLES::
5250
5251
sage: M = matroids.named_matroids.R10()
5252
sage: M.characteristic()
5253
0
5254
"""
5255
return 0
5256
5257
cdef bint __is_exchange_pair(self, long x, long y):
5258
r"""
5259
Check if ``self.basis() - x + y`` is again a basis. Internal method.
5260
"""
5261
return (<IntegerMatrix>self._A).get(self._prow[x], self._prow[y]) # Not a Sage matrix operation
5262
5263
cdef bint __exchange(self, long x, long y):
5264
"""
5265
Put element indexed by ``x`` into basis, taking out element ``y``. Assumptions are that this is a valid basis exchange.
5266
5267
.. NOTE::
5268
5269
Safe for noncommutative rings.
5270
"""
5271
cdef long px, py, r
5272
cdef int a, piv, pivi
5273
px = self._prow[x]
5274
py = self._prow[y]
5275
piv = (<IntegerMatrix>self._A).get(px, py) # Not a Sage matrix operation
5276
pivi = piv # NOTE: 1 and -1 are their own inverses.
5277
(<IntegerMatrix>self._A).rescale_row_c(px, pivi, 0)
5278
(<IntegerMatrix>self._A).set(px, py, pivi + 1) # pivoting without column scaling. Add extra so column does not need adjusting # Not a Sage matrix operation
5279
for r in xrange(self._A.nrows()): # if A and A' are the matrices before and after pivoting, then
5280
a = (<IntegerMatrix>self._A).get(r, py) # ker[I A] equals ker[I A'] except for the labelling of the columns # Not a Sage matrix operation
5281
if a and r != px:
5282
(<IntegerMatrix>self._A).add_multiple_of_row_c(r, px, -a, 0)
5283
(<IntegerMatrix>self._A).set(px, py, pivi) # Not a Sage matrix operation
5284
self._prow[y] = px
5285
self._prow[x] = py
5286
BasisExchangeMatroid.__exchange(self, x, y)
5287
5288
cdef __exchange_value(self, long x, long y):
5289
r"""
5290
Return the (x, y) entry of the current representation.
5291
5292
.. NOTE::
5293
5294
This uses get_unsafe(), which returns a Sage ``Integer`` instance,
5295
rather than the ``int`` returned by ``get``. The
5296
advantage is that cross ratio tests will return rational numbers
5297
rather than unwarranted zeroes.
5298
"""
5299
return (<IntegerMatrix>self._A).get_unsafe(self._prow[x], self._prow[y])
5300
5301
def _repr_(self):
5302
"""
5303
Return a string representation of ``self``.
5304
5305
EXAMPLES::
5306
5307
sage: M = matroids.named_matroids.R10()
5308
sage: M.rename()
5309
sage: repr(M) # indirect doctest
5310
'Regular matroid of rank 5 on 10 elements with 162 bases'
5311
"""
5312
S = "Regular matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements with " + str(self.bases_count()) + " bases"
5313
return S
5314
5315
cpdef bases_count(self):
5316
"""
5317
Count the number of bases.
5318
5319
EXAMPLES::
5320
5321
sage: M = matroids.CompleteGraphic(5)
5322
sage: M.bases_count()
5323
125
5324
5325
ALGORITHM:
5326
5327
Since the matroid is regular, we use Kirchhoff's Matrix-Tree Theorem.
5328
See also :wikipedia:`Kirchhoff's_theorem`.
5329
"""
5330
if self._bases_count is None:
5331
R = self._basic_representation()._matrix_()
5332
self._bases_count = (R * R.transpose()).det()
5333
return self._bases_count
5334
5335
cpdef _projection(self):
5336
"""
5337
Return the projection matrix onto the row space.
5338
5339
INPUT:
5340
5341
- Nothing
5342
5343
OUTPUT:
5344
5345
A matrix `P`, defined as follows. If `A` is a representation matrix
5346
of the matroid, then `Q = A^T (A A^T)^{-1} A`. Finally, `P` is equal
5347
to `Q` multiplied by the number of bases of the matroid.
5348
5349
The matrix `P` is independent of the choice of `A`, except for column
5350
scaling. It has the property that `xP` is the orthogonal projection of
5351
the row vector `x` onto the row space of `A`. For regular matroids,
5352
there is an extended Matrix Tree theorem that derives the fraction of
5353
bases containing a subset by computing the determinant of the
5354
principal submatrix corresponding to that subset. See [Lyons]_ .
5355
In particular, the entries of `P` are integers.
5356
5357
EXAMPLES::
5358
5359
sage: from sage.matroids.advanced import *
5360
sage: M = RegularMatroid(reduced_matrix=Matrix([[-1, 0, 1],
5361
....: [-1, 1, 0], [0, 1, -1]]))
5362
sage: M._projection()
5363
[ 8 -4 4 -4 0 4]
5364
[-4 8 -4 -4 4 0]
5365
[ 4 -4 8 0 4 -4]
5366
[-4 -4 0 8 -4 -4]
5367
[ 0 4 4 -4 8 -4]
5368
[ 4 0 -4 -4 -4 8]
5369
"""
5370
if self._r_projection is None:
5371
R = self._basic_representation()._matrix_()
5372
X = (R * R.transpose())
5373
self._bases_count = X.det()
5374
self._r_projection = self._bases_count * R.transpose() * X.inverse() * R
5375
return self._r_projection
5376
5377
cpdef _invariant(self):
5378
"""
5379
Compute a regular matroid invariant.
5380
5381
OUTPUT:
5382
5383
The hash of a list of pairs `(w, A[w])` and `(w, B[w])`, where `A[w]`
5384
counts the number of `i` such that `|P[i, i]|=w` (where `P` is the
5385
projection matrix from ``self._projection()``), and `B[w]` counts the
5386
number of pairs `(i, j)` such that `|P[i, j]|=w`.
5387
5388
EXAMPLES::
5389
5390
sage: M = matroids.named_matroids.R10()
5391
sage: N = matroids.named_matroids.R10().dual()
5392
sage: O = matroids.named_matroids.R12()
5393
sage: M._invariant() == N._invariant()
5394
True
5395
sage: M._invariant() == O._invariant()
5396
False
5397
"""
5398
# TODO: this currently uses Sage matrices internally. Perhaps dependence on those can be eliminated for further speed gains.
5399
if self._r_invariant is not None:
5400
return self._r_invariant
5401
cdef Matrix P
5402
P = self._projection()
5403
A = {}
5404
B = {}
5405
for i in xrange(P.nrows()):
5406
w = P.get_unsafe(i, i)
5407
if w != 0:
5408
if w in A:
5409
A[w] += 1
5410
else:
5411
A[w] = 1
5412
for j in xrange(i):
5413
w = abs(P.get_unsafe(i, j))
5414
if w != 0:
5415
if w in B:
5416
B[w] += 1
5417
else:
5418
B[w] = 1
5419
self._r_invariant = hash(tuple([tuple([(w, A[w]) for w in sorted(A)]), tuple([(w, B[w]) for w in sorted(B)])]))
5420
return self._r_invariant
5421
5422
cpdef _hypergraph(self):
5423
"""
5424
Create a bipartite digraph and a vertex partition.
5425
5426
INPUT:
5427
5428
- Nothing.
5429
5430
OUTPUT:
5431
5432
- ``PV`` -- A partition of the vertices of ``G``.
5433
- ``tups`` -- A list of pairs ``(x, y)``, where ``x`` denotes the
5434
color class of a part and ``y`` the number of elements in that part.
5435
- ``G`` -- a graph.
5436
5437
All are derived from the entries of the projection matrix `P`. The
5438
partition ``PV`` groups vertices of the form `i` by the value of
5439
`P[i, i]`. Whenever `P[i, j]` is nonzero, there are edges `i - (i, j)`
5440
and `j - (i, j)`. Finally, the vertices `(i, j)` are grouped by value
5441
of `P[i, j]`.
5442
5443
EXAMPLES::
5444
5445
sage: M = matroids.named_matroids.R10()
5446
sage: PV, tups, G = M._hypergraph()
5447
sage: G
5448
Digraph on 55 vertices
5449
"""
5450
# NEW VERSION, USES SAGE'S GRAPH ISOMORPHISM
5451
from sage.graphs.all import Graph, DiGraph
5452
if self._r_hypergraph is not None:
5453
return (self._hypergraph_vertex_partition, self._hypergraph_tuples, self._r_hypergraph)
5454
cdef Matrix P = self._projection()
5455
A = {}
5456
B = {}
5457
V = []
5458
E = []
5459
for i in xrange(P.nrows()):
5460
e = self._E[i]
5461
w = P.get_unsafe(i, i)
5462
if w != 0:
5463
if w in A:
5464
A[w].append(e)
5465
else:
5466
A[w] = [e]
5467
V.append(e)
5468
for j in xrange(i):
5469
f = self._E[j]
5470
w = abs(P.get_unsafe(i, j))
5471
if w != 0:
5472
if w in B:
5473
B[w].append(frozenset([e, f]))
5474
else:
5475
B[w] = [frozenset([e, f])]
5476
E.append(frozenset([e, f]))
5477
self._hypergraph_vertex_partition = [[str(x) for x in A[w]] for w in sorted(A)] + [['**' + str(x) for x in B[w]] for w in sorted(B)]
5478
self._hypergraph_tuples = [(w, len(A[w])) for w in sorted(A)] + [(w, len(B[w])) for w in sorted(B)]
5479
G = DiGraph()
5480
G.add_vertices([str(x) for x in V] + ['**' + str(x) for x in E])
5481
# Note that Sage's Graph object attempts a sort on calling G.vertices(), which means vertices have to be well-behaved.
5482
for X in E:
5483
Y = list(X)
5484
G.add_edge(str(Y[0]), '**' + str(X))
5485
G.add_edge(str(Y[1]), '**' + str(X))
5486
5487
self._r_hypergraph = G
5488
return self._hypergraph_vertex_partition, self._hypergraph_tuples, self._r_hypergraph
5489
# REMNANT OF THE OLD CODE THAT WAS NOT YET TRANSLATED TO SAGE'S GRAPH ISOMORPHISM. POTENTIAL SPEEDUP?
5490
# C = []
5491
# if len(A) < 5:
5492
# for i in xrange(P.nrows()):
5493
# for j in xrange(i):
5494
# if P.get_unsafe(i, j) == 0:
5495
# continue
5496
# for k in xrange(j):
5497
# w = P.get_unsafe(i, j)*P.get_unsafe(j, k)*P.get_unsafe(k, i)
5498
# if w < 0:
5499
# C.append(frozenset([self._E[i], self._E[j], self._E[k]]))
5500
# self._r_hypergraph.add_color_class(C)
5501
# self._r_hypergraph = self._r_hypergraph.max_refined()
5502
# return self._r_hypergraph
5503
5504
cpdef _is_isomorphic(self, other):
5505
"""
5506
Test if ``self`` is isomorphic to ``other``.
5507
5508
Internal version that performs no checks on input.
5509
5510
INPUT:
5511
5512
- ``other`` -- A matroid.
5513
5514
OUTPUT:
5515
5516
Boolean.
5517
5518
EXAMPLES::
5519
5520
sage: M1 = matroids.Wheel(3)
5521
sage: M2 = matroids.CompleteGraphic(4)
5522
sage: M1._is_isomorphic(M2)
5523
True
5524
5525
sage: M1 = matroids.Wheel(3)
5526
sage: M2 = matroids.named_matroids.Fano()
5527
sage: M1._is_isomorphic(M2)
5528
False
5529
sage: M1._is_isomorphic(M2.delete('a'))
5530
True
5531
"""
5532
if type(other) == RegularMatroid:
5533
return self.is_field_isomorphic(other)
5534
else:
5535
return LinearMatroid._is_isomorphic(self, other)
5536
5537
cpdef _fast_isom_test(self, other):
5538
r"""
5539
Run a quick test to see if two regular matroids are isomorphic.
5540
5541
The test is based on:
5542
5543
* A comparison of the number of bases, which may be computed
5544
efficiently through the matrix-tree lemma (see self.bases_count()).
5545
* A comparison of the orthogonal projection matrices of both matroids
5546
(see self._invariant()).
5547
* A isomorphism test which makes use of a hypergraph derived from the
5548
orthogonal projection matrix (see self._hypertest()).
5549
5550
INPUT:
5551
5552
- ``other`` -- a regular matroid.
5553
5554
OUTPUT:
5555
5556
- ``True``, if ``self`` is isomorphic to ``other``;
5557
- ``False``, if ``self`` is not isomorphic to ``other``;
5558
5559
EXAMPLES::
5560
5561
sage: M = matroids.named_matroids.R10()\'a'
5562
sage: N = matroids.named_matroids.R10()\'b'
5563
sage: M._fast_isom_test(N)
5564
True
5565
"""
5566
if self.bases_count() != other.bases_count():
5567
return False
5568
if self._invariant() != other._invariant():
5569
return False
5570
if self.size() > 8: # TODO: Optimize the cutoff. _hypertest() is slow for small matroids, and can be fast for larger ones.
5571
return self._hypertest(other)
5572
5573
cdef _hypertest(self, other):
5574
"""
5575
Test if the hypergraphs associated with ``self`` and ``other`` are
5576
isomorphic.
5577
5578
INPUT:
5579
5580
- ``other`` -- A ``RegularMatroid`` instance.
5581
5582
OUTPUT:
5583
5584
- ``True`` if the hypergraphs are isomorphic; ``False`` otherwise.
5585
"""
5586
from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic
5587
HS = self._hypergraph()
5588
HO = other._hypergraph()
5589
VO = []
5590
for X in HO[0]:
5591
VO.extend(X)
5592
return isomorphic(HS[2], HO[2], HS[0], VO, 1, 1) is not None
5593
5594
cpdef has_line_minor(self, k, hyperlines=None):
5595
"""
5596
Test if the matroid has a `U_{2, k}`-minor.
5597
5598
The matroid `U_{2, k}` is a matroid on `k` elements in which every
5599
subset of at most 2 elements is independent, and every subset of more
5600
than two elements is dependent.
5601
5602
The optional argument ``hyperlines`` restricts the search space: this
5603
method returns ``False`` if `si(M/F)` is isomorphic to `U_{2, l}` with
5604
`l \geq k` for some `F` in ``hyperlines``, and ``True`` otherwise.
5605
5606
INPUT:
5607
5608
- ``k`` -- the length of the line minor
5609
- ``hyperlines`` -- (default: ``None``) a set of flats of codimension
5610
2. Defaults to the set of all flats of codimension 2.
5611
5612
OUTPUT:
5613
5614
Boolean.
5615
5616
.. SEEALSO::
5617
5618
:meth:`Matroid.has_minor() <sage.matroids.matroid.Matroid.has_minor>`
5619
5620
EXAMPLES::
5621
5622
sage: M = matroids.named_matroids.R10()
5623
sage: M.has_line_minor(4)
5624
False
5625
sage: M.has_line_minor(3)
5626
True
5627
sage: M.has_line_minor(k=3, hyperlines=[['a', 'b', 'c'],
5628
....: ['a', 'b', 'd' ]])
5629
True
5630
5631
"""
5632
if k > 3:
5633
return False
5634
return Matroid.has_line_minor(self, k, hyperlines)
5635
5636
cpdef _linear_extension_chains(self, F, fundamentals=None):
5637
r"""
5638
Create a list of chains that determine single-element extensions of
5639
this linear matroid representation.
5640
5641
.. WARNING::
5642
5643
Intended for internal use; does no input checking.
5644
5645
INPUT:
5646
5647
- ``F`` -- an independent set of elements.
5648
- ``fundamentals`` -- (default: ``None``) a set elements of the base
5649
ring.
5650
5651
OUTPUT:
5652
5653
A list of chains, so each single-element regular extension of this
5654
linear matroid, with support contained in ``F``, is
5655
given by one of these chains.
5656
5657
EXAMPLES::
5658
5659
sage: M = matroids.Wheel(3)
5660
sage: len(M._linear_extension_chains(F=set([0, 1, 2])))
5661
7
5662
sage: M._linear_extension_chains(F=set())
5663
[{}]
5664
sage: M._linear_extension_chains(F=set([1]))
5665
[{}, {1: 1}]
5666
sage: len(M._linear_extension_chains(F=set([0, 1])))
5667
4
5668
"""
5669
if fundamentals is None:
5670
fundamentals = set([1])
5671
return LinearMatroid._linear_extension_chains(self, F, fundamentals)
5672
5673
cpdef is_graphic(self):
5674
"""
5675
Test if the regular matroid is graphic.
5676
5677
A matroid is *graphic* if there exists a graph whose edge set equals
5678
the groundset of the matroid, such that a subset of elements of the
5679
matroid is independent if and only if the corresponding subgraph is
5680
acyclic.
5681
5682
OUTPUT:
5683
5684
Boolean.
5685
5686
EXAMPLES::
5687
5688
sage: M = matroids.named_matroids.R10()
5689
sage: M.is_graphic()
5690
False
5691
sage: M = matroids.CompleteGraphic(5)
5692
sage: M.is_graphic()
5693
True
5694
sage: M.dual().is_graphic()
5695
False
5696
5697
ALGORITHM:
5698
5699
In a recent paper, Geelen and Gerards [GG12]_ reduced the problem to
5700
testing if a system of linear equations has a solution. While not the
5701
fastest method, and not necessarily constructive (in the presence of
5702
2-separations especially), it is easy to implement.
5703
"""
5704
return BinaryMatroid(reduced_matrix=self._reduced_representation()).is_graphic()
5705
5706
cpdef is_valid(self):
5707
r"""
5708
Test if the data obey the matroid axioms.
5709
5710
Since this is a regular matroid, this function tests if the
5711
representation matrix is *totally unimodular*, i.e. if all square
5712
submatrices have determinant in `\{-1, 0, 1\}`.
5713
5714
OUTPUT:
5715
5716
Boolean.
5717
5718
EXAMPLES::
5719
5720
sage: M = Matroid(Matrix(ZZ, [[1, 0, 0, 1, 1, 0, 1],
5721
....: [0, 1, 0, 1, 0, 1, 1],
5722
....: [0, 0, 1, 0, 1, 1, 1]]),
5723
....: regular=True, check=False)
5724
sage: M.is_valid()
5725
False
5726
sage: M = Matroid(graphs.PetersenGraph())
5727
sage: M.is_valid()
5728
True
5729
"""
5730
M = LinearMatroid(ring=QQ, reduced_matrix=self.representation(self.basis(), True, False))
5731
CR = M.cross_ratios()
5732
return CR.issubset(set([1]))
5733
5734
# Copying, loading, saving
5735
5736
def __copy__(self):
5737
"""
5738
Create a shallow copy.
5739
5740
EXAMPLES::
5741
5742
sage: M = matroids.named_matroids.R10()
5743
sage: N = copy(M) # indirect doctest
5744
sage: M == N
5745
True
5746
"""
5747
cdef RegularMatroid N
5748
if self._representation is not None:
5749
N = RegularMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True)
5750
else:
5751
rows, cols = self._current_rows_cols()
5752
N = RegularMatroid(groundset=rows + cols, reduced_matrix=self._A)
5753
N.rename(getattr(self, '__custom_name'))
5754
return N
5755
5756
def __deepcopy__(self, memo):
5757
"""
5758
Create a deep copy.
5759
5760
EXAMPLES::
5761
5762
sage: M = matroids.named_matroids.R10()
5763
sage: N = deepcopy(M) # indirect doctest
5764
sage: M == N
5765
True
5766
"""
5767
cdef RegularMatroid N
5768
if self._representation is not None:
5769
N = RegularMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True)
5770
else:
5771
rows, cols = self._current_rows_cols()
5772
N = RegularMatroid(groundset=deepcopy(rows + cols, memo), reduced_matrix=deepcopy(self._A, memo))
5773
N.rename(deepcopy(getattr(self, '__custom_name'), memo))
5774
return N
5775
5776
def __reduce__(self):
5777
"""
5778
Save the matroid for later reloading.
5779
5780
OUTPUT:
5781
5782
A tuple ``(unpickle_regular_matroid, (version, data))``, where
5783
``unpickle_regular_matroid`` is the name of a function that, when
5784
called with ``(version, data)``, produces a matroid isomorphic to
5785
``self``. ``version`` is an integer (currently 0) and ``data`` is a
5786
tuple ``(A, E, reduced, name)`` where ``A`` is the representation
5787
matrix, ``E`` is the groundset of the matroid, ``reduced`` is a
5788
boolean indicating whether ``A`` is a reduced matrix, and ``name`` is
5789
a custom name.
5790
5791
.. WARNING::
5792
5793
Users should never call this function directly.
5794
5795
EXAMPLES::
5796
5797
sage: from sage.matroids.advanced import *
5798
sage: M = matroids.named_matroids.R12()
5799
sage: M == loads(dumps(M)) # indirect doctest
5800
True
5801
sage: M.rename("R_{12}")
5802
sage: loads(dumps(M))
5803
R_{12}
5804
sage: M = RegularMatroid(Matrix(QQ, [[1, 0, 1], [1, 0, 1]]))
5805
sage: N = loads(dumps(M))
5806
sage: N.representation()
5807
[1 0 1]
5808
[1 0 1]
5809
"""
5810
import sage.matroids.unpickling
5811
cdef LeanMatrix A
5812
version = 0
5813
if self._representation is not None:
5814
A = self._representation
5815
gs = self._E
5816
reduced = False
5817
else:
5818
A = self._reduced_representation()
5819
rows, cols = self._current_rows_cols()
5820
gs = rows + cols
5821
reduced = True
5822
data = (A, gs, reduced, getattr(self, '__custom_name'))
5823
return sage.matroids.unpickling.unpickle_regular_matroid, (version, data)
5824
5825