Path: blob/master/src/sage/matroids/circuit_closures_matroid.pyx
8817 views
r"""1Circuit closures matroids23Matroids are characterized by a list of all tuples `(C, k)`, where `C` is the4closure of a circuit, and `k` the rank of `C`. The CircuitClosuresMatroid5class implements matroids using this information as data.67Construction8============910A ``CircuitClosuresMatroid`` can be created from another matroid or from a11list of circuit-closures. For a full description of allowed inputs, see12:class:`below <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`.13It is recommended to use the14:func:`Matroid() <sage.matroids.constructor.Matroid>` function for a more15flexible construction of a ``CircuitClosuresMatroid``. For direct access to16the ``CircuitClosuresMatroid`` constructor, run::1718sage: from sage.matroids.advanced import *1920See also :mod:`sage.matroids.advanced`.2122EXAMPLES::2324sage: from sage.matroids.advanced import *25sage: M1 = CircuitClosuresMatroid(groundset='abcdef',26....: circuit_closures={2: ['abc', 'ade'], 3: ['abcdef']})27sage: M2 = Matroid(circuit_closures={2: ['abc', 'ade'], 3: ['abcdef']})28sage: M3 = Matroid(circuit_closures=[(2, 'abc'),29....: (3, 'abcdef'), (2, 'ade')])30sage: M1 == M231True32sage: M1 == M333True3435Note that the class does not implement custom minor and dual operations::3637sage: from sage.matroids.advanced import *38sage: M = CircuitClosuresMatroid(groundset='abcdef',39....: circuit_closures={2: ['abc', 'ade'], 3: ['abcdef']})40sage: isinstance(M.contract('a'), MinorMatroid)41True42sage: isinstance(M.dual(), DualMatroid)43True4445AUTHORS:4647- Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version4849TESTS::5051sage: from sage.matroids.advanced import *52sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())53sage: TestSuite(M).run()5455Methods56=======57"""58#*****************************************************************************59# Copyright (C) 2013 Rudi Pendavingh <[email protected]>60# Copyright (C) 2013 Stefan van Zwam <[email protected]>61#62# Distributed under the terms of the GNU General Public License (GPL)63# as published by the Free Software Foundation; either version 2 of64# the License, or (at your option) any later version.65# http://www.gnu.org/licenses/66#*****************************************************************************67from matroid cimport Matroid68from set_system cimport SetSystem69from utilities import setprint_s7071cdef class CircuitClosuresMatroid(Matroid):72"""73A general matroid `M` is characterized by its rank `r(M)` and the set of74pairs7576`(k, \{` closure `(C) : C ` circuit of ` M, r(C)=k\})` for `k=0, .., r(M)-1`7778As each independent set of size `k` is in at most one closure(`C`) of rank79`k`, and each closure(`C`) of rank `k` contains at least `k + 1`80independent sets of size `k`, there are at most `{n \choose k}/(k + 1)`81such closures-of-circuits of rank `k`. Each closure(`C`) takes `O(n)` bits82to store, giving an upper bound of `O(2^n)` on the space complexity of the83entire matroid.8485A subset `X` of the ground set is independent if and only if8687`| X \cap ` closure `(C) | \leq k` for all circuits `C` of `M` with88`r(C)=k`.8990So determining whether a set is independent takes time proportional to the91space complexity of the matroid.9293INPUT:9495- ``M`` -- (default: ``None``) an arbitrary matroid.96- ``groundset`` -- (default: ``None``) the groundset of a matroid.97- ``circuit_closures`` -- (default: ``None``) the collection of circuit98closures of a matroid, presented as a dictionary whose keys are ranks,99and whose values are sets of circuit closures of the specified rank.100101OUTPUT:102103- If the input is a matroid ``M``, return a ``CircuitClosuresMatroid``104instance representing ``M``.105- Otherwise, return a ``CircuitClosuresMatroid`` instance based on106``groundset`` and ``circuit_closures``.107108.. NOTE::109110For a more flexible means of input, use the ``Matroid()`` function.111112EXAMPLES::113114sage: from sage.matroids.advanced import *115sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())116sage: M117Matroid of rank 3 on 7 elements with circuit-closures118{2: {{'b', 'e', 'g'}, {'b', 'c', 'd'}, {'a', 'c', 'e'},119{'c', 'f', 'g'}, {'d', 'e', 'f'}, {'a', 'd', 'g'},120{'a', 'b', 'f'}}, 3: {{'a', 'b', 'c', 'd', 'e', 'f', 'g'}}}121sage: M = CircuitClosuresMatroid(groundset='abcdefgh',122....: circuit_closures={3: ['edfg', 'acdg', 'bcfg', 'cefh',123....: 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'],124....: 4: ['abcdefgh']})125sage: M.equals(matroids.named_matroids.P8())126True127"""128129# NECESSARY130def __init__(self, M=None, groundset=None, circuit_closures=None):131"""132Initialization of the matroid. See class docstring for full133documentation.134135EXAMPLES::136137sage: from sage.matroids.advanced import *138sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())139sage: M140Matroid of rank 3 on 7 elements with circuit-closures141{2: {{'b', 'e', 'g'}, {'b', 'c', 'd'}, {'a', 'c', 'e'},142{'c', 'f', 'g'}, {'d', 'e', 'f'}, {'a', 'd', 'g'},143{'a', 'b', 'f'}}, 3: {{'a', 'b', 'c', 'd', 'e', 'f', 'g'}}}144145sage: M = CircuitClosuresMatroid(groundset='abcdefgh',146....: circuit_closures={3: ['edfg', 'acdg', 'bcfg', 'cefh',147....: 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'],148....: 4: ['abcdefgh']})149sage: M.equals(matroids.named_matroids.P8())150True151"""152if M is not None:153self._groundset = M.groundset()154self._circuit_closures = M.circuit_closures()155else:156self._groundset = frozenset(groundset)157self._circuit_closures = {}158for k in circuit_closures.iterkeys():159self._circuit_closures[k] = frozenset([frozenset(X) for X in circuit_closures[k]])160self._matroid_rank = self.rank(self._groundset)161162cpdef groundset(self):163"""164Return the groundset of the matroid.165166The groundset is the set of elements that comprise the matroid.167168OUTPUT:169170A set.171172EXAMPLES::173174sage: M = matroids.named_matroids.Pappus()175sage: sorted(M.groundset())176['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']177"""178return frozenset(self._groundset)179180cpdef _rank(self, X):181"""182Return the rank of a set ``X``.183184This method does no checking on ``X``, and185``X`` may be assumed to have the same interface as ``frozenset``.186187INPUT:188189- ``X`` -- an object with Python's ``frozenset`` interface.190191OUTPUT:192193The rank of ``X`` in the matroid.194195EXAMPLES::196197sage: M = matroids.named_matroids.NonPappus()198sage: M._rank('abc')1992200"""201return len(self._max_independent(X))202203# OPTIONAL, OPTIMIZED FOR THIS CLASS204cpdef full_rank(self):205r"""206Return the rank of the matroid.207208The *rank* of the matroid is the size of the largest independent209subset of the groundset.210211OUTPUT:212213Integer.214215EXAMPLES::216217sage: M = matroids.named_matroids.Vamos()218sage: M.full_rank()2194220sage: M.dual().full_rank()2214222"""223return self._matroid_rank224225cpdef _is_independent(self, F):226"""227Test if input is independent.228229INPUT:230231- ``X`` -- An object with Python's ``frozenset`` interface containing232a subset of ``self.groundset()``.233234OUTPUT:235236Boolean.237238EXAMPLES::239240sage: M = matroids.named_matroids.Vamos()241sage: M._is_independent(set(['a', 'b', 'c']))242True243sage: M._is_independent(set(['a', 'b', 'c', 'd']))244False245246"""247for r in sorted(self._circuit_closures.keys()):248if len(F) <= r:249break250for C in self._circuit_closures[r]:251S = F & C252if(len(S) > r):253return False254return True255256cpdef _max_independent(self, F):257"""258Compute a maximal independent subset.259260INPUT:261262- ``X`` -- An object with Python's ``frozenset`` interface containing263a subset of ``self.groundset()``.264265OUTPUT:266267A maximal independent subset of ``X``.268269EXAMPLES::270271sage: M = matroids.named_matroids.Vamos()272sage: sorted(M._max_independent(set(['a', 'c', 'd', 'e', 'f'])))273['a', 'd', 'e', 'f']274275"""276I = set(F)277for r in sorted(self._circuit_closures.keys()):278if len(I) == 0:279break280for C in self._circuit_closures[r]:281if len(I) == 0:282break283S = I & C284while(len(S) > r):285I.discard(S.pop())286287return frozenset(I)288289cpdef _circuit(self, F):290"""291Return a minimal dependent subset.292293INPUT:294295- ``X`` -- An object with Python's ``frozenset`` interface containing296a subset of ``self.groundset()``.297298OUTPUT:299300A circuit contained in ``X``, if it exists. Otherwise an error is301raised.302303EXAMPLES::304305sage: M = matroids.named_matroids.Vamos()306sage: sorted(M._circuit(set(['a', 'c', 'd', 'e', 'f'])))307['c', 'd', 'e', 'f']308sage: sorted(M._circuit(set(['a', 'c', 'd'])))309Traceback (most recent call last):310...311ValueError: no circuit in independent set.312313"""314for r in sorted(self._circuit_closures.keys()):315for C in self._circuit_closures[r]:316S = set(F & C)317if(len(S) > r):318while len(S) > r + 1:319S.pop()320return frozenset(S)321raise ValueError("no circuit in independent set.")322323cpdef circuit_closures(self):324"""325Return the list of closures of circuits of the matroid.326327A *circuit closure* is a closed set containing a circuit.328329OUTPUT:330331A dictionary containing the circuit closures of the matroid, indexed332by their ranks.333334.. SEEALSO::335336:meth:`Matroid.circuit() <sage.matroids.matroid.Matroid.circuit>`,337:meth:`Matroid.closure() <sage.matroids.matroid.Matroid.closure>`338339EXAMPLES::340341sage: from sage.matroids.advanced import *342sage: M = CircuitClosuresMatroid(matroids.named_matroids.Fano())343sage: CC = M.circuit_closures()344sage: len(CC[2])3457346sage: len(CC[3])3471348sage: len(CC[1])349Traceback (most recent call last):350...351KeyError: 1352sage: [sorted(X) for X in CC[3]]353[['a', 'b', 'c', 'd', 'e', 'f', 'g']]354"""355return self._circuit_closures356357cpdef _is_isomorphic(self, other):358"""359Test if ``self`` is isomorphic to ``other``.360361Internal version that performs no checks on input.362363INPUT:364365- ``other`` -- A matroid.366367OUTPUT:368369Boolean.370371EXAMPLES::372373sage: from sage.matroids.advanced import *374sage: M1 = CircuitClosuresMatroid(matroids.Wheel(3))375sage: M2 = matroids.CompleteGraphic(4)376sage: M1._is_isomorphic(M2)377True378sage: M1 = CircuitClosuresMatroid(matroids.named_matroids.Fano())379sage: M2 = matroids.named_matroids.NonFano()380sage: M1._is_isomorphic(M2)381False382383"""384N = CircuitClosuresMatroid(other)385if sorted(self._circuit_closures.keys()) != sorted(N._circuit_closures.keys()):386return False387SM = SetSystem(list(self.groundset()))388for r in self._circuit_closures:389for C in self._circuit_closures[r]:390SM.append(C)391SN = SetSystem(list(N.groundset()))392for r in N._circuit_closures:393for C in N._circuit_closures[r]:394SN.append(C)395return SM._isomorphism(SN) is not None396397# REPRESENTATION398def _repr_(self):399"""400Return a string representation of the matroid.401402EXAMPLES::403404sage: M = matroids.named_matroids.Vamos()405sage: print(M._repr_())406Matroid of rank 4 on 8 elements with circuit-closures407{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},408{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'},409{'c', 'd', 'e', 'f'}},4104: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}411"""412return Matroid._repr_(self) + " with circuit-closures\n" + setprint_s(self._circuit_closures)413414# COMPARISON415416def __hash__(self):417r"""418Return an invariant of the matroid.419420This function is called when matroids are added to a set. It is very421desirable to override it so it can distinguish matroids on the same422groundset, which is a very typical use case!423424.. WARNING::425426This method is linked to __richcmp__ (in Cython) and __cmp__ or427__eq__/__ne__ (in Python). If you override one, you should428(and in Cython: MUST) override the other!429430EXAMPLES::431432sage: M = matroids.named_matroids.Vamos()433sage: N = matroids.named_matroids.Vamos()434sage: hash(M) == hash(N)435True436sage: O = matroids.named_matroids.NonVamos()437sage: hash(M) == hash(O)438False439"""440return hash(tuple([self.groundset(), tuple([(r, len(self._circuit_closures[r])) for r in sorted(self._circuit_closures.keys())])]))441442def __richcmp__(left, right, int op):443r"""444Compare two matroids.445446We take a very restricted view on equality: the objects need to be of447the exact same type (so no subclassing) and the internal data need to448be the same. For BasisMatroids, this means that the groundsets and the449sets of bases of the two matroids are equal.450451EXAMPLES::452453sage: M = matroids.named_matroids.Pappus()454sage: N = matroids.named_matroids.NonPappus()455sage: M == N456False457sage: N = Matroid(M.bases())458sage: M == N459False460"""461cdef CircuitClosuresMatroid lt, rt462if op in [0, 1, 4, 5]: # <, <=, >, >=463return NotImplemented464if not isinstance(left, CircuitClosuresMatroid) or not isinstance(right, CircuitClosuresMatroid):465return NotImplemented466lt = <CircuitClosuresMatroid> left467rt = <CircuitClosuresMatroid> right468if op == 2: # ==469res = True470if op == 3: # !=471res = False472# res gets inverted if matroids are deemed different.473if lt.groundset() != rt.groundset():474return not res475if lt.full_rank() != rt.full_rank():476return not res477if lt._circuit_closures == rt._circuit_closures:478return res479return not res480481# COPYING, LOADING, SAVING482483def __copy__(self):484"""485Create a shallow copy.486487EXAMPLES::488489sage: M = matroids.named_matroids.Vamos()490sage: N = copy(M) # indirect doctest491sage: M == N492True493sage: M.groundset() is N.groundset()494True495496"""497N = CircuitClosuresMatroid(groundset=[], circuit_closures={})498N._groundset = self._groundset499N._circuit_closures = self._circuit_closures500N._matroid_rank = self._matroid_rank501if getattr(self, '__custom_name') is not None: # because of name wrangling, this is not caught by the default copy502N.rename(getattr(self, '__custom_name'))503return N504505def __deepcopy__(self, memo={}):506"""507Create a deep copy.508509.. NOTE::510511Since matroids are immutable, a shallow copy normally suffices.512513EXAMPLES::514515sage: M = matroids.named_matroids.Vamos()516sage: N = deepcopy(M) # indirect doctest517sage: M == N518True519sage: M.groundset() is N.groundset()520False521"""522from copy import deepcopy523# Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion.524N = CircuitClosuresMatroid(groundset=deepcopy(self._groundset, memo), circuit_closures=deepcopy(self._circuit_closures, memo))525if getattr(self, '__custom_name') is not None: # because of name wrangling, this is not caught by the default deepcopy526N.rename(deepcopy(getattr(self, '__custom_name'), memo))527return N528529def __reduce__(self):530"""531Save the matroid for later reloading.532533OUTPUT:534535A tuple ``(unpickle, (version, data))``, where ``unpickle`` is the536name of a function that, when called with ``(version, data)``,537produces a matroid isomorphic to ``self``. ``version`` is an integer538(currently 0) and ``data`` is a tuple ``(E, CC, name)`` where ``E`` is539the groundset, ``CC`` is the dictionary of circuit closures, and540``name`` is a custom name.541542EXAMPLES::543544sage: M = matroids.named_matroids.Vamos()545sage: M == loads(dumps(M)) # indirect doctest546True547sage: M.reset_name()548sage: loads(dumps(M))549Matroid of rank 4 on 8 elements with circuit-closures550{3: {{'a', 'b', 'c', 'd'}, {'a', 'b', 'e', 'f'},551{'e', 'f', 'g', 'h'}, {'a', 'b', 'g', 'h'},552{'c', 'd', 'e', 'f'}},5534: {{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}}554555"""556import sage.matroids.unpickling557data = (self._groundset, self._circuit_closures, getattr(self, '__custom_name'))558version = 0559return sage.matroids.unpickling.unpickle_circuit_closures_matroid, (version, data)560561# todo: customized minor, extend methods.562563564