From 2e4020a3c691d39b117816b5be2c7905bcd858a0 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Wed, 14 Feb 2024 13:46:24 +0200 Subject: [PATCH 1/8] Add class FlatsMatroid --- src/sage/matroids/constructor.py | 17 +- src/sage/matroids/flats_matroid.pxd | 20 ++ src/sage/matroids/flats_matroid.pyx | 524 ++++++++++++++++++++++++++++ src/sage/matroids/unpickling.pyx | 46 +++ 4 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 src/sage/matroids/flats_matroid.pxd create mode 100644 src/sage/matroids/flats_matroid.pyx diff --git a/src/sage/matroids/constructor.py b/src/sage/matroids/constructor.py index 137f4c28230..ef14148947b 100644 --- a/src/sage/matroids/constructor.py +++ b/src/sage/matroids/constructor.py @@ -113,6 +113,7 @@ import sage.matroids.basis_exchange_matroid from .rank_matroid import RankMatroid from .circuits_matroid import CircuitsMatroid +from .flats_matroid import FlatsMatroid from .circuit_closures_matroid import CircuitClosuresMatroid from .basis_matroid import BasisMatroid from .linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid @@ -180,6 +181,7 @@ def Matroid(groundset=None, data=None, **kwds): - ``circuits`` -- The list of circuits of the matroid. - ``nonspanning_circuits`` -- The list of nonspanning_circuits of the matroid. + - ``flats`` -- The dictionary of flats indexed by their rank. - ``graph`` -- A graph, whose edges form the elements of the matroid. - ``matrix`` -- A matrix representation of the matroid. - ``reduced_matrix`` -- A reduced representation of the matroid: if @@ -698,8 +700,9 @@ def Matroid(groundset=None, data=None, **kwds): key = None if data is None: for k in ['bases', 'independent_sets', 'circuits', - 'nonspanning_circuits', 'graph', 'matrix', 'reduced_matrix', - 'rank_function', 'revlex', 'circuit_closures', 'matroid']: + 'nonspanning_circuits', 'flats', 'graph', 'matrix', + 'reduced_matrix', 'rank_function', 'revlex', + 'circuit_closures', 'matroid']: if k in kwds: data = kwds.pop(k) key = k @@ -791,6 +794,16 @@ def Matroid(groundset=None, data=None, **kwds): nsc_defined=True ) + # Flats + elif key == 'flats': + # Determine groundset + if groundset is None: + groundset = set() + for i in data: + for F in data[i]: + groundset.update(F) + M = FlatsMatroid(groundset=groundset, flats=data) + # Graphs: elif key == 'graph': from sage.graphs.graph import Graph diff --git a/src/sage/matroids/flats_matroid.pxd b/src/sage/matroids/flats_matroid.pxd new file mode 100644 index 00000000000..f3ceb787f30 --- /dev/null +++ b/src/sage/matroids/flats_matroid.pxd @@ -0,0 +1,20 @@ +from sage.matroids.matroid cimport Matroid + +cdef class FlatsMatroid(Matroid): + cdef frozenset _groundset # _E + cdef int _matroid_rank # _R + cdef dict _F # flats + cpdef groundset(self) noexcept + cpdef _rank(self, X) noexcept + cpdef full_rank(self) noexcept + cpdef _is_independent(self, F) noexcept + + # enumeration + cpdef flats(self, k) noexcept + cpdef whitney_numbers2(self) noexcept + + # isomorphism + cpdef _is_isomorphic(self, other, certificate=*) noexcept + + # verification + cpdef is_valid(self) noexcept diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx new file mode 100644 index 00000000000..546526ee300 --- /dev/null +++ b/src/sage/matroids/flats_matroid.pyx @@ -0,0 +1,524 @@ +r""" +Flats matroids + +Matroids are characterized by a set of flats, which are sets invariant under +closure. The ``FlatsMatroid`` class implements matroids using this information +as data. + +A ``FlatsMatroid`` can be created from another matroid or from a dictionary of +flats. 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 ``FlatsMatroid`` and other +classes of matroids. For direct access to the ``FlatsMatroid`` constructor, +run:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + +AUTHORS: + +- Giorgos Mousa (2024-01-01): initial version +""" + +# **************************************************************************** +# Copyright (C) 2024 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 sage.matroids.utilities import setprint_s +from cpython.object cimport Py_EQ, Py_NE + + +cdef class FlatsMatroid(Matroid): + r""" + INPUT: + + - ``M`` -- a matroid (default: ``None``) + - ``groundset`` -- a list (default: ``None``); the groundset of the matroid + - ``flats`` -- a dictionary (default: ``None``); the lists of `k`-flats of + the matroid, indexed by their rank `k` + + .. NOTE:: + + For a more flexible means of input, use the ``Matroid()`` function. + """ + + # necessary (__init__, groundset, _rank) + + def __init__(self, M=None, groundset=None, flats=None): + """ + Initialization of the matroid. See class docstring for full + documentation. + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Fano()) + sage: TestSuite(M).run() + """ + self._F = {} + if M is not None: + self._groundset = frozenset(M.groundset()) + for i in range(len(M.groundset()) + 1): + for F in M.flats(i): + try: + self._F[i].add(frozenset(F)) + except KeyError: + self._F[i] = set() + self._F[i].add(frozenset(F)) + else: + self._groundset = frozenset(groundset) + for i in flats: + for F in flats[i]: + try: + self._F[i].add(frozenset(F)) + except KeyError: + self._F[i] = set() + self._F[i].add(frozenset(F)) + self._matroid_rank = self.rank(self._groundset) + + 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: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(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: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Theta(3)) + sage: M._rank(['x1', 'y0', 'y2']) + 2 + """ + cdef frozenset XX = frozenset(X) + cdef int min = len(self._groundset) + for i in self._F: + if i < min: + for f in self._F[i]: + if f >= XX: + min = i + break + return min + + # 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: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Theta(7)) + sage: M.full_rank() + 7 + """ + return self._matroid_rank + + 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: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = matroids.catalog.NonDesargues() + sage: N = FlatsMatroid(M) + sage: N._is_isomorphic(M) + True + sage: N._is_isomorphic(matroids.catalog.R9()) + False + + .. NOTE:: + + Internal version that does no input checking. + """ + if certificate: + return self._is_isomorphic(other), self._isomorphism(other) + N = FlatsMatroid(other) + flats_self = frozenset([F for i in self._F for F in self._F[i]]) + flats_other = frozenset([F for i in N._F for F in N._F[i]]) + SS = SetSystem(list(self._groundset), flats_self) + OS = SetSystem(list(N._groundset), flats_other) + return SS._isomorphism(OS) is not None + + # representation + + def _repr_(self): + """ + Return a string representation of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Uniform(6, 6)); M + Matroid of rank 6 on 6 elements with 64 flats + """ + flats_num = sum(1 for i in self._F for F in self._F[i]) + return Matroid._repr_(self) + " with " + str(flats_num) + " flats" + + # 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.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: N = FlatsMatroid(matroids.catalog.Vamos()) + sage: hash(M) == hash(N) + True + sage: O = FlatsMatroid(matroids.catalog.NonVamos()) + sage: hash(M) == hash(O) + False + """ + flats = frozenset([F for i in self._F for F in self._F[i]]) + return hash(tuple([self._groundset, flats])) + + 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 FlatsMatroids, this means that the groundsets and the + dictionaries of flats of the two matroids are equal. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Pappus()) + sage: N = FlatsMatroid(matroids.catalog.NonPappus()) + sage: M == N + False + sage: N = Matroid(M.bases()) + sage: M == N + False + """ + cdef FlatsMatroid 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(lt._F, rt._F, op) + + # copying, loading, saving + + def __copy__(self): + """ + Create a shallow copy. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: N = copy(M) # indirect doctest + sage: M == N + True + sage: M.groundset() is N.groundset() + True + """ + N = FlatsMatroid(groundset=[], flats={}) + N._groundset = self._groundset + N._F = self._F + 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.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(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 = FlatsMatroid(groundset=deepcopy(self._groundset, memo), flats=deepcopy(self._F, 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, F, name)`` where ``E`` is + the groundset, ``F`` is the dictionary of flats, and ``name`` is a + custom name. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: M == loads(dumps(M)) # indirect doctest + True + sage: M.reset_name() + sage: loads(dumps(M)) + Matroid of rank 4 on 8 elements with 79 flats + """ + import sage.matroids.unpickling + data = (self._groundset, self._F, self.get_custom_name()) + version = 0 + return sage.matroids.unpickling.unpickle_flats_matroid, (version, data) + + # enumeration + + cpdef flats(self, k) noexcept: + r""" + Return the flats of the matroid of specified rank. + + INPUT: + + - ``k`` -- an integer + + OUTPUT: a SetSystem + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Uniform(3, 4)) + sage: sorted(M.flats(2), key=str) + [frozenset({0, 1}), + frozenset({0, 2}), + frozenset({0, 3}), + frozenset({1, 2}), + frozenset({1, 3}), + frozenset({2, 3})] + """ + if k in self._F: + return SetSystem(list(self._groundset), self._F[k]) + return SetSystem(list(self._groundset)) + + def flats_iterator(self, k): + r""" + Return an iterator over the flats of the matroid of specified rank. + + INPUT: + + - ``k`` -- an integer + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Uniform(3, 4)) + sage: sorted([list(F) for F in M.flats_iterator(2)]) + [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] + """ + if k in self._F: + for F in self._F[k]: + yield F + + cpdef whitney_numbers2(self) noexcept: + r""" + Return the Whitney numbers of the second kind of the matroid. + + The Whitney numbers of the second kind are here encoded as a vector + `(W_0, ..., W_r)`, where `W_i` is the number of flats of rank `i`, and + `r` is the rank of the matroid. + + OUTPUT: a list of integers + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.XY13()) + sage: M.whitney_numbers2() + [1, 13, 78, 250, 394, 191, 1] + """ + cdef list W = [] + cdef int i + for i in sorted(self._F): + W.append(len(self._F[i])) + return W + + # verification + + cpdef is_valid(self) noexcept: + r""" + Test if ``self`` obeys the matroid axioms. + + For a matroid defined by its flats, we check the flat axioms. + + OUTPUT: boolean + + EXAMPLES:: + + sage: M = Matroid(flats={0: [[]], 1: [[0], [1]], 2: [[0, 1]]}) + sage: M.is_valid() + True + sage: M = Matroid(flats={0: [''], 1: ['a', 'b'], 2: ['ab']}) + sage: M.is_valid() + True + sage: M = Matroid(flats={0: [[]], 1: [[0], [1]]}) # missing groundset + sage: M.is_valid() + False + sage: M = Matroid(flats={0: [''], + ....: 1: ['0','1','2','3','4','5','6','7','8','9','a','b','c'], + ....: 2: ['45','46','47','4c','56','57','5c','67','6c','7c', + ....: '048','149','24a','34b','059','15a','25b','358', + ....: '06a','16b','268','369','07b','178','279','37a', + ....: '0123c','89abc'], + ....: 3: ['0123456789abc']}) + sage: M.is_valid() + True + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.NonVamos()) + sage: M.is_valid() + True + + TESTS:: + + sage: Matroid(flats={0: [], 1: [[0], [1]], 2: [[0, 1]]}).is_valid() # missing an intersection + False + sage: Matroid(flats={0: [[]], 2: [[0], [1]], 3: [[0, 1]]}).is_valid() # invalid ranks + False + sage: Matroid(flats={0: [[]], 1: [[0], [1]], 2: [[0], [0, 1]]}).is_valid() # duplicates + False + sage: Matroid(flats={0: [[]], 1: [[0], [1], [0, 1]]}).is_valid() + False + sage: Matroid(flats={0: [[]], 1: [[0, 1], [2]], 2: [[0], [1], [0, 1, 2]]}).is_valid() + False + sage: M = Matroid(flats={0: [''], # missing an extention of flat ['5'] by '6' + ....: 1: ['0','1','2','3','4','5','6','7','8','9','a','b','c'], + ....: 2: ['45','46','47','4c','57','5c','67','6c','7c', + ....: '048','149','24a','34b','059','15a','25b','358', + ....: '06a','16b','268','369','07b','178','279','37a', + ....: '0123c','89abc'], + ....: 3: ['0123456789abc']}) + sage: M.is_valid() + False + """ + cdef int i, j, k + cdef frozenset F1, F2, F3, I12 + cdef list ranks, cover, flats_lst + cdef bint flag + + # check flats dictionary for invalid ranks and repeated flats + ranks = sorted(self._F) + if ranks != list(range(len(ranks))): + return False + flats_lst = [F for i in self._F for F in self._F[i]] + if len(flats_lst) != len(set(flats_lst)): + return False + + # the groundset must be a flat + flag = False + for i in self._F: + for F1 in self._F[i]: + if F1 == self._groundset: + flag = True + break + if not flag: + return False + + # a single element extension of a flat must be a subset of exactly one flat + for i in ranks[:-1]: + for F1 in self._F[i]: + cover = [] + for F2 in self._F[i+1]: + if F2 >= F1: + cover.extend(F1 ^ F2) + if len(cover) != len(F1 ^ self._groundset) or set(cover) != F1 ^ self._groundset: + return False + + # the intersection of two flats must be a flat + for i in ranks: + for j in ranks[i:]: + for F1 in self._F[i]: + for F2 in self._F[j]: + flag = False + I12 = F1 & F2 + for k in self._F: + if k <= i: + for F3 in self._F[k]: + if F3 == I12: + flag = True + break + if flag: + break + if not flag: + return False + + return True diff --git a/src/sage/matroids/unpickling.pyx b/src/sage/matroids/unpickling.pyx index 2cd946d7503..da9ef0a5e5f 100644 --- a/src/sage/matroids/unpickling.pyx +++ b/src/sage/matroids/unpickling.pyx @@ -14,6 +14,7 @@ Python terminology) functions for Sage's matroids. AUTHORS: - Rudi Pendavingh, Stefan van Zwam (2013-07-01): initial version +- Giorgos Mousa (2024-01-01): add CircuitsMatroid and FlatsMatroid """ # **************************************************************************** # Copyright (C) 2013 Rudi Pendavingh @@ -32,6 +33,7 @@ from sage.rings.rational cimport Rational from sage.matroids.basis_matroid cimport BasisMatroid from sage.matroids.circuits_matroid cimport CircuitsMatroid from sage.matroids.circuit_closures_matroid cimport CircuitClosuresMatroid +from sage.matroids.flats_matroid cimport FlatsMatroid from sage.matroids.dual_matroid import DualMatroid from sage.matroids.graphic_matroid import GraphicMatroid from sage.matroids.lean_matrix cimport GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, PlusMinusOneMatrix, RationalMatrix @@ -173,6 +175,50 @@ def unpickle_circuit_closures_matroid(version, data): return M +############################################################################# +# FlatsMatroid +############################################################################# + +def unpickle_flats_matroid(version, data): + """ + Unpickle a FlatsMatroid. + + *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, F, name)`` in which ``E`` is the groundset of + the matroid, ``F`` is the dictionary of flats, and ``name`` is a custom + name. + + OUTPUT: + + A matroid. + + .. WARNING:: + + Users should never call this function directly. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: M == loads(dumps(M)) # indirect doctest + True + """ + cdef FlatsMatroid M + if version != 0: + raise TypeError("object was created with newer version of Sage. Please upgrade.") + M = FlatsMatroid(groundset=data[0], flats=data[1]) + if data[2] is not None: + M.rename(data[2]) + return M + + ############################################################################# # DualMatroid ############################################################################# From c68aec538497b99364b4588b2c246dc5627cbdbf Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Thu, 15 Feb 2024 12:45:09 +0200 Subject: [PATCH 2/8] Few optimizations --- src/sage/matroids/flats_matroid.pyx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 546526ee300..1456a952d89 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -76,14 +76,14 @@ cdef class FlatsMatroid(Matroid): self._F[i].add(frozenset(F)) else: self._groundset = frozenset(groundset) - for i in flats: + for i in sorted(flats): for F in flats[i]: try: self._F[i].add(frozenset(F)) except KeyError: self._F[i] = set() self._F[i].add(frozenset(F)) - self._matroid_rank = self.rank(self._groundset) + self._matroid_rank = max([0] + list(self._F)) cpdef groundset(self) noexcept: """ @@ -121,16 +121,20 @@ cdef class FlatsMatroid(Matroid): sage: M = FlatsMatroid(matroids.Theta(3)) sage: M._rank(['x1', 'y0', 'y2']) 2 + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = matroids.catalog.NonDesargues() + sage: F = FlatsMatroid(M) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == F.rank(S) """ cdef frozenset XX = frozenset(X) - cdef int min = len(self._groundset) - for i in self._F: - if i < min: + for i in range(self.rank() + 1): for f in self._F[i]: if f >= XX: - min = i - break - return min + return i # optional @@ -409,7 +413,7 @@ cdef class FlatsMatroid(Matroid): """ cdef list W = [] cdef int i - for i in sorted(self._F): + for i in self._F: W.append(len(self._F[i])) return W @@ -476,7 +480,7 @@ cdef class FlatsMatroid(Matroid): cdef bint flag # check flats dictionary for invalid ranks and repeated flats - ranks = sorted(self._F) + ranks = list(self._F) if ranks != list(range(len(ranks))): return False flats_lst = [F for i in self._F for F in self._F[i]] From bc92c7ee3ab15fe90e05504da1fc119f4764794a Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Thu, 15 Feb 2024 14:52:13 +0200 Subject: [PATCH 3/8] Set "default=-1" for rank of FlatsMatroid This is for an invalid input with no flats --- src/sage/matroids/flats_matroid.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 1456a952d89..3c69920717b 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -83,7 +83,7 @@ cdef class FlatsMatroid(Matroid): except KeyError: self._F[i] = set() self._F[i].add(frozenset(F)) - self._matroid_rank = max([0] + list(self._F)) + self._matroid_rank = max(self._F, default=-1) cpdef groundset(self) noexcept: """ From 427ca29c02ba95c6054897597c6bba7f9a7210ff Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Fri, 16 Feb 2024 17:30:32 +0200 Subject: [PATCH 4/8] Add FlatsMatroid to doc --- src/doc/en/reference/matroids/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/reference/matroids/index.rst b/src/doc/en/reference/matroids/index.rst index 692d798d222..b52c5b3420a 100644 --- a/src/doc/en/reference/matroids/index.rst +++ b/src/doc/en/reference/matroids/index.rst @@ -29,6 +29,7 @@ Concrete implementations sage/matroids/basis_matroid sage/matroids/circuits_matroid sage/matroids/circuit_closures_matroid + sage/matroids/flats_matroid sage/matroids/linear_matroid sage/matroids/rank_matroid sage/matroids/graphic_matroid From fadbcae1ff1c9ef3f5810e3401aa1dd020cc9f97 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Sun, 25 Feb 2024 14:43:03 +0200 Subject: [PATCH 5/8] Minor improvements in testing --- src/sage/matroids/flats_matroid.pyx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 3c69920717b..14816ddd7b5 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -150,9 +150,9 @@ cdef class FlatsMatroid(Matroid): EXAMPLES:: sage: from sage.matroids.flats_matroid import FlatsMatroid - sage: M = FlatsMatroid(matroids.Theta(7)) + sage: M = FlatsMatroid(matroids.Theta(6)) sage: M.full_rank() - 7 + 6 """ return self._matroid_rank @@ -410,6 +410,12 @@ cdef class FlatsMatroid(Matroid): sage: M = FlatsMatroid(matroids.catalog.XY13()) sage: M.whitney_numbers2() [1, 13, 78, 250, 394, 191, 1] + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: for M in matroids.AllMatroids(4): # optional - matroid_database + ....: assert M.whitney_numbers2() == FlatsMatroid(M).whitney_numbers2() """ cdef list W = [] cdef int i From 9cb320881fe75c6cc12919f0d40eaf894450f957 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Sun, 24 Mar 2024 22:53:32 +0200 Subject: [PATCH 6/8] Suggestions by mkoeppe --- src/sage/matroids/constructor.py | 16 ++++++++++++++-- src/sage/matroids/flats_matroid.pyx | 4 ++-- src/sage/matroids/unpickling.pyx | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/sage/matroids/constructor.py b/src/sage/matroids/constructor.py index ef14148947b..cecfead568a 100644 --- a/src/sage/matroids/constructor.py +++ b/src/sage/matroids/constructor.py @@ -179,7 +179,7 @@ def Matroid(groundset=None, data=None, **kwds): matroid. - ``independent_sets`` -- The list of independent sets of the matroid. - ``circuits`` -- The list of circuits of the matroid. - - ``nonspanning_circuits`` -- The list of nonspanning_circuits of the + - ``nonspanning_circuits`` -- The list of nonspanning circuits of the matroid. - ``flats`` -- The dictionary of flats indexed by their rank. - ``graph`` -- A graph, whose edges form the elements of the matroid. @@ -226,6 +226,8 @@ def Matroid(groundset=None, data=None, **kwds): The ``Matroid()`` method will return instances of type :class:`BasisMatroid `, + :class:`CircuitsMatroid `, + :class:`FlatsMatroid `, :class:`CircuitClosuresMatroid `, :class:`LinearMatroid `, :class:`BinaryMatroid `, @@ -307,7 +309,7 @@ def Matroid(groundset=None, data=None, **kwds): sage: M1 = Matroid(groundset='abc', circuits=['bc']) A matroid specified by a list of circuits gets converted to a - :class:`CircuitsMatroid ` + :class:`CircuitsMatroid ` internally:: sage: from sage.matroids.circuits_matroid import CircuitsMatroid @@ -331,6 +333,16 @@ def Matroid(groundset=None, data=None, **kwds): sage: M.is_valid() False + #. Dictionary of flats: + + :: + + sage: M = Matroid(flats={0: [''], 1: ['a', 'b'], 2: ['ab']}) + sage: M.is_valid() + True + sage: type(M) + + #. Graph: Sage has great support for graphs, see :mod:`sage.graphs.graph`. diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 14816ddd7b5..44dec61c93d 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -111,7 +111,7 @@ cdef class FlatsMatroid(Matroid): INPUT: - - ``X`` -- an object with Python's ``frozenset`` interface + - ``X`` -- an object with Python's :class:`frozenset` interface OUTPUT: an integer; the rank of ``X`` in the matroid @@ -357,7 +357,7 @@ cdef class FlatsMatroid(Matroid): - ``k`` -- an integer - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` EXAMPLES:: diff --git a/src/sage/matroids/unpickling.pyx b/src/sage/matroids/unpickling.pyx index da9ef0a5e5f..9e41c28a477 100644 --- a/src/sage/matroids/unpickling.pyx +++ b/src/sage/matroids/unpickling.pyx @@ -181,7 +181,7 @@ def unpickle_circuit_closures_matroid(version, data): def unpickle_flats_matroid(version, data): """ - Unpickle a FlatsMatroid. + Unpickle a :class:`FlatsMatroid`. *Pickling* is Python's term for the loading and saving of objects. Functions like these serve to reconstruct a saved object. This all happens From 72e41219e9639ee41d0e01c793def7de3ebefbbc Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Mon, 25 Mar 2024 12:31:36 +0200 Subject: [PATCH 7/8] Remove noexcept-s and simplify __copy__ and __deepcopy__ --- src/sage/matroids/flats_matroid.pxd | 16 +++++++-------- src/sage/matroids/flats_matroid.pyx | 31 ++++++++++------------------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/sage/matroids/flats_matroid.pxd b/src/sage/matroids/flats_matroid.pxd index f3ceb787f30..e97b929429b 100644 --- a/src/sage/matroids/flats_matroid.pxd +++ b/src/sage/matroids/flats_matroid.pxd @@ -4,17 +4,17 @@ cdef class FlatsMatroid(Matroid): cdef frozenset _groundset # _E cdef int _matroid_rank # _R cdef dict _F # flats - cpdef groundset(self) noexcept - cpdef _rank(self, X) noexcept - cpdef full_rank(self) noexcept - cpdef _is_independent(self, F) noexcept + cpdef groundset(self) + cpdef _rank(self, X) + cpdef full_rank(self) + cpdef _is_independent(self, F) # enumeration - cpdef flats(self, k) noexcept - cpdef whitney_numbers2(self) noexcept + cpdef flats(self, k) + cpdef whitney_numbers2(self) # isomorphism - cpdef _is_isomorphic(self, other, certificate=*) noexcept + cpdef _is_isomorphic(self, other, certificate=*) # verification - cpdef is_valid(self) noexcept + cpdef is_valid(self) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 44dec61c93d..0629d987dd1 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -85,7 +85,7 @@ cdef class FlatsMatroid(Matroid): self._F[i].add(frozenset(F)) self._matroid_rank = max(self._F, default=-1) - cpdef groundset(self) noexcept: + cpdef groundset(self): """ Return the groundset of the matroid. @@ -102,7 +102,7 @@ cdef class FlatsMatroid(Matroid): """ return self._groundset - cpdef _rank(self, X) noexcept: + cpdef _rank(self, X): """ Return the rank of a set ``X``. @@ -138,7 +138,7 @@ cdef class FlatsMatroid(Matroid): # optional - cpdef full_rank(self) noexcept: + cpdef full_rank(self): r""" Return the rank of the matroid. @@ -156,7 +156,7 @@ cdef class FlatsMatroid(Matroid): """ return self._matroid_rank - cpdef _is_isomorphic(self, other, certificate=False) noexcept: + cpdef _is_isomorphic(self, other, certificate=False): """ Test if ``self`` is isomorphic to ``other``. @@ -286,12 +286,7 @@ cdef class FlatsMatroid(Matroid): sage: M.groundset() is N.groundset() True """ - N = FlatsMatroid(groundset=[], flats={}) - N._groundset = self._groundset - N._F = self._F - N._matroid_rank = self._matroid_rank - N.rename(self.get_custom_name()) - return N + return self def __deepcopy__(self, memo=None): """ @@ -309,15 +304,9 @@ cdef class FlatsMatroid(Matroid): sage: M == N True sage: M.groundset() is N.groundset() - False + True """ - 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 = FlatsMatroid(groundset=deepcopy(self._groundset, memo), flats=deepcopy(self._F, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N + return self def __reduce__(self): """ @@ -349,7 +338,7 @@ cdef class FlatsMatroid(Matroid): # enumeration - cpdef flats(self, k) noexcept: + cpdef flats(self, k): r""" Return the flats of the matroid of specified rank. @@ -394,7 +383,7 @@ cdef class FlatsMatroid(Matroid): for F in self._F[k]: yield F - cpdef whitney_numbers2(self) noexcept: + cpdef whitney_numbers2(self): r""" Return the Whitney numbers of the second kind of the matroid. @@ -425,7 +414,7 @@ cdef class FlatsMatroid(Matroid): # verification - cpdef is_valid(self) noexcept: + cpdef is_valid(self): r""" Test if ``self`` obeys the matroid axioms. From d4659d2bba497b34dc9b7fbb32826bea2e3fd3c2 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Tue, 26 Mar 2024 12:05:49 +0200 Subject: [PATCH 8/8] Remove copy and deepcopy TODO: add FlatsMatroid copy tests in matroid.pyx after #37670 --- src/sage/matroids/advanced.py | 2 ++ src/sage/matroids/flats_matroid.pyx | 36 ----------------------------- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/src/sage/matroids/advanced.py b/src/sage/matroids/advanced.py index b38075bb39c..d290aca2060 100644 --- a/src/sage/matroids/advanced.py +++ b/src/sage/matroids/advanced.py @@ -15,6 +15,7 @@ - :class:`RankMatroid ` - :class:`CircuitClosuresMatroid ` - :class:`BasisMatroid ` + - :class:`FlatsMatroid ` - :class:`LinearMatroid ` - :class:`RegularMatroid ` - :class:`BinaryMatroid ` @@ -54,6 +55,7 @@ from .rank_matroid import RankMatroid from .circuit_closures_matroid import CircuitClosuresMatroid from .basis_matroid import BasisMatroid +from .flats_matroid import FlatsMatroid from .linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid from .utilities import setprint, newlabel, get_nonisomorphic_matroids, lift_cross_ratios, lift_map from . import lean_matrix diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 0629d987dd1..ae95455bb2f 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -272,42 +272,6 @@ cdef class FlatsMatroid(Matroid): # copying, loading, saving - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: from sage.matroids.flats_matroid import FlatsMatroid - sage: M = FlatsMatroid(matroids.catalog.Vamos()) - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - return self - - def __deepcopy__(self, memo=None): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: from sage.matroids.flats_matroid import FlatsMatroid - sage: M = FlatsMatroid(matroids.catalog.Vamos()) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - return self - def __reduce__(self): """ Save the matroid for later reloading.