diff --git a/src/doc/en/reference/matroids/index.rst b/src/doc/en/reference/matroids/index.rst index 4b854c47573..692d798d222 100644 --- a/src/doc/en/reference/matroids/index.rst +++ b/src/doc/en/reference/matroids/index.rst @@ -27,6 +27,7 @@ Concrete implementations :maxdepth: 1 sage/matroids/basis_matroid + sage/matroids/circuits_matroid sage/matroids/circuit_closures_matroid sage/matroids/linear_matroid sage/matroids/rank_matroid diff --git a/src/sage/matroids/circuits_matroid.pxd b/src/sage/matroids/circuits_matroid.pxd new file mode 100644 index 00000000000..2de06bd16ab --- /dev/null +++ b/src/sage/matroids/circuits_matroid.pxd @@ -0,0 +1,31 @@ +from sage.matroids.matroid cimport Matroid +from sage.matroids.set_system cimport SetSystem + +cdef class CircuitsMatroid(Matroid): + cdef frozenset _groundset # _E + cdef int _matroid_rank # _R + cdef SetSystem _C # circuits + cdef dict _k_C # k-circuits (k=len) + cdef bint _nsc_defined + cpdef groundset(self) noexcept + cpdef _rank(self, X) noexcept + cpdef full_rank(self) noexcept + cpdef _is_independent(self, F) noexcept + cpdef _max_independent(self, F) noexcept + cpdef _circuit(self, F) noexcept + + # enumeration + cpdef bases(self) noexcept + cpdef circuits(self, k=*) noexcept + cpdef nonspanning_circuits(self) noexcept + cpdef no_broken_circuits_sets(self, ordering=*) noexcept + + # properties + cpdef girth(self) noexcept + cpdef is_paving(self) noexcept + + # isomorphism + cpdef _is_isomorphic(self, other, certificate=*) noexcept + + # verification + cpdef is_valid(self) noexcept diff --git a/src/sage/matroids/circuits_matroid.pyx b/src/sage/matroids/circuits_matroid.pyx new file mode 100644 index 00000000000..bbcf6ce8319 --- /dev/null +++ b/src/sage/matroids/circuits_matroid.pyx @@ -0,0 +1,750 @@ +r""" +Circuits matroids + +Matroids are characterized by a list of circuits, which are minimal dependent +sets. The ``CircuitsMatroid`` class implements matroids using this information +as data. + +A ``CircuitsMatroid`` can be created from another matroid or from a list of +circuits. For a full description of allowed inputs, see +:class:`below `. It is +recommended to use the :func:`Matroid() ` +function for a more flexible way of constructing a ``CircuitsMatroid`` and +other classes of matroids. For direct access to the ``CircuitsMatroid`` +constructor, run:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + +AUTHORS: + +- Giorgos Mousa (2023-12-23): initial version +""" + +# **************************************************************************** +# Copyright (C) 2023 Giorgos Mousa +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.structure.richcmp cimport rich_to_bool, richcmp +from sage.matroids.matroid cimport Matroid +from sage.matroids.set_system cimport SetSystem +from cpython.object cimport Py_EQ, Py_NE + + +cdef class CircuitsMatroid(Matroid): + r""" + A matroid defined by its circuits. + + INPUT: + + - ``M`` -- a matroid (default: ``None``) + - ``groundset`` -- a list (default: ``None``); the groundset of the matroid + - ``circuits`` -- a list (default: ``None``); the collection of circuits of + the matroid + - ``nsc_defined`` -- boolean (default: ``False``); whether the matroid was + defined by its nonspanning circuits + + .. NOTE:: + + For a more flexible means of input, use the ``Matroid()`` function. + """ + + # necessary (__init__, groundset, _rank) + + def __init__(self, M=None, groundset=None, circuits=None, nsc_defined=False): + """ + Initialization of the matroid. See class docstring for full + documentation. + + TESTS:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.Fano()) + sage: TestSuite(M).run() + """ + if M is not None: + self._groundset = frozenset(M.groundset()) + self._C = SetSystem(list(M.groundset()), frozenset([frozenset(C) for C in M.circuits()])) + else: + self._groundset = frozenset(groundset) + self._C = SetSystem(list(groundset), frozenset([frozenset(C) for C in circuits])) + # k-circuits + self._k_C = {} + for C in self._C: + try: + self._k_C[len(C)] += [C] + except KeyError: + self._k_C[len(C)] = [] + self._k_C[len(C)] += [C] + self._matroid_rank = self.rank(self._groundset) + self._nsc_defined = nsc_defined + + cpdef groundset(self) noexcept: + """ + Return the groundset of the matroid. + + The groundset is the set of elements that comprise the matroid. + + OUTPUT: a set + + EXAMPLES:: + + sage: M = matroids.Theta(2) + sage: sorted(M.groundset()) + ['x0', 'x1', 'y0', 'y1'] + """ + return self._groundset + + cpdef _rank(self, X) noexcept: + """ + Return the rank of a set ``X``. + + This method does no checking on ``X``, and ``X`` may be assumed to have + the same interface as ``frozenset``. + + INPUT: + + - ``X`` -- an object with Python's ``frozenset`` interface + + OUTPUT: an integer; the rank of ``X`` in the matroid + + EXAMPLES:: + + sage: M = matroids.Theta(3) + sage: M._rank(['x1', 'y0', 'y2']) + 2 + """ + return len(self._max_independent(X)) + + # optional + + cpdef full_rank(self) noexcept: + r""" + Return the rank of the matroid. + + The *rank* of the matroid is the size of the largest independent + subset of the groundset. + + OUTPUT: an integer; the rank of the matroid + + EXAMPLES:: + + sage: M = matroids.Theta(20) + sage: M.full_rank() + 20 + """ + return self._matroid_rank + + cpdef _is_independent(self, F) noexcept: + """ + Test if input is independent. + + INPUT: + + - ``X`` -- An object with Python's ``frozenset`` interface containing + a subset of ``self.groundset()`` + + OUTPUT: boolean + + EXAMPLES:: + + sage: M = matroids.Theta(4) + sage: M._is_independent(['y0', 'y1', 'y3', 'x2']) + False + sage: M._is_independent(['y0', 'y2', 'y3', 'x2']) + True + """ + cdef set I = set(F) + cdef int s = len(F) + for i in self._k_C: + if i <= s: + for C in self._k_C[i]: + if C <= I: + return False + return True + + cpdef _max_independent(self, F) noexcept: + """ + Compute a maximal independent subset. + + INPUT: + + - ``X`` -- An object with Python's ``frozenset`` interface containing + a subset of ``self.groundset()`` + + OUTPUT: a frozenset; a maximal independent subset of ``X`` + + EXAMPLES:: + + sage: M = matroids.Theta(6) + sage: len(M._max_independent(M.groundset())) + 6 + """ + cdef set I = set(F) + for i in self._k_C: + for C in self._k_C[i]: + if i <= len(I) and i > 0: + if C <= I: + e = next(iter(C)) + I.remove(e) + + return frozenset(I) + + cpdef _circuit(self, F) noexcept: + """ + Return a minimal dependent subset. + + INPUT: + + - ``X`` -- An object with Python's ``frozenset`` interface containing + a subset of ``self.groundset()``. + + OUTPUT: a frozenset; a circuit contained in ``X``, if it exists. + Otherwise an error is raised. + + EXAMPLES:: + + sage: M = matroids.Theta(4) + sage: sorted(M._circuit(['y0', 'y1', 'y3', 'x2'])) + ['x2', 'y0', 'y1', 'y3'] + sage: M._circuit(['y0', 'y2', 'y3', 'x2']) + Traceback (most recent call last): + ... + ValueError: no circuit in independent set + """ + cdef set I = set(F) + for C in self._C: + if C <= I: + return C + raise ValueError("no circuit in independent set") + + cpdef _is_isomorphic(self, other, certificate=False) noexcept: + """ + Test if ``self`` is isomorphic to ``other``. + + INPUT: + + - ``other`` -- a matroid + - ``certificate`` -- boolean (default: ``False``) + + OUTPUT: boolean, and, if ``certificate=True``, a dictionary giving the + isomorphism or ``None`` + + EXAMPLES:: + + sage: M = matroids.Spike(3) + sage: from sage.matroids.basis_matroid import BasisMatroid + sage: N = BasisMatroid(M) + sage: M.is_isomorphic(N) + True + sage: N = matroids.catalog.Vamos() + sage: M.is_isomorphic(N) + False + + .. NOTE:: + + Internal version that does no input checking. + """ + if certificate: + return self._is_isomorphic(other), self._isomorphism(other) + N = CircuitsMatroid(other) + return self._C._isomorphism(N._C) is not None + + # representation + + def _repr_(self): + """ + Return a string representation of the matroid. + + EXAMPLES:: + + sage: matroids.Theta(10) + Theta_10: Matroid of rank 10 on 20 elements with 490 circuits + sage: matroids.catalog.NonDesargues() + NonDesargues: Matroid of rank 3 on 10 elements with 9 nonspanning circuits + """ + if self._nsc_defined: + return Matroid._repr_(self) + " with " + str(len(self.nonspanning_circuits())) + " nonspanning circuits" + else: + return Matroid._repr_(self) + " with " + str(len(self._C)) + " circuits" + + # comparison + + def __hash__(self): + r""" + Return an invariant of the matroid. + + This function is called when matroids are added to a set. It is very + desirable to override it so it can distinguish matroids on the same + groundset, which is a very typical use case! + + .. WARNING:: + + This method is linked to __richcmp__ (in Cython) and __cmp__ or + __eq__/__ne__ (in Python). If you override one, you should + (and in Cython: MUST) override the other! + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.Vamos()) + sage: N = CircuitsMatroid(matroids.catalog.Vamos()) + sage: hash(M) == hash(N) + True + sage: O = CircuitsMatroid(matroids.catalog.NonVamos()) + sage: hash(M) == hash(O) + False + """ + return hash(tuple([self.groundset(), frozenset(self._C)])) + + def __richcmp__(left, right, int op): + r""" + Compare two matroids. + + We take a very restricted view on equality: the objects need to be of + the exact same type (so no subclassing) and the internal data need to + be the same. For CircuitsMatroids, this means that the groundsets and + the sets of circuits of the two matroids are equal. + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.Pappus()) + sage: N = CircuitsMatroid(matroids.catalog.NonPappus()) + sage: M == N + False + sage: N = Matroid(circuits=M.circuits()) + sage: M == N + True + """ + cdef CircuitsMatroid lt, rt + if op not in [Py_EQ, Py_NE]: + return NotImplemented + if type(left) is not type(right): + return NotImplemented + lt = left + rt = right + if lt.groundset() != rt.groundset(): + return rich_to_bool(op, 1) + if lt.full_rank() != rt.full_rank(): + return rich_to_bool(op, 1) + return richcmp(frozenset(lt._C), frozenset(rt._C), op) + + # copying, loading, saving + + def __copy__(self): + """ + Create a shallow copy. + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.Vamos()) + sage: N = copy(M) # indirect doctest + sage: M == N + True + sage: M.groundset() is N.groundset() + True + """ + N = CircuitsMatroid(groundset=[], circuits=[]) + N._groundset = self._groundset + N._C = self._C + N._k_C = self._k_C + N._nsc_defined = self._nsc_defined + N._matroid_rank = self._matroid_rank + N.rename(self.get_custom_name()) + return N + + def __deepcopy__(self, memo=None): + """ + Create a deep copy. + + .. NOTE:: + + Since matroids are immutable, a shallow copy normally suffices. + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.Vamos()) + sage: N = deepcopy(M) # indirect doctest + sage: M == N + True + sage: M.groundset() is N.groundset() + False + """ + if memo is None: + memo = {} + from copy import deepcopy + # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. + N = CircuitsMatroid(groundset=deepcopy(self._groundset, memo), circuits=deepcopy(frozenset(self._C), memo)) + N.rename(deepcopy(self.get_custom_name(), memo)) + return N + + def __reduce__(self): + """ + Save the matroid for later reloading. + + OUTPUT: + + A tuple ``(unpickle, (version, data))``, where ``unpickle`` is the + name of a function that, when called with ``(version, data)``, + produces a matroid isomorphic to ``self``. ``version`` is an integer + (currently 0) and ``data`` is a tuple ``(E, C, name)`` where ``E`` is + the groundset, ``C`` is the list of circuits, and ``name`` is a custom + name. + + EXAMPLES:: + + sage: M = matroids.Theta(5) + sage: M == loads(dumps(M)) # indirect doctest + True + sage: M.reset_name() + sage: loads(dumps(M)) + Matroid of rank 5 on 10 elements with 45 circuits + """ + import sage.matroids.unpickling + data = (self._groundset, frozenset(self._C), self.get_custom_name()) + version = 0 + return sage.matroids.unpickling.unpickle_circuits_matroid, (version, data) + + # enumeration + + cpdef bases(self) noexcept: + r""" + Return the bases of the matroid. + + OUTPUT: a SetSystem + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) + sage: len(M.bases()) + 6 + """ + cdef SetSystem B, NSC + cdef bint flag + B = SetSystem(list(self.groundset())) + NSC = self.nonspanning_circuits() + from itertools import combinations + for S in combinations(self._groundset, self._matroid_rank): + flag = True + S = frozenset(S) + for C in NSC: + if C <= S: + flag = False + break + if flag: + B.append(S) + return B + + def bases_iterator(self): + r""" + Return an iterator over the bases of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) + sage: it = M.bases_iterator() + sage: it.__next__() + frozenset({0, 1}) + sage: sorted(M.bases_iterator(), key=str) + [frozenset({0, 1}), + frozenset({0, 2}), + frozenset({0, 3}), + frozenset({1, 2}), + frozenset({1, 3}), + frozenset({2, 3})] + """ + from itertools import combinations + cdef SetSystem NSC = self.nonspanning_circuits() + for B in combinations(self._groundset, self._matroid_rank): + B = frozenset(B) + if not any(C <= B for C in NSC): + yield B + + cpdef circuits(self, k=None) noexcept: + """ + Return the circuits of the matroid. + + INPUT: + + - ``k`` -- an integer (optional); the length of the circuits + + OUTPUT: a SetSystem + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) + sage: M.circuits() + Iterator over a system of subsets + sage: list(M.circuits(0)) + [] + sage: sorted(M.circuits(3), key=str) + [frozenset({0, 1, 2}), + frozenset({0, 1, 3}), + frozenset({0, 2, 3}), + frozenset({1, 2, 3})] + """ + cdef SetSystem C + C = SetSystem(list(self.groundset())) + if k is not None: + if k in self._k_C: + for c in self._k_C[k]: + C.append(c) + else: + for i in self._k_C: + for c in self._k_C[i]: + C.append(c) + return C + + def circuits_iterator(self, k=None): + """ + Return an iterator over the circuits of the matroid. + + INPUT: + + - ``k`` -- an integer (optional); the length of the circuits + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) + sage: sum(1 for C in M.circuits_iterator()) + 4 + sage: list(M.circuits_iterator(0)) + [] + sage: sorted(M.circuits_iterator(3), key=str) + [frozenset({0, 1, 2}), + frozenset({0, 1, 3}), + frozenset({0, 2, 3}), + frozenset({1, 2, 3})] + """ + if k is not None: + if k in self._k_C: + for C in self._k_C[k]: + yield C + else: + for i in self._k_C: + for C in self._k_C[i]: + yield C + + cpdef nonspanning_circuits(self) noexcept: + """ + Return the nonspanning circuits of the matroid. + + OUTPUT: a SetSystem + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) + sage: M.nonspanning_circuits() + Iterator over a system of subsets + """ + cdef list NSC = [] + for i in self._k_C: + if i <= self.rank(): + NSC.extend(self._k_C[i]) + return SetSystem(list(self.groundset()), NSC) + + def nonspanning_circuits_iterator(self): + """ + Return an iterator over the nonspanning circuits of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) + sage: list(M.nonspanning_circuits_iterator()) + [] + """ + for i in self._k_C: + if i <= self.rank(): + for C in self._k_C[i]: + yield C + + cpdef no_broken_circuits_sets(self, ordering=None) noexcept: + r""" + Return the no broken circuits (NBC) sets of ``self``. + + An NBC set is a subset `A` of the ground set under some total + ordering `<` such that `A` contains no broken circuit. + + INPUT: + + - ``ordering`` -- a total ordering of the groundset given as a list + + OUTPUT: a list of frozensets + + EXAMPLES:: + + sage: M = Matroid(circuits=[[1,2,3], [3,4,5], [1,2,4,5]]) + sage: SimplicialComplex(M.no_broken_circuits_sets()) + Simplicial complex with vertex set (1, 2, 3, 4, 5) + and facets {(1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5)} + sage: SimplicialComplex(M.no_broken_circuits_sets([5,4,3,2,1])) + Simplicial complex with vertex set (1, 2, 3, 4, 5) + and facets {(1, 3, 5), (1, 4, 5), (2, 3, 5), (2, 4, 5)} + + :: + + sage: M = Matroid(circuits=[[1,2,3], [1,4,5], [2,3,4,5]]) + sage: SimplicialComplex(M.no_broken_circuits_sets([5,4,3,2,1])) + Simplicial complex with vertex set (1, 2, 3, 4, 5) + and facets {(1, 3, 5), (2, 3, 5), (2, 4, 5), (3, 4, 5)} + + TESTS:: + + sage: M = Matroid(circuits=[[1,2,3], [3,4,5], [1,2,4,5]]) + sage: C1 = SimplicialComplex(M.no_broken_circuits_sets()) + sage: from sage.matroids.basis_matroid import BasisMatroid + sage: M = BasisMatroid(Matroid(circuits=[[1,2,3], [3,4,5], [1,2,4,5]])) + sage: C2 = SimplicialComplex(M.no_broken_circuits_sets()) + sage: C1 == C2 + True + """ + if ordering is None: + ordering = sorted(self.groundset(), key=str) + else: + if frozenset(ordering) != self.groundset(): + raise ValueError("not an ordering of the groundset") + + # compute broken circuits + cdef list BC = [] + for C in self._C: + for e in ordering: + if e in C: + BC.append(C - set([e])) + break + + cdef list F = [] # broken circuit complex facets + for B in self.bases(): + flag = True + for bc in BC: + if bc <= B: + flag = False + break + if flag: + F.append(B) + + from sage.topology.simplicial_complex import SimplicialComplex + return [frozenset(f) for f in SimplicialComplex(F).face_iterator()] + + # properties + + cpdef girth(self) noexcept: + r""" + Return the girth of the matroid. + + The girth is the size of the smallest circuit. In case the matroid has + no circuits the girth is `\infty`. + + EXAMPLES:: + + sage: matroids.Theta(10).girth() + 3 + + REFERENCES: + + [Oxl2011]_, p. 327. + """ + return min(self._k_C, default=float('inf')) + + cpdef is_paving(self) noexcept: + """ + Return if ``self`` is paving. + + A matroid is paving if each of its circuits has size `r` or `r+1`. + + OUTPUT: boolean + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.Vamos()) + sage: M.is_paving() + True + """ + return self.girth() >= self.rank() + + # verification + + cpdef is_valid(self) noexcept: + r""" + Test if ``self`` obeys the matroid axioms. + + For a matroid defined by its circuits, we check the circuit axioms. + + OUTPUT: boolean + + EXAMPLES:: + + sage: C = [[1, 2, 3], [3, 4, 5], [1, 2, 4, 5]] + sage: M = Matroid(circuits=C) + sage: M.is_valid() + True + sage: C = [[1,2], [1, 2, 3], [3, 4, 5], [1, 2, 4, 5]] + sage: M = Matroid(circuits=C) + sage: M.is_valid() + False + sage: C = [[3,6], [1, 2, 3], [3, 4, 5], [1, 2, 4, 5]] + sage: M = Matroid(circuits=C) + sage: M.is_valid() + False + sage: C = [[3,6], [1, 2, 3], [3, 4, 5], [1, 2, 6], [6, 4, 5], [1, 2, 4, 5]] + sage: M = Matroid(circuits=C) + sage: M.is_valid() + True + sage: C = [[], [1, 2, 3], [3, 4, 5], [1, 2, 4, 5]] + sage: M = Matroid(circuits=C) + sage: M.is_valid() + False + sage: C = [[1, 2, 3], [3, 4, 5]] + sage: M = Matroid(circuits=C) + sage: M.is_valid() + False + """ + from itertools import combinations_with_replacement + cdef int i, j, k, S_len + cdef frozenset C1, C2, C3, I12, U12 + cdef bint flag + for (i, j) in combinations_with_replacement(sorted(self._k_C), 2): + # loop through all circuit length pairs (i, j) with i <= j + for C1 in self._k_C[i]: + if not C1: # the empty set can't be a circuit + return False + for C2 in self._k_C[j]: + I12 = C1 & C2 + if not I12: # C1 and C2 are disjoint; nothing to test + continue + if len(I12) == len(C1): + if len(C1) == len(C2): # they are the same circuit + break + # C1 < C2; a circuit can't be a subset of another circuit + return False + # check circuit elimination axiom + U12 = C1 | C2 + S_len = len(U12) - 1 # the size of S below + for e in I12: + flag = False + S = U12 - {e} + for k in self._k_C: + if k <= S_len: + for C3 in self._k_C[k]: + if C3 <= S: + flag = True + break + if flag: + break + if not flag: + return False + return True diff --git a/src/sage/matroids/constructor.py b/src/sage/matroids/constructor.py index 40a39a4eda8..137f4c28230 100644 --- a/src/sage/matroids/constructor.py +++ b/src/sage/matroids/constructor.py @@ -112,6 +112,7 @@ import sage.matroids.matroid import sage.matroids.basis_exchange_matroid from .rank_matroid import RankMatroid +from .circuits_matroid import CircuitsMatroid from .circuit_closures_matroid import CircuitClosuresMatroid from .basis_matroid import BasisMatroid from .linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid @@ -302,21 +303,23 @@ def Matroid(groundset=None, data=None, **kwds): :: sage: M1 = Matroid(groundset='abc', circuits=['bc']) - sage: M2 = Matroid(bases=['ab', 'ac']) - sage: M1 == M2 - True A matroid specified by a list of circuits gets converted to a - :class:`BasisMatroid ` + :class:`CircuitsMatroid ` internally:: + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M2 = CircuitsMatroid(Matroid(bases=['ab', 'ac'])) + sage: M1 == M2 + True + sage: M = Matroid(groundset='abcd', circuits=['abc', 'abd', 'acd', ....: 'bcd']) sage: type(M) - <... 'sage.matroids.basis_matroid.BasisMatroid'> + Strange things can happen if the input does not satisfy the circuit - axioms, and these are not always caught by the + axioms, and these can be caught by the :meth:`is_valid() ` method. So always check whether your input makes sense! @@ -324,11 +327,7 @@ def Matroid(groundset=None, data=None, **kwds): sage: M = Matroid('abcd', circuits=['ab', 'acd']) sage: M.is_valid() - True - sage: [sorted(C) for C in M.circuits()] # random - [['a']] - - + False #. Graph: @@ -762,17 +761,7 @@ def Matroid(groundset=None, data=None, **kwds): groundset = set() for C in data: groundset.update(C) - # determine the rank by computing a basis element - b = set(groundset) - for C in data: - I = b.intersection(C) - if len(I) >= len(C): - b.discard(I.pop()) - rk = len(b) - # Construct the basis matroid of appropriate rank. Note: slow! - BB = [frozenset(B) for B in combinations(groundset, rk) - if not any(frozenset(C).issubset(B) for C in data)] - M = BasisMatroid(groundset=groundset, bases=BB) + M = CircuitsMatroid(groundset=groundset, circuits=data) # Nonspanning circuits: elif key == 'nonspanning_circuits': @@ -796,10 +785,13 @@ def Matroid(groundset=None, data=None, **kwds): break if flag: B += [list(b)] - M = BasisMatroid(groundset=groundset, bases=B) + # convert to circuits matroid defined by non-spanning circuits + M = CircuitsMatroid( + BasisMatroid(groundset=groundset, bases=B), + nsc_defined=True + ) # Graphs: - elif key == 'graph': from sage.graphs.graph import Graph diff --git a/src/sage/matroids/database_collections.py b/src/sage/matroids/database_collections.py index 7480df75e62..c3276be61d6 100644 --- a/src/sage/matroids/database_collections.py +++ b/src/sage/matroids/database_collections.py @@ -250,8 +250,8 @@ def OxleyMatroids(): REFERENCES: - These matroids are the nonparametrized matroids that appear in the - Appendix ``Some Interesting Matroids`` in [Oxl2011]_ (p. 639-64). + These matroids are the nonparametrized matroids that appear in the Appendix + ``Some Interesting Matroids`` in [Oxl2011]_ (p. 639-64). """ from sage.matroids.database_matroids import ( U24, U25, U35, K4, Whirl3, Q6, P6, U36, R6, diff --git a/src/sage/matroids/database_matroids.py b/src/sage/matroids/database_matroids.py index 1272efefff5..0a75ce22283 100644 --- a/src/sage/matroids/database_matroids.py +++ b/src/sage/matroids/database_matroids.py @@ -198,8 +198,8 @@ def Whirl3(): sage: W.automorphism_group().is_transitive() False - For all elements `e`, neither `\mathcal{W}_3 \setminus \{e\}` nor - `\mathcal{W}_3 / \{e\}` is `3`-connected:: + For all elements `e`, neither `\mathcal{W}_3 \setminus \{e\}` nor `\mathcal{W}_3 / \{e\}` + is `3`-connected:: sage: import random sage: e = random.choice(list(W.groundset())) @@ -266,8 +266,7 @@ def P6(): {2: {{'a', 'b', 'c'}}, 3: {{'a', 'b', 'c', 'd', 'e', 'f'}}} sage: len(set(M.nonspanning_circuits()).difference(M.nonbases())) == 0 True - sage: Matroid(matrix=random_matrix(GF(4, 'a'), ncols=5, - ....: nrows=5)).has_minor(M) + sage: Matroid(matrix=random_matrix(GF(4, 'a'), ncols=5, nrows=5)).has_minor(M) False sage: M.is_valid() True @@ -445,8 +444,8 @@ def NonFano(): sage: M = matroids.catalog.NonFano(); M NonFano: Ternary matroid of rank 3 on 7 elements, type 0- sage: setprint(M.nonbases()) - [{'a', 'b', 'f'}, {'a', 'c', 'e'}, {'a', 'd', 'g'}, {'b', 'c', 'd'}, - {'b', 'e', 'g'}, {'c', 'f', 'g'}] + [{'a', 'b', 'f'}, {'a', 'c', 'e'}, {'a', 'd', 'g'}, + {'b', 'c', 'd'}, {'b', 'e', 'g'}, {'c', 'f', 'g'}] sage: M.delete('f').is_isomorphic(matroids.CompleteGraphic(4)) True sage: M.delete('g').is_isomorphic(matroids.CompleteGraphic(4)) @@ -479,8 +478,8 @@ def NonFanoDual(): sage: sorted(M.groundset()) ['a', 'b', 'c', 'd', 'e', 'f', 'g'] - Every single-element contraction of `(F_7^-)^*` is isomorphic to `M(K_4)` - or `\mathcal{W}^3`:: + Every single-element contraction of `(F_7^-)^*` is isomorphic to `M(K_4)` or + `\mathcal{W}^3`:: sage: import random sage: e = random.choice(list(M.groundset())) @@ -585,8 +584,8 @@ def AG32(): sage: M.equals(M.dual()) True - Every single-element deletion is isomorphic to `F_7^*` and every - single-element contraction is isomorphic to `F_7`:: + Every single-element deletion is isomorphic to `F_7^*` and every single-element + contraction is isomorphic to `F_7`:: sage: F7 = matroids.catalog.Fano() sage: F7D = matroids.catalog.FanoDual() @@ -640,8 +639,8 @@ def AG32prime(): sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) True - Every single-element deletion is isomorphic to `F_7^*` or `(F_7^-)^*` and - every single-element contraction is isomorphic to `F_7` or `F_7^-`:: + Every single-element deletion is isomorphic to `F_7^*` or `(F_7^-)^*` and every + single-element contraction is isomorphic to `F_7` or `F_7^-`:: sage: F7 = matroids.catalog.Fano() sage: F7D = matroids.catalog.FanoDual() @@ -740,8 +739,7 @@ def F8(): {'a', 'e', 'f', 'h'}, {'b', 'c', 'd', 'g'}, {'b', 'c', 'e', 'f'}, {'c', 'd', 'e', 'h'}, {'c', 'f', 'g', 'h'}, {'d', 'e', 'f', 'g'}}, 4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}} - sage: D = get_nonisomorphic_matroids([M.contract(i) - ....: for i in M.groundset()]) + sage: D = get_nonisomorphic_matroids([M.contract(i) for i in M.groundset()]) sage: len(D) 3 sage: [N.is_isomorphic(matroids.catalog.Fano()) for N in D] @@ -891,8 +889,7 @@ def S8(): sage: M.is_graphic() False sage: D = get_nonisomorphic_matroids( - ....: list(matroids.catalog.Fano().linear_coextensions( - ....: cosimple=True))) + ....: list(matroids.catalog.Fano().linear_coextensions(cosimple=True))) sage: len(D) 2 sage: [N.is_isomorphic(M) for N in D] @@ -1055,8 +1052,7 @@ def P8(): P8: Ternary matroid of rank 4 on 8 elements, type 2+ sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) True - sage: Matroid(matrix=random_matrix(GF(4, 'a'), ncols=5, - ....: nrows=5)).has_minor(M) + sage: Matroid(matrix=random_matrix(GF(4, 'a'), ncols=5, nrows=5)).has_minor(M) False sage: M.bicycle_dimension() 2 @@ -1099,8 +1095,7 @@ def P8pp(): 4: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}} sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) True - sage: len(get_nonisomorphic_matroids([M.contract(i) - ....: for i in M.groundset()])) + sage: len(get_nonisomorphic_matroids([M.contract(i) for i in M.groundset()])) 1 sage: M.is_valid() # long time True @@ -1181,8 +1176,7 @@ def K33dual(): sage: M = matroids.catalog.K33dual(); M M*(K3, 3): Regular matroid of rank 4 on 9 elements with 81 bases - sage: any(N.is_3connected() - ....: for N in M.linear_extensions(simple=True)) + sage: any(N.is_3connected() for N in M.linear_extensions(simple=True)) False sage: M.is_valid() True @@ -1308,7 +1302,7 @@ def R9(): EXAMPLES:: sage: M = matroids.catalog.R9(); M - R9: Matroid of rank 3 on 9 elements with 69 bases + R9: Matroid of rank 3 on 9 elements with 15 nonspanning circuits sage: M.is_valid() True sage: len(M.nonspanning_circuits()) @@ -1471,7 +1465,7 @@ def R10(): R10: Regular matroid of rank 5 on 10 elements with 162 bases sage: cct = [] sage: for i in M.circuits(): - ....: cct.append(len(i)) + ....: cct.append(len(i)) sage: Set(cct) {4, 6} sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) @@ -1527,7 +1521,8 @@ def NonDesargues(groundset=None): EXAMPLES:: - sage: M = matroids.catalog.NonDesargues() + sage: M = matroids.catalog.NonDesargues(); M + NonDesargues: Matroid of rank 3 on 10 elements with 9 nonspanning circuits sage: M.is_valid() True sage: M.automorphism_group().is_transitive() @@ -1728,9 +1723,7 @@ def Wheel(r, field=None, ring=None): will be a regular matroid. - ``field`` -- any field; same as ``ring``, but only fields are allowed - OUTPUT: - - the rank-`r` wheel matroid, represented as a regular matroid + OUTPUT: the rank-`r` wheel matroid, represented as a regular matroid EXAMPLES:: @@ -1802,9 +1795,7 @@ def Whirl(r): - ``r`` -- a positive integer; the rank of the desired matroid. - OUTPUT: - - the rank-`r` whirl matroid, represented as a ternary matroid + OUTPUT: the rank-`r` whirl matroid, represented as a ternary matroid EXAMPLES:: @@ -1813,8 +1804,8 @@ def Whirl(r): sage: M.is_valid() True sage: M.tutte_polynomial() - x^5 + y^5 + 5*x^4 + 5*x^3*y + 5*x^2*y^2 + 5*x*y^3 + 5*y^4 + 10*x^3 + - 15*x^2*y + 15*x*y^2 + 10*y^3 + 10*x^2 + 15*x*y + 10*y^2 + 5*x + 5*y + x^5 + y^5 + 5*x^4 + 5*x^3*y + 5*x^2*y^2 + 5*x*y^3 + 5*y^4 + 10*x^3 + 15*x^2*y + + 15*x*y^2 + 10*y^3 + 10*x^2 + 15*x*y + 10*y^2 + 5*x + 5*y sage: M.is_isomorphic(matroids.Wheel(5)) False sage: M = matroids.Whirl(3) @@ -1876,9 +1867,7 @@ def Uniform(r, n): - ``n`` -- a nonnegative integer; the number of elements of the uniform matroid - OUTPUT: - - the uniform matroid `U_{r,n}` + OUTPUT: the uniform matroid `U_{r,n}` EXAMPLES:: @@ -1930,9 +1919,7 @@ def PG(n, q, x=None): non-prime field, used for non-prime fields. If not supplied, ``'x'`` is used. - OUTPUT: - - a linear matroid whose elements are the points of `PG(n, q)` + OUTPUT: a linear matroid whose elements are the points of `PG(n, q)` EXAMPLES:: @@ -1942,8 +1929,8 @@ def PG(n, q, x=None): sage: matroids.PG(5, 4, 'z').size() == (4^6 - 1) / (4 - 1) True sage: M = matroids.PG(4, 7); M - PG(4, 7): Linear matroid of rank 5 on 2801 elements represented over - the Finite Field of size 7 + PG(4, 7): Linear matroid of rank 5 on 2801 elements represented over the Finite Field + of size 7 REFERENCES: @@ -1977,17 +1964,14 @@ def AG(n, q, x=None): non-prime field, used for non-prime fields. If not supplied, ``'x'`` is used. - OUTPUT: - - a linear matroid whose elements are the points of `AG(n, q)` + OUTPUT: a linear matroid whose elements are the points of `AG(n, q)` EXAMPLES:: sage: M = matroids.AG(2, 3).delete(8) sage: M.is_isomorphic(matroids.catalog.AG23minus()) True - sage: matroids.AG(5, 4, 'z').size() == ((4 ^ 6 - 1) / (4 - 1) - - ....: (4 ^ 5 - 1)/(4 - 1)) + sage: matroids.AG(5, 4, 'z').size() == ((4 ^ 6 - 1) / (4 - 1) - (4 ^ 5 - 1)/(4 - 1)) True sage: M = matroids.AG(4, 2); M AG(4, 2): Binary matroid of rank 5 on 16 elements, type (5, 0) @@ -2019,9 +2003,7 @@ def Z(r, t=True): - ``r`` -- an integer (`r \ge 3`); the rank of the spike - ``t`` -- a Boolean (default: ``True``); whether the spike is tipped - OUTPUT: - - a matroid; the unique rank-`r` binary spike (tipped or tipless) + OUTPUT: a matroid; the unique rank-`r` binary spike (tipped or tipless) EXAMPLES:: @@ -2096,7 +2078,7 @@ def Spike(r, t=True, C3=[]): The groundset is `E = \{t, x_1, x_2, \ldots, x_r, y_1, y_2, \ldots, y_r\}` with `r(E) = r`. - The non-spanning circuits are `\{L_1, L_2, \ldots, L_r\}`, all sets of the + The nonspanning circuits are `\{L_1, L_2, \ldots, L_r\}`, all sets of the form `(L_i \cup L_j) \setminus t` for `1 \le i < j \le r`, and some (possibly empty) collection `C_3` of sets of the form `\{z_1, z_2, \ldots, z_r\}` where `z_i \in \{x_i, y_i\}` for all `i`, and no two members of @@ -2105,18 +2087,17 @@ def Spike(r, t=True, C3=[]): INPUT: - ``r`` -- an integer (`r \ge 3`); the rank of the spike - - ``t`` -- a boolean (default: ``True``); whether the spike is tipped + - ``t`` -- boolean (default: ``True``); whether the spike is tipped - ``C3`` -- a list (default: ``[]``); a list of extra nonspanning circuits. The default (i.e. the empty list) results in a free `r`-spike - OUTPUT: - - a matroid; a rank-`r` spike (tipped or tipless) + OUTPUT: a matroid; a rank-`r` spike (tipped or tipless) EXAMPLES:: sage: M = matroids.Spike(3, False); M - Free 3-spike\t: Matroid of rank 3 on 6 elements with 20 bases + Free 3-spike\t: M \ {'t'}, where M is Matroid of rank 3 on 7 elements with 3 + nonspanning circuits sage: M.is_isomorphic(matroids.Uniform(3, 6)) True sage: len(matroids.Spike(8).bases()) @@ -2227,9 +2208,7 @@ def Theta(n): - ``n`` -- an integer (`n \ge 2`); the rank of the matroid - OUTPUT: - - a matroid (`\Theta_n`) + OUTPUT: a matroid (`\Theta_n`) EXAMPLES:: @@ -2253,8 +2232,7 @@ def Theta(n): sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) True - For `n \le 3`, its automorphism group is transitive, while for `n \ge 4` - it is not:: + For `n \le 3`, its automorphism group is transitive, while for `n \ge 4` it is not:: sage: n = random.choice(range(4, 8)) sage: M = matroids.Theta(2 + n % 2) @@ -2301,17 +2279,14 @@ def Psi(r): - ``r`` -- an integer (`r \ge 3`); the rank of the matroid - OUTPUT: - - a matroid (`\Psi_r`) + OUTPUT: a matroid (`\Psi_r`) EXAMPLES:: sage: matroids.Psi(7) Psi_7: Matroid of rank 7 on 14 elements with 2060 bases - The matroid `\Psi_r` is `3`-connected but, for all `r \ge 4`, not - `4`-connected:: + The matroid `\Psi_r` is `3`-connected but, for all `r \ge 4`, not `4`-connected:: sage: M = matroids.Psi(3) sage: M.is_4connected() @@ -2847,8 +2822,7 @@ def BB9gDY(): EXAMPLES:: sage: M = matroids.catalog.BB9gDY(); M - Segment cosegment exchange on BB9: Quaternary matroid of rank 5 on 9 - elements + Segment cosegment exchange on BB9: Quaternary matroid of rank 5 on 9 elements sage: M.is_valid() True """ @@ -4812,7 +4786,7 @@ def R9B(): def Block_9_4(): """ - Return the paving matroid whose non-spanning circuits form the blocks of a + Return the paving matroid whose nonspanning circuits form the blocks of a `2-(9, 4, 3)` design. EXAMPLES:: @@ -4897,7 +4871,7 @@ def N1(): def Block_10_5(): """ - Return the paving matroid whose non-spanning circuits form the blocks of a + Return the paving matroid whose nonspanning circuits form the blocks of a `3-(10, 5, 3)` design. EXAMPLES:: @@ -5099,8 +5073,7 @@ def ExtendedBinaryGolayCode(): EXAMPLES:: sage: M = matroids.catalog.ExtendedBinaryGolayCode(); M - Extended Binary Golay Code: Binary matroid of rank 12 on 24 elements, - type (12, 0) + Extended Binary Golay Code: Binary matroid of rank 12 on 24 elements, type (12, 0) sage: C = LinearCode(M.representation()) sage: C.is_permutation_equivalent(codes.GolayCode(GF(2))) True @@ -5151,9 +5124,7 @@ def CompleteGraphic(n): - ``n`` -- an integer, the number of vertices of the underlying complete graph. - OUTPUT: - - The graphic matroid associated with the `n`-vertex complete graph. + OUTPUT: The graphic matroid associated with the `n`-vertex complete graph. This matroid has rank `n - 1`. EXAMPLES:: diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index b2d86f1e64a..2338c144190 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -2956,9 +2956,12 @@ cdef class Matroid(SageObject): - ``ordering`` -- a total ordering of the groundset given as a list + OUTPUT: a list of frozensets + EXAMPLES:: - sage: M = Matroid(circuits=[[1,2,3], [3,4,5], [1,2,4,5]]) + sage: from sage.matroids.basis_matroid import BasisMatroid + sage: M = BasisMatroid(Matroid(circuits=[[1,2,3], [3,4,5], [1,2,4,5]])) sage: SimplicialComplex(M.no_broken_circuits_sets()) # needs sage.graphs Simplicial complex with vertex set (1, 2, 3, 4, 5) and facets {(1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5)} @@ -3121,9 +3124,9 @@ cdef class Matroid(SageObject): A 6-dimensional polyhedron in ZZ^7 defined as the convex hull of 29 vertices - REFERENCE: + REFERENCES: - - [DLHK2007]_ + [DLHK2007]_ """ from sage.geometry.polyhedron.constructor import Polyhedron from sage.modules.free_module import FreeModule @@ -3164,7 +3167,7 @@ cdef class Matroid(SageObject): A 7-dimensional polyhedron in ZZ^7 defined as the convex hull of 58 vertices - REFERENCE: + REFERENCES: [DLHK2007]_ """ diff --git a/src/sage/matroids/unpickling.pyx b/src/sage/matroids/unpickling.pyx index d49cf378a63..2cd946d7503 100644 --- a/src/sage/matroids/unpickling.pyx +++ b/src/sage/matroids/unpickling.pyx @@ -26,18 +26,17 @@ AUTHORS: # **************************************************************************** from sage.data_structures.bitset_base cimport * -import sage.matroids.matroid -import sage.matroids.basis_exchange_matroid -from sage.matroids.minor_matroid import MinorMatroid -from sage.matroids.dual_matroid import DualMatroid -from sage.matroids.circuit_closures_matroid cimport CircuitClosuresMatroid +from sage.libs.gmp.mpq cimport mpq_set +from sage.rings.rational cimport Rational + from sage.matroids.basis_matroid cimport BasisMatroid -from sage.matroids.linear_matroid cimport LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid -from sage.matroids.lean_matrix cimport GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, PlusMinusOneMatrix, RationalMatrix +from sage.matroids.circuits_matroid cimport CircuitsMatroid +from sage.matroids.circuit_closures_matroid cimport CircuitClosuresMatroid +from sage.matroids.dual_matroid import DualMatroid from sage.matroids.graphic_matroid import GraphicMatroid - -from sage.rings.rational cimport Rational -from sage.libs.gmp.mpq cimport mpq_set +from sage.matroids.lean_matrix cimport GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, PlusMinusOneMatrix, RationalMatrix +from sage.matroids.linear_matroid cimport LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid +from sage.matroids.minor_matroid import MinorMatroid ############################################################################# @@ -88,6 +87,49 @@ def unpickle_basis_matroid(version, data): return M +############################################################################# +# CircuitsMatroid +############################################################################# + +def unpickle_circuits_matroid(version, data): + """ + Unpickle a CircuitsMatroid. + + *Pickling* is Python's term for the loading and saving of objects. + Functions like these serve to reconstruct a saved object. This all happens + transparently through the ``load`` and ``save`` commands, and you should + never have to call this function directly. + + INPUT: + + - ``version`` -- an integer, expected to be 0 + - ``data`` -- a tuple ``(E, C, name)`` in which ``E`` is the groundset + of the matroid, ``C`` is the list of circuits , and ``name`` is a custom + name. + + OUTPUT: + + A matroid. + + .. WARNING:: + + Users should never call this function directly. + + EXAMPLES:: + + sage: M = matroids.Theta(5) + sage: M == loads(dumps(M)) # indirect doctest + True + """ + cdef CircuitsMatroid M + if version != 0: + raise TypeError("object was created with newer version of Sage. Please upgrade.") + M = CircuitsMatroid(groundset=data[0], circuits=data[1]) + if data[2] is not None: + M.rename(data[2]) + return M + + ############################################################################# # CircuitClosuresMatroid ############################################################################# @@ -358,11 +400,11 @@ def unpickle_rational_matrix(version, data): mpq_set(A._entries[i], ( data[2][i]).value) return A + ############################################################################# # LinearMatroid and subclasses ############################################################################# - def unpickle_linear_matroid(version, data): """ Unpickle a LinearMatroid. @@ -643,11 +685,11 @@ def unpickle_minor_matroid(version, data): M.rename(data[3]) return M + ############################################################################# # Graphic Matroids ############################################################################# - def unpickle_graphic_matroid(version, data): """ Unpickle a GraphicMatroid.