def Matroid(groundset=None, data=None, **kwds): r""" Construct a matroid. Matroids are combinatorial structures that capture the abstract properties of (linear/algebraic/...) dependence. Formally, a matroid is a pair `M = (E, I)` of a finite set `E`, the *groundset*, and a collection of subsets `I`, the independent sets, subject to the following axioms: * `I` contains the empty set * If `X` is a set in `I`, then each subset of `X` is in `I` * If two subsets `X`, `Y` are in `I`, and `|X| > |Y|`, then there exists `x \in X - Y` such that `Y + \{x\}` is in `I`. See the :wikipedia:`Wikipedia article on matroids <Matroid>` for more theory and examples. Matroids can be obtained from many types of mathematical structures, and Sage supports a number of them. There are two main entry points to Sage's matroid functionality. For built-in matroids, do the following: * Within a Sage session, type "matroids." (Do not press "Enter", and do not forget the final period ".") * Hit "tab". You will see a list of methods which will construct matroids. For example:: sage: F7 = matroids.named_matroids.Fano() sage: len(F7.nonspanning_circuits()) 7 or:: sage: U36 = matroids.Uniform(3, 6) sage: U36.equals(U36.dual()) True To define your own matroid, use the function ``Matroid()``. This function attempts to interpret its arguments to create an appropriate matroid. The following named arguments are supported: INPUT: - ``groundset`` -- (optional) If provided, the groundset of the matroid. Otherwise, the function attempts to determine a groundset from the data. Exactly one of the following inputs must be given (where ``data`` must be a positional argument and anything else must be a keyword argument): - ``data`` -- a graph or a matrix or a RevLex-Index string or a list of independent sets containing all bases or a matroid. - ``bases`` -- The list of bases (maximal independent sets) of the matroid. - ``independent_sets`` -- The list of independent sets of the matroid. - ``circuits`` -- The list of circuits of the matroid. - ``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 ``reduced_matrix = A`` then the matroid is represented by `[I\ \ A]` where `I` is an appropriately sized identity matrix. - ``rank_function`` -- A function that computes the rank of each subset. Can only be provided together with a groundset. - ``circuit_closures`` -- Either a list of tuples ``(k, C)`` with ``C`` the closure of a circuit, and ``k`` the rank of ``C``, or a dictionary ``D`` with ``D[k]`` the set of closures of rank-``k`` circuits. - ``revlex`` -- the encoding as a string of ``0`` and ``*`` symbols. Used by [MatroidDatabase]_ and explained in [MMIB2012]_. - ``matroid`` -- An object that is already a matroid. Useful only with the ``regular`` option. Further options: - ``regular`` -- (default: ``False``) boolean. If ``True``, output a :class:`RegularMatroid <sage.matroids.linear_matroid.RegularMatroid>` instance such that, *if* the input defines a valid regular matroid, then the output represents this matroid. Note that this option can be combined with any type of input. - ``ring`` -- any ring. If provided, and the input is a ``matrix`` or ``reduced_matrix``, output will be a linear matroid over the ring or field ``ring``. - ``field`` -- any field. Same as ``ring``, but only fields are allowed. - ``check`` -- (default: ``True``) boolean. If ``True`` and ``regular`` is true, the output is checked to make sure it is a valid regular matroid. .. WARNING:: Except for regular matroids, the input is not checked for validity. If your data does not correspond to an actual matroid, the behavior of the methods is undefined and may cause strange errors. To ensure you have a matroid, run :meth:`M.is_valid() <sage.matroids.matroid.Matroid.is_valid>`. .. NOTE:: The ``Matroid()`` method will return instances of type :class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`, :class:`CircuitClosuresMatroid <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`, :class:`LinearMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`BinaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`TernaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`QuaternaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`RegularMatroid <sage.matroids.linear_matroid.LinearMatroid>`, or :class:`RankMatroid <sage.matroids.rank_matroid.RankMatroid>`. To import these classes (and other useful functions) directly into Sage's main namespace, type:: sage: from sage.matroids.advanced import * See :mod:`sage.matroids.advanced <sage.matroids.advanced>`. EXAMPLES: Note that in these examples we will often use the fact that strings are iterable in these examples. So we type ``'abcd'`` to denote the list ``['a', 'b', 'c', 'd']``. #. List of bases: All of the following inputs are allowed, and equivalent:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'ac', 'ad', ....: 'bc', 'bd', 'cd']) sage: M2 = Matroid(bases=['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M3 = Matroid(['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M4 = Matroid('abcd', ['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M5 = Matroid('abcd', bases=[['a', 'b'], ['a', 'c'], ....: ['a', 'd'], ['b', 'c'], ....: ['b', 'd'], ['c', 'd']]) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True sage: M1 == M5 True We do not check if the provided input forms an actual matroid:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'cd']) sage: M1.full_rank() 2 sage: M1.is_valid() False Bases may be repeated:: sage: M1 = Matroid(['ab', 'ac']) sage: M2 = Matroid(['ab', 'ac', 'ab']) sage: M1 == M2 True #. List of independent sets: :: sage: M1 = Matroid(groundset='abcd', ....: independent_sets=['', 'a', 'b', 'c', 'd', 'ab', ....: 'ac', 'ad', 'bc', 'bd', 'cd']) We only require that the list of independent sets contains each basis of the matroid; omissions of smaller independent sets and repetitions are allowed:: sage: M1 = Matroid(bases=['ab', 'ac']) sage: M2 = Matroid(independent_sets=['a', 'ab', 'b', 'ab', 'a', ....: 'b', 'ac']) sage: M1 == M2 True #. List of circuits: :: 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 <sage.matroids.basis_matroid.BasisMatroid>` internally:: 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 :meth:`is_valid() <sage.matroids.matroid.Matroid.is_valid>` method. So always check whether your input makes sense! :: sage: M = Matroid('abcd', circuits=['ab', 'acd']) sage: M.is_valid() True sage: [sorted(C) for C in M.circuits()] [['a']] #. Graph: Sage has great support for graphs, see :mod:`sage.graphs.graph`. :: sage: G = graphs.PetersenGraph() sage: Matroid(G) Graphic matroid of rank 9 on 15 elements If each edge has a unique label, then those are used as the ground set labels:: sage: G = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'c')]) sage: M = Matroid(G) sage: sorted(M.groundset()) ['a', 'b', 'c'] If there are parallel edges, then integers are used for the ground set. If there are no edges in parallel, and is not a complete list of labels, or the labels are not unique, then vertex tuples are used:: sage: G = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'b')]) sage: M = Matroid(G) sage: sorted(M.groundset()) [(0, 1), (0, 2), (1, 2)] sage: H = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'b'), (1, 2, 'c')], multiedges=True) sage: N = Matroid(H) sage: sorted(N.groundset()) [0, 1, 2, 3] The GraphicMatroid object forces its graph to be connected. If a disconnected graph is used as input, it will connect the components. sage: G1 = graphs.CycleGraph(3); G2 = graphs.DiamondGraph() sage: G = G1.disjoint_union(G2) sage: M = Matroid(G) sage: M Graphic matroid of rank 5 on 8 elements sage: M.graph() Looped multi-graph on 6 vertices sage: M.graph().is_connected() True sage: M.is_connected() False If the keyword ``regular`` is set to ``True``, the output will instead be an instance of ``RegularMatroid``. :: sage: G = Graph([(0, 1), (0, 2), (1, 2)]) sage: M = Matroid(G, regular=True); M Regular matroid of rank 2 on 3 elements with 3 bases Note: if a groundset is specified, we assume it is in the same order as :meth:`G.edge_iterator() <sage.graphs.generic_graph.GenericGraph.edge_iterator>` provides:: sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)], multiedges=True) sage: M = Matroid('abcd', G) sage: M.rank(['b', 'c']) 1 As before, if no edge labels are present and the graph is simple, we use the tuples ``(i, j)`` of endpoints. If that fails, we simply use a list ``[0..m-1]`` :: sage: G = Graph([(0, 1), (0, 2), (1, 2)]) sage: M = Matroid(G, regular=True) sage: sorted(M.groundset()) [(0, 1), (0, 2), (1, 2)] sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)], multiedges=True) sage: M = Matroid(G, regular=True) sage: sorted(M.groundset()) [0, 1, 2, 3] When the ``graph`` keyword is used, a variety of inputs can be converted to a graph automatically. The following uses a graph6 string (see the :class:`Graph <sage.graphs.graph.Graph>` method's documentation):: sage: Matroid(graph=':I`AKGsaOs`cI]Gb~') Graphic matroid of rank 9 on 17 elements However, this method is no more clever than ``Graph()``:: sage: Matroid(graph=41/2) Traceback (most recent call last): ... ValueError: This input cannot be turned into a graph #. Matrix: The basic input is a :mod:`Sage matrix <sage.matrix.constructor>`:: sage: A = Matrix(GF(2), [[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]]) sage: M = Matroid(matrix=A) sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True Various shortcuts are possible:: sage: M1 = Matroid(matrix=[[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]], ring=GF(2)) sage: M2 = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], ring=GF(2)) sage: M3 = Matroid(groundset=[0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: ring=GF(2)) sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M4 = Matroid([0, 1, 2, 3, 4, 5], A) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True However, with unnamed arguments the input has to be a ``Matrix`` instance, or the function will try to interpret it as a set of bases:: sage: Matroid([0, 1, 2], [[1, 0, 1], [0, 1, 1]]) Traceback (most recent call last): ... ValueError: basis has wrong cardinality. If the groundset size equals number of rows plus number of columns, an identity matrix is prepended. Otherwise the groundset size must equal the number of columns:: sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M = Matroid([0, 1, 2], A) sage: N = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.rank() 2 sage: N.rank() 3 We automatically create an optimized subclass, if available:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2)) Binary matroid of rank 3 on 6 elements, type (2, 7) sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(3)) Ternary matroid of rank 3 on 6 elements, type 0- sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(4, 'x')) Quaternary matroid of rank 3 on 6 elements sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2), regular=True) Regular matroid of rank 3 on 6 elements with 16 bases Otherwise the generic LinearMatroid class is used:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(83)) Linear matroid of rank 3 on 6 elements represented over the Finite Field of size 83 An integer matrix is automatically converted to a matrix over `\QQ`. If you really want integers, you can specify the ring explicitly:: sage: A = Matrix([[1, 1, 0], [1, 0, 1], [0, 1, -1]]) sage: A.base_ring() Integer Ring sage: M = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.base_ring() Rational Field sage: M = Matroid([0, 1, 2, 3, 4, 5], A, ring=ZZ) sage: M.base_ring() Integer Ring #. Rank function: Any function mapping subsets to integers can be used as input:: sage: def f(X): ....: return min(len(X), 2) sage: M = Matroid('abcd', rank_function=f) sage: M Matroid of rank 2 on 4 elements sage: M.is_isomorphic(matroids.Uniform(2, 4)) True #. Circuit closures: This is often a really concise way to specify a matroid. The usual way is a dictionary of lists:: sage: M = Matroid(circuit_closures={3: ['edfg', 'acdg', 'bcfg', ....: 'cefh', 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'], ....: 4: ['abcdefgh']}) sage: M.equals(matroids.named_matroids.P8()) True You can also input tuples `(k, X)` where `X` is the closure of a circuit, and `k` the rank of `X`:: sage: M = Matroid(circuit_closures=[(2, 'abd'), (3, 'abcdef'), ....: (2, 'bce')]) sage: M.equals(matroids.named_matroids.Q6()) True #. RevLex-Index: This requires the ``groundset`` to be given and also needs a additional keyword argument ``rank`` to specify the rank of the matroid:: sage: M = Matroid("abcdef", "000000******0**", rank=4); M Matroid of rank 4 on 6 elements with 8 bases sage: list(M.bases()) [frozenset({'a', 'b', 'd', 'f'}), frozenset({'a', 'c', 'd', 'f'}), frozenset({'b', 'c', 'd', 'f'}), frozenset({'a', 'b', 'e', 'f'}), frozenset({'a', 'c', 'e', 'f'}), frozenset({'b', 'c', 'e', 'f'}), frozenset({'b', 'd', 'e', 'f'}), frozenset({'c', 'd', 'e', 'f'})] Only the ``0`` symbols really matter, any symbol can be used instead of ``*``: sage: Matroid("abcdefg", revlex="0++++++++0++++0+++++0+--++----+--++", rank=4) Matroid of rank 4 on 7 elements with 31 bases It is checked that the input makes sense (but not that it defines a matroid):: sage: Matroid("abcdef", "000000******0**") Traceback (most recent call last): ... TypeError: for RevLex-Index, the rank needs to be specified sage: Matroid("abcdef", "000000******0**", rank=3) Traceback (most recent call last): ... ValueError: expected string of length 20 (6 choose 3), got 15 sage: M = Matroid("abcdef", "*0000000000000*", rank=4); M Matroid of rank 4 on 6 elements with 2 bases sage: M.is_valid() False #. Matroid: Most of the time, the matroid itself is returned:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M) sage: N is M True But it can be useful with the ``regular`` option:: sage: M = Matroid(circuit_closures={2:['adb', 'bec', 'cfa', ....: 'def'], 3:['abcdef']}) sage: N = Matroid(M, regular=True) sage: N Regular matroid of rank 3 on 6 elements with 16 bases sage: Matrix(N) [1 0 0 1 1 0] [0 1 0 1 1 1] [0 0 1 0 1 1] The ``regular`` option:: sage: M = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], regular=True) sage: M Regular matroid of rank 3 on 6 elements with 16 bases sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True By default we check if the resulting matroid is actually regular. To increase speed, this check can be skipped:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M, regular=True) Traceback (most recent call last): ... ValueError: input is not a valid regular matroid sage: N = Matroid(M, regular=True, check=False) sage: N Regular matroid of rank 3 on 7 elements with 32 bases sage: N.is_valid() False Sometimes the output is regular, but represents a different matroid from the one you intended:: sage: M = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]])) sage: N = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]]), ....: regular=True) sage: N.is_valid() True sage: N.is_isomorphic(M) False TESTS:: sage: Matroid() Traceback (most recent call last): ... TypeError: no input data given for Matroid() sage: Matroid("abc", bases=["abc"], foo="bar") Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'foo' sage: Matroid(data=["x"], matrix=Matrix(1,1)) Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'matrix' sage: Matroid(bases=["x"], matrix=Matrix(1,1)) Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'matrix' sage: Matroid(Matrix(1,1), ring=ZZ, field=QQ) Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'ring' sage: Matroid(rank_function=lambda X: len(X)) Traceback (most recent call last): ... TypeError: for rank functions, the groundset needs to be specified sage: Matroid(matroid="rubbish") Traceback (most recent call last): ... TypeError: input 'rubbish' is not a matroid """ # process options want_regular = kwds.pop('regular', False) check = kwds.pop('check', True) base_ring = None if 'field' in kwds: base_ring = kwds.pop('field') if check and not base_ring.is_field(): raise TypeError("{} is not a field".format(base_ring)) elif 'ring' in kwds: base_ring = kwds.pop('ring') if check and not base_ring.is_ring(): raise TypeError("{} is not a ring".format(base_ring)) # "key" is the kind of data we got key = None if data is None: for k in ['bases', 'independent_sets', 'circuits', 'graph', 'matrix', 'reduced_matrix', 'rank_function', 'revlex', 'circuit_closures', 'matroid']: if k in kwds: data = kwds.pop(k) key = k break else: # Assume that the single positional argument was actually # the data (instead of the groundset) data = groundset groundset = None if key is None: if isinstance(data, sage.graphs.graph.Graph): key = 'graph' elif is_Matrix(data): key = 'matrix' elif isinstance(data, sage.matroids.matroid.Matroid): key = 'matroid' elif isinstance(data, str): key = 'revlex' elif data is None: raise TypeError("no input data given for Matroid()") else: key = 'independent_sets' # Bases: if key == 'bases': if groundset is None: groundset = set() for B in data: groundset.update(B) M = BasisMatroid(groundset=groundset, bases=data) # Independent sets: elif key == 'independent_sets': # Convert to list of bases first rk = -1 bases = [] for I in data: if len(I) == rk: bases.append(I) elif len(I) > rk: bases = [I] rk = len(I) if groundset is None: groundset = set() for B in bases: groundset.update(B) M = BasisMatroid(groundset=groundset, bases=bases) # Circuits: elif key == 'circuits': # Convert to list of bases first # Determine groundset (note that this cannot detect coloops) if groundset is None: 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) # Graphs: elif key == 'graph': if isinstance(data, sage.graphs.generic_graph.GenericGraph): G = data else: G = Graph(data) # Decide on the groundset m = G.num_edges() if groundset is None: # 1. Attempt to use edge labels. sl = G.edge_labels() if len(sl) == len(set(sl)): groundset = sl # 2. If simple, use vertex tuples elif not G.has_multiple_edges(): groundset = [(i, j) for i, j, k in G.edge_iterator()] else: # 3. Use numbers groundset = list(range(m)) if want_regular: # Construct the incidence matrix # NOTE: we are not using Sage's built-in method because # 1) we would need to fix the loops anyway # 2) Sage will sort the columns, making it impossible to keep labels! V = G.vertices() n = G.num_verts() A = Matrix(ZZ, n, m, 0) mm = 0 for i, j, k in G.edge_iterator(): A[V.index(i), mm] = -1 A[V.index(j), mm] += 1 # So loops get 0 mm += 1 M = RegularMatroid(matrix=A, groundset=groundset) want_regular = False # Save some time, since result is already regular else: M = GraphicMatroid(G, groundset=groundset) # Matrices: elif key in ['matrix', 'reduced_matrix']: A = data is_reduced = (key == 'reduced_matrix') # Fix the representation if not is_Matrix(A): if base_ring is not None: A = Matrix(base_ring, A) else: A = Matrix(A) # Fix the ring if base_ring is not None: if A.base_ring() is not base_ring: A = A.change_ring(base_ring) elif A.base_ring() is ZZ and not want_regular: # Usually a rational matrix is intended, we presume. A = A.change_ring(QQ) base_ring = QQ else: base_ring = A.base_ring() # Check groundset if groundset is not None: if not is_reduced: if len(groundset) == A.ncols(): pass elif len(groundset) == A.nrows() + A.ncols(): is_reduced = True else: raise ValueError("groundset size does not correspond to matrix size") elif is_reduced: if len(groundset) == A.nrows() + A.ncols(): pass else: raise ValueError("groundset size does not correspond to matrix size") if is_reduced: kw = dict(groundset=groundset, reduced_matrix=A) else: kw = dict(groundset=groundset, matrix=A) if isinstance(base_ring, FiniteField): q = base_ring.order() else: q = 0 if q == 2: M = BinaryMatroid(**kw) elif q == 3: M = TernaryMatroid(**kw) elif q == 4: M = QuaternaryMatroid(**kw) else: M = LinearMatroid(ring=base_ring, **kw) # Rank functions: elif key == 'rank_function': if groundset is None: raise TypeError('for rank functions, the groundset needs to be specified') M = RankMatroid(groundset=groundset, rank_function=data) # RevLex-Index: elif key == "revlex": if groundset is None: raise TypeError('for RevLex-Index, the groundset needs to be specified') try: rk = kwds.pop("rank") except KeyError: raise TypeError('for RevLex-Index, the rank needs to be specified') groundset = tuple(groundset) data = tuple(data) rk = int(rk) N = len(groundset) def revlex_sort_key(s): return tuple(reversed(s)) subsets = sorted(combinations(range(N), rk), key=revlex_sort_key) if len(data) != len(subsets): raise ValueError("expected string of length %s (%s choose %s), got %s" % (len(subsets), N, rk, len(data))) bases = [] for i, x in enumerate(data): if x != '0': bases.append([groundset[c] for c in subsets[i]]) M = BasisMatroid(groundset=groundset, bases=bases) # Circuit closures: elif key == 'circuit_closures': if isinstance(data, dict): CC = data else: # Convert to dictionary CC = {} for X in data: if X[0] not in CC: CC[X[0]] = [] CC[X[0]].append(X[1]) if groundset is None: groundset = set() for X in itervalues(CC): for Y in X: groundset.update(Y) M = CircuitClosuresMatroid(groundset=groundset, circuit_closures=CC) # Matroids: elif key == 'matroid': if not isinstance(data, sage.matroids.matroid.Matroid): raise TypeError("input {!r} is not a matroid".format(data)) M = data else: raise AssertionError("unknown key %r" % key) # All keywords should be used for k in kwds: raise TypeError("Matroid() got an unexpected keyword argument '{}'".format(k)) if want_regular: M = sage.matroids.utilities.make_regular_matroid_from_matroid(M) if check and not M.is_valid(): raise ValueError('input is not a valid regular matroid') return M
def Matroid(groundset=None, data=None, **kwds): r""" Construct a matroid. Matroids are combinatorial structures that capture the abstract properties of (linear/algebraic/...) dependence. Formally, a matroid is a pair `M = (E, I)` of a finite set `E`, the *groundset*, and a collection of subsets `I`, the independent sets, subject to the following axioms: * `I` contains the empty set * If `X` is a set in `I`, then each subset of `X` is in `I` * If two subsets `X`, `Y` are in `I`, and `|X| > |Y|`, then there exists `x \in X - Y` such that `Y + \{x\}` is in `I`. See the :wikipedia:`Wikipedia article on matroids <Matroid>` for more theory and examples. Matroids can be obtained from many types of mathematical structures, and Sage supports a number of them. There are two main entry points to Sage's matroid functionality. For built-in matroids, do the following: * Within a Sage session, type "matroids." (Do not press "Enter", and do not forget the final period ".") * Hit "tab". You will see a list of methods which will construct matroids. For example:: sage: F7 = matroids.named_matroids.Fano() sage: len(F7.nonspanning_circuits()) 7 or:: sage: U36 = matroids.Uniform(3, 6) sage: U36.equals(U36.dual()) True To define your own matroid, use the function ``Matroid()``. This function attempts to interpret its arguments to create an appropriate matroid. The following named arguments are supported: INPUT: - ``groundset`` -- (optional) If provided, the groundset of the matroid. Otherwise, the function attempts to determine a groundset from the data. Exactly one of the following inputs must be given (where ``data`` must be a positional argument and anything else must be a keyword argument): - ``data`` -- a graph or a matrix or a RevLex-Index string or a list of independent sets containing all bases or a matroid. - ``bases`` -- The list of bases (maximal independent sets) of the matroid. - ``independent_sets`` -- The list of independent sets of the matroid. - ``circuits`` -- The list of circuits of the matroid. - ``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 ``reduced_matrix = A`` then the matroid is represented by `[I\ \ A]` where `I` is an appropriately sized identity matrix. - ``rank_function`` -- A function that computes the rank of each subset. Can only be provided together with a groundset. - ``circuit_closures`` -- Either a list of tuples ``(k, C)`` with ``C`` the closure of a circuit, and ``k`` the rank of ``C``, or a dictionary ``D`` with ``D[k]`` the set of closures of rank-``k`` circuits. - ``revlex`` -- the encoding as a string of ``0`` and ``*`` symbols. Used by [MatroidDatabase]_ and explained in [MMIB2012]_. - ``matroid`` -- An object that is already a matroid. Useful only with the ``regular`` option. Further options: - ``regular`` -- (default: ``False``) boolean. If ``True``, output a :class:`RegularMatroid <sage.matroids.linear_matroid.RegularMatroid>` instance such that, *if* the input defines a valid regular matroid, then the output represents this matroid. Note that this option can be combined with any type of input. - ``ring`` -- any ring. If provided, and the input is a ``matrix`` or ``reduced_matrix``, output will be a linear matroid over the ring or field ``ring``. - ``field`` -- any field. Same as ``ring``, but only fields are allowed. - ``check`` -- (default: ``True``) boolean. If ``True`` and ``regular`` is true, the output is checked to make sure it is a valid regular matroid. .. WARNING:: Except for regular matroids, the input is not checked for validity. If your data does not correspond to an actual matroid, the behavior of the methods is undefined and may cause strange errors. To ensure you have a matroid, run :meth:`M.is_valid() <sage.matroids.matroid.Matroid.is_valid>`. .. NOTE:: The ``Matroid()`` method will return instances of type :class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`, :class:`CircuitClosuresMatroid <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`, :class:`LinearMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`BinaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`TernaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`QuaternaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`RegularMatroid <sage.matroids.linear_matroid.LinearMatroid>`, or :class:`RankMatroid <sage.matroids.rank_matroid.RankMatroid>`. To import these classes (and other useful functions) directly into Sage's main namespace, type:: sage: from sage.matroids.advanced import * See :mod:`sage.matroids.advanced <sage.matroids.advanced>`. EXAMPLES: Note that in these examples we will often use the fact that strings are iterable in these examples. So we type ``'abcd'`` to denote the list ``['a', 'b', 'c', 'd']``. #. List of bases: All of the following inputs are allowed, and equivalent:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'ac', 'ad', ....: 'bc', 'bd', 'cd']) sage: M2 = Matroid(bases=['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M3 = Matroid(['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M4 = Matroid('abcd', ['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M5 = Matroid('abcd', bases=[['a', 'b'], ['a', 'c'], ....: ['a', 'd'], ['b', 'c'], ....: ['b', 'd'], ['c', 'd']]) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True sage: M1 == M5 True We do not check if the provided input forms an actual matroid:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'cd']) sage: M1.full_rank() 2 sage: M1.is_valid() False Bases may be repeated:: sage: M1 = Matroid(['ab', 'ac']) sage: M2 = Matroid(['ab', 'ac', 'ab']) sage: M1 == M2 True #. List of independent sets: :: sage: M1 = Matroid(groundset='abcd', ....: independent_sets=['', 'a', 'b', 'c', 'd', 'ab', ....: 'ac', 'ad', 'bc', 'bd', 'cd']) We only require that the list of independent sets contains each basis of the matroid; omissions of smaller independent sets and repetitions are allowed:: sage: M1 = Matroid(bases=['ab', 'ac']) sage: M2 = Matroid(independent_sets=['a', 'ab', 'b', 'ab', 'a', ....: 'b', 'ac']) sage: M1 == M2 True #. List of circuits: :: 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 <sage.matroids.basis_matroid.BasisMatroid>` internally:: 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 :meth:`is_valid() <sage.matroids.matroid.Matroid.is_valid>` method. So always check whether your input makes sense! :: sage: M = Matroid('abcd', circuits=['ab', 'acd']) sage: M.is_valid() True sage: [sorted(C) for C in M.circuits()] # py2 [['a']] sage: [sorted(C) for C in M.circuits()] # py3 random [['a']] #. Graph: Sage has great support for graphs, see :mod:`sage.graphs.graph`. :: sage: G = graphs.PetersenGraph() sage: Matroid(G) Graphic matroid of rank 9 on 15 elements If each edge has a unique label, then those are used as the ground set labels:: sage: G = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'c')]) sage: M = Matroid(G) sage: sorted(M.groundset()) ['a', 'b', 'c'] If there are parallel edges, then integers are used for the ground set. If there are no edges in parallel, and is not a complete list of labels, or the labels are not unique, then vertex tuples are used:: sage: G = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'b')]) sage: M = Matroid(G) sage: sorted(M.groundset()) [(0, 1), (0, 2), (1, 2)] sage: H = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'b'), (1, 2, 'c')], multiedges=True) sage: N = Matroid(H) sage: sorted(N.groundset()) [0, 1, 2, 3] The GraphicMatroid object forces its graph to be connected. If a disconnected graph is used as input, it will connect the components. sage: G1 = graphs.CycleGraph(3); G2 = graphs.DiamondGraph() sage: G = G1.disjoint_union(G2) sage: M = Matroid(G) sage: M Graphic matroid of rank 5 on 8 elements sage: M.graph() Looped multi-graph on 6 vertices sage: M.graph().is_connected() True sage: M.is_connected() False If the keyword ``regular`` is set to ``True``, the output will instead be an instance of ``RegularMatroid``. :: sage: G = Graph([(0, 1), (0, 2), (1, 2)]) sage: M = Matroid(G, regular=True); M Regular matroid of rank 2 on 3 elements with 3 bases Note: if a groundset is specified, we assume it is in the same order as :meth:`G.edge_iterator() <sage.graphs.generic_graph.GenericGraph.edge_iterator>` provides:: sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)], multiedges=True) sage: M = Matroid('abcd', G) sage: M.rank(['b', 'c']) 1 As before, if no edge labels are present and the graph is simple, we use the tuples ``(i, j)`` of endpoints. If that fails, we simply use a list ``[0..m-1]`` :: sage: G = Graph([(0, 1), (0, 2), (1, 2)]) sage: M = Matroid(G, regular=True) sage: sorted(M.groundset()) [(0, 1), (0, 2), (1, 2)] sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)], multiedges=True) sage: M = Matroid(G, regular=True) sage: sorted(M.groundset()) [0, 1, 2, 3] When the ``graph`` keyword is used, a variety of inputs can be converted to a graph automatically. The following uses a graph6 string (see the :class:`Graph <sage.graphs.graph.Graph>` method's documentation):: sage: Matroid(graph=':I`AKGsaOs`cI]Gb~') Graphic matroid of rank 9 on 17 elements However, this method is no more clever than ``Graph()``:: sage: Matroid(graph=41/2) Traceback (most recent call last): ... ValueError: This input cannot be turned into a graph #. Matrix: The basic input is a :mod:`Sage matrix <sage.matrix.constructor>`:: sage: A = Matrix(GF(2), [[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]]) sage: M = Matroid(matrix=A) sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True Various shortcuts are possible:: sage: M1 = Matroid(matrix=[[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]], ring=GF(2)) sage: M2 = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], ring=GF(2)) sage: M3 = Matroid(groundset=[0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: ring=GF(2)) sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M4 = Matroid([0, 1, 2, 3, 4, 5], A) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True However, with unnamed arguments the input has to be a ``Matrix`` instance, or the function will try to interpret it as a set of bases:: sage: Matroid([0, 1, 2], [[1, 0, 1], [0, 1, 1]]) Traceback (most recent call last): ... ValueError: basis has wrong cardinality. If the groundset size equals number of rows plus number of columns, an identity matrix is prepended. Otherwise the groundset size must equal the number of columns:: sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M = Matroid([0, 1, 2], A) sage: N = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.rank() 2 sage: N.rank() 3 We automatically create an optimized subclass, if available:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2)) Binary matroid of rank 3 on 6 elements, type (2, 7) sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(3)) Ternary matroid of rank 3 on 6 elements, type 0- sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(4, 'x')) Quaternary matroid of rank 3 on 6 elements sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2), regular=True) Regular matroid of rank 3 on 6 elements with 16 bases Otherwise the generic LinearMatroid class is used:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(83)) Linear matroid of rank 3 on 6 elements represented over the Finite Field of size 83 An integer matrix is automatically converted to a matrix over `\QQ`. If you really want integers, you can specify the ring explicitly:: sage: A = Matrix([[1, 1, 0], [1, 0, 1], [0, 1, -1]]) sage: A.base_ring() Integer Ring sage: M = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.base_ring() Rational Field sage: M = Matroid([0, 1, 2, 3, 4, 5], A, ring=ZZ) sage: M.base_ring() Integer Ring #. Rank function: Any function mapping subsets to integers can be used as input:: sage: def f(X): ....: return min(len(X), 2) sage: M = Matroid('abcd', rank_function=f) sage: M Matroid of rank 2 on 4 elements sage: M.is_isomorphic(matroids.Uniform(2, 4)) True #. Circuit closures: This is often a really concise way to specify a matroid. The usual way is a dictionary of lists:: sage: M = Matroid(circuit_closures={3: ['edfg', 'acdg', 'bcfg', ....: 'cefh', 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'], ....: 4: ['abcdefgh']}) sage: M.equals(matroids.named_matroids.P8()) True You can also input tuples `(k, X)` where `X` is the closure of a circuit, and `k` the rank of `X`:: sage: M = Matroid(circuit_closures=[(2, 'abd'), (3, 'abcdef'), ....: (2, 'bce')]) sage: M.equals(matroids.named_matroids.Q6()) True #. RevLex-Index: This requires the ``groundset`` to be given and also needs a additional keyword argument ``rank`` to specify the rank of the matroid:: sage: M = Matroid("abcdef", "000000******0**", rank=4); M Matroid of rank 4 on 6 elements with 8 bases sage: list(M.bases()) [frozenset({'a', 'b', 'd', 'f'}), frozenset({'a', 'c', 'd', 'f'}), frozenset({'b', 'c', 'd', 'f'}), frozenset({'a', 'b', 'e', 'f'}), frozenset({'a', 'c', 'e', 'f'}), frozenset({'b', 'c', 'e', 'f'}), frozenset({'b', 'd', 'e', 'f'}), frozenset({'c', 'd', 'e', 'f'})] Only the ``0`` symbols really matter, any symbol can be used instead of ``*``: sage: Matroid("abcdefg", revlex="0++++++++0++++0+++++0+--++----+--++", rank=4) Matroid of rank 4 on 7 elements with 31 bases It is checked that the input makes sense (but not that it defines a matroid):: sage: Matroid("abcdef", "000000******0**") Traceback (most recent call last): ... TypeError: for RevLex-Index, the rank needs to be specified sage: Matroid("abcdef", "000000******0**", rank=3) Traceback (most recent call last): ... ValueError: expected string of length 20 (6 choose 3), got 15 sage: M = Matroid("abcdef", "*0000000000000*", rank=4); M Matroid of rank 4 on 6 elements with 2 bases sage: M.is_valid() False #. Matroid: Most of the time, the matroid itself is returned:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M) sage: N is M True But it can be useful with the ``regular`` option:: sage: M = Matroid(circuit_closures={2:['adb', 'bec', 'cfa', ....: 'def'], 3:['abcdef']}) sage: N = Matroid(M, regular=True) sage: N Regular matroid of rank 3 on 6 elements with 16 bases sage: M == N False sage: M.is_isomorphic(N) True sage: Matrix(N) # py2 [1 0 0 1 1 0] [0 1 0 1 1 1] [0 0 1 0 1 1] sage: Matrix(N) # py3 random [1 0 0 1 1 0] [0 1 0 1 1 1] [0 0 1 0 1 1] The ``regular`` option:: sage: M = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], regular=True) sage: M Regular matroid of rank 3 on 6 elements with 16 bases sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True By default we check if the resulting matroid is actually regular. To increase speed, this check can be skipped:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M, regular=True) Traceback (most recent call last): ... ValueError: input is not a valid regular matroid sage: N = Matroid(M, regular=True, check=False) sage: N Regular matroid of rank 3 on 7 elements with 32 bases sage: N.is_valid() False Sometimes the output is regular, but represents a different matroid from the one you intended:: sage: M = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]])) sage: N = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]]), ....: regular=True) sage: N.is_valid() True sage: N.is_isomorphic(M) False TESTS:: sage: Matroid() Traceback (most recent call last): ... TypeError: no input data given for Matroid() sage: Matroid("abc", bases=["abc"], foo="bar") Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'foo' sage: Matroid(data=["x"], matrix=Matrix(1,1)) Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'matrix' sage: Matroid(bases=["x"], matrix=Matrix(1,1)) Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'matrix' sage: Matroid(Matrix(1,1), ring=ZZ, field=QQ) Traceback (most recent call last): ... TypeError: Matroid() got an unexpected keyword argument 'ring' sage: Matroid(rank_function=lambda X: len(X)) Traceback (most recent call last): ... TypeError: for rank functions, the groundset needs to be specified sage: Matroid(matroid="rubbish") Traceback (most recent call last): ... TypeError: input 'rubbish' is not a matroid """ # process options want_regular = kwds.pop('regular', False) check = kwds.pop('check', True) base_ring = None if 'field' in kwds: base_ring = kwds.pop('field') if check and not base_ring.is_field(): raise TypeError("{} is not a field".format(base_ring)) elif 'ring' in kwds: base_ring = kwds.pop('ring') if check and not base_ring.is_ring(): raise TypeError("{} is not a ring".format(base_ring)) # "key" is the kind of data we got key = None if data is None: for k in [ 'bases', 'independent_sets', 'circuits', 'graph', 'matrix', 'reduced_matrix', 'rank_function', 'revlex', 'circuit_closures', 'matroid' ]: if k in kwds: data = kwds.pop(k) key = k break else: # Assume that the single positional argument was actually # the data (instead of the groundset) data = groundset groundset = None if key is None: if isinstance(data, sage.graphs.graph.Graph): key = 'graph' elif is_Matrix(data): key = 'matrix' elif isinstance(data, sage.matroids.matroid.Matroid): key = 'matroid' elif isinstance(data, str): key = 'revlex' elif data is None: raise TypeError("no input data given for Matroid()") else: key = 'independent_sets' # Bases: if key == 'bases': if groundset is None: groundset = set() for B in data: groundset.update(B) M = BasisMatroid(groundset=groundset, bases=data) # Independent sets: elif key == 'independent_sets': # Convert to list of bases first rk = -1 bases = [] for I in data: if len(I) == rk: bases.append(I) elif len(I) > rk: bases = [I] rk = len(I) if groundset is None: groundset = set() for B in bases: groundset.update(B) M = BasisMatroid(groundset=groundset, bases=bases) # Circuits: elif key == 'circuits': # Convert to list of bases first # Determine groundset (note that this cannot detect coloops) if groundset is None: 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) # Graphs: elif key == 'graph': if isinstance(data, sage.graphs.generic_graph.GenericGraph): G = data else: G = Graph(data) # Decide on the groundset m = G.num_edges() if groundset is None: # 1. Attempt to use edge labels. sl = G.edge_labels() if len(sl) == len(set(sl)): groundset = sl # 2. If simple, use vertex tuples elif not G.has_multiple_edges(): groundset = [(i, j) for i, j, k in G.edge_iterator()] else: # 3. Use numbers groundset = list(range(m)) if want_regular: # Construct the incidence matrix # NOTE: we are not using Sage's built-in method because # 1) we would need to fix the loops anyway # 2) Sage will sort the columns, making it impossible to keep labels! V = G.vertices() n = G.num_verts() A = Matrix(ZZ, n, m, 0) mm = 0 for i, j, k in G.edge_iterator(): A[V.index(i), mm] = -1 A[V.index(j), mm] += 1 # So loops get 0 mm += 1 M = RegularMatroid(matrix=A, groundset=groundset) want_regular = False # Save some time, since result is already regular else: M = GraphicMatroid(G, groundset=groundset) # Matrices: elif key in ['matrix', 'reduced_matrix']: A = data is_reduced = (key == 'reduced_matrix') # Fix the representation if not is_Matrix(A): if base_ring is not None: A = Matrix(base_ring, A) else: A = Matrix(A) # Fix the ring if base_ring is not None: if A.base_ring() is not base_ring: A = A.change_ring(base_ring) elif A.base_ring( ) is ZZ and not want_regular: # Usually a rational matrix is intended, we presume. A = A.change_ring(QQ) base_ring = QQ else: base_ring = A.base_ring() # Check groundset if groundset is not None: if not is_reduced: if len(groundset) == A.ncols(): pass elif len(groundset) == A.nrows() + A.ncols(): is_reduced = True else: raise ValueError( "groundset size does not correspond to matrix size") elif is_reduced: if len(groundset) == A.nrows() + A.ncols(): pass else: raise ValueError( "groundset size does not correspond to matrix size") if is_reduced: kw = dict(groundset=groundset, reduced_matrix=A) else: kw = dict(groundset=groundset, matrix=A) if isinstance(base_ring, FiniteField): q = base_ring.order() else: q = 0 if q == 2: M = BinaryMatroid(**kw) elif q == 3: M = TernaryMatroid(**kw) elif q == 4: M = QuaternaryMatroid(**kw) else: M = LinearMatroid(ring=base_ring, **kw) # Rank functions: elif key == 'rank_function': if groundset is None: raise TypeError( 'for rank functions, the groundset needs to be specified') M = RankMatroid(groundset=groundset, rank_function=data) # RevLex-Index: elif key == "revlex": if groundset is None: raise TypeError( 'for RevLex-Index, the groundset needs to be specified') try: rk = kwds.pop("rank") except KeyError: raise TypeError('for RevLex-Index, the rank needs to be specified') groundset = tuple(groundset) data = tuple(data) rk = int(rk) N = len(groundset) def revlex_sort_key(s): return tuple(reversed(s)) subsets = sorted(combinations(range(N), rk), key=revlex_sort_key) if len(data) != len(subsets): raise ValueError( "expected string of length %s (%s choose %s), got %s" % (len(subsets), N, rk, len(data))) bases = [] for i, x in enumerate(data): if x != '0': bases.append([groundset[c] for c in subsets[i]]) M = BasisMatroid(groundset=groundset, bases=bases) # Circuit closures: elif key == 'circuit_closures': if isinstance(data, dict): CC = data else: # Convert to dictionary CC = {} for X in data: if X[0] not in CC: CC[X[0]] = [] CC[X[0]].append(X[1]) if groundset is None: groundset = set() for X in itervalues(CC): for Y in X: groundset.update(Y) M = CircuitClosuresMatroid(groundset=groundset, circuit_closures=CC) # Matroids: elif key == 'matroid': if not isinstance(data, sage.matroids.matroid.Matroid): raise TypeError("input {!r} is not a matroid".format(data)) M = data else: raise AssertionError("unknown key %r" % key) # All keywords should be used for k in kwds: raise TypeError( "Matroid() got an unexpected keyword argument '{}'".format(k)) if want_regular: M = sage.matroids.utilities.make_regular_matroid_from_matroid(M) if check and not M.is_valid(): raise ValueError('input is not a valid regular matroid') return M
def Matroid(*args, **kwds): r""" Construct a matroid. Matroids are combinatorial structures that capture the abstract properties of (linear/algebraic/...) dependence. Formally, a matroid is a pair `M = (E, I)` of a finite set `E`, the *groundset*, and a collection of subsets `I`, the independent sets, subject to the following axioms: * `I` contains the empty set * If `X` is a set in `I`, then each subset of `X` is in `I` * If two subsets `X`, `Y` are in `I`, and `|X| > |Y|`, then there exists `x \in X - Y` such that `Y + \{x\}` is in `I`. See the :wikipedia:`Wikipedia article on matroids <Matroid>` for more theory and examples. Matroids can be obtained from many types of mathematical structures, and Sage supports a number of them. There are two main entry points to Sage's matroid functionality. For built-in matroids, do the following: * Within a Sage session, type "matroids." (Do not press "Enter", and do not forget the final period ".") * Hit "tab". You will see a list of methods which will construct matroids. For example:: sage: F7 = matroids.named_matroids.Fano() sage: len(F7.nonspanning_circuits()) 7 or:: sage: U36 = matroids.Uniform(3, 6) sage: U36.equals(U36.dual()) True To define your own matroid, use the function ``Matroid()``. This function attempts to interpret its arguments to create an appropriate matroid. The following named arguments are supported: INPUT: - ``groundset`` -- If provided, the groundset of the matroid. If not provided, the function attempts to determine a groundset from the data. - ``bases`` -- The list of bases (maximal independent sets) of the matroid. - ``independent_sets`` -- The list of independent sets of the matroid. - ``circuits`` -- The list of circuits of the matroid. - ``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 ``reduced_matrix = A`` then the matroid is represented by `[I\ \ A]` where `I` is an appropriately sized identity matrix. - ``rank_function`` -- A function that computes the rank of each subset. Can only be provided together with a groundset. - ``circuit_closures`` -- Either a list of tuples ``(k, C)`` with ``C`` the closure of a circuit, and ``k`` the rank of ``C``, or a dictionary ``D`` with ``D[k]`` the set of closures of rank-``k`` circuits. - ``matroid`` -- An object that is already a matroid. Useful only with the ``regular`` option. Up to two unnamed arguments are allowed. - One unnamed argument, no named arguments other than ``regular`` -- the input should be either a graph, or a matrix, or a list of independent sets containing all bases, or a matroid. - Two unnamed arguments: the first is the groundset, the second a graph, or a matrix, or a list of independent sets containing all bases, or a matroid. - One unnamed argument, at least one named argument: the unnamed argument is the groundset, the named argument is as above (but must be different from ``groundset``). The examples section details how each of the input types deals with explicit or implicit groundset arguments. OPTIONS: - ``regular`` -- (default: ``False``) boolean. If ``True``, output a :class:`RegularMatroid <sage.matroids.linear_matroid.RegularMatroid>` instance such that, *if* the input defines a valid regular matroid, then the output represents this matroid. Note that this option can be combined with any type of input. - ``ring`` -- any ring. If provided, and the input is a ``matrix`` or ``reduced_matrix``, output will be a linear matroid over the ring or field ``ring``. - ``field`` -- any field. Same as ``ring``, but only fields are allowed. - ``check`` -- (default: ``True``) boolean. If ``True`` and ``regular`` is true, the output is checked to make sure it is a valid regular matroid. .. WARNING:: Except for regular matroids, the input is not checked for validity. If your data does not correspond to an actual matroid, the behavior of the methods is undefined and may cause strange errors. To ensure you have a matroid, run :meth:`M.is_valid() <sage.matroids.matroid.Matroid.is_valid>`. .. NOTE:: The ``Matroid()`` method will return instances of type :class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`, :class:`CircuitClosuresMatroid <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`, :class:`LinearMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`BinaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`TernaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`QuaternaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`RegularMatroid <sage.matroids.linear_matroid.LinearMatroid>`, or :class:`RankMatroid <sage.matroids.rank_matroid.RankMatroid>`. To import these classes (and other useful functions) directly into Sage's main namespace, type:: sage: from sage.matroids.advanced import * See :mod:`sage.matroids.advanced <sage.matroids.advanced>`. EXAMPLES: Note that in these examples we will often use the fact that strings are iterable in these examples. So we type ``'abcd'`` to denote the list ``['a', 'b', 'c', 'd']``. #. List of bases: All of the following inputs are allowed, and equivalent:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'ac', 'ad', ....: 'bc', 'bd', 'cd']) sage: M2 = Matroid(bases=['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M3 = Matroid(['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M4 = Matroid('abcd', ['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M5 = Matroid('abcd', bases=[['a', 'b'], ['a', 'c'], ....: ['a', 'd'], ['b', 'c'], ....: ['b', 'd'], ['c', 'd']]) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True sage: M1 == M5 True We do not check if the provided input forms an actual matroid:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'cd']) sage: M1.full_rank() 2 sage: M1.is_valid() False Bases may be repeated:: sage: M1 = Matroid(['ab', 'ac']) sage: M2 = Matroid(['ab', 'ac', 'ab']) sage: M1 == M2 True #. List of independent sets: :: sage: M1 = Matroid(groundset='abcd', ....: independent_sets=['', 'a', 'b', 'c', 'd', 'ab', ....: 'ac', 'ad', 'bc', 'bd', 'cd']) We only require that the list of independent sets contains each basis of the matroid; omissions of smaller independent sets and repetitions are allowed:: sage: M1 = Matroid(bases=['ab', 'ac']) sage: M2 = Matroid(independent_sets=['a', 'ab', 'b', 'ab', 'a', ....: 'b', 'ac']) sage: M1 == M2 True #. List of circuits: :: 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 <sage.matroids.basis_matroid.BasisMatroid>` internally:: sage: M = Matroid(groundset='abcd', circuits=['abc', 'abd', 'acd', ....: 'bcd']) sage: type(M) <type '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 :meth:`is_valid() <sage.matroids.matroid.Matroid.is_valid>` method. So always check whether your input makes sense! :: sage: M = Matroid('abcd', circuits=['ab', 'acd']) sage: M.is_valid() True sage: [sorted(C) for C in M.circuits()] [['a']] #. Graph: Sage has great support for graphs, see :mod:`sage.graphs.graph`. :: sage: G = graphs.PetersenGraph() sage: Matroid(G) Regular matroid of rank 9 on 15 elements with 2000 bases Note: if a groundset is specified, we assume it is in the same order as :meth:`G.edge_iterator() <sage.graphs.generic_graph.GenericGraph.edge_iterator>` provides:: sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)]) sage: M = Matroid('abcd', G) sage: M.rank(['b', 'c']) 1 If no groundset is provided, we attempt to use the edge labels:: sage: G = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'c')]) sage: M = Matroid(G) sage: sorted(M.groundset()) ['a', 'b', 'c'] If no edge labels are present and the graph is simple, we use the tuples ``(i, j)`` of endpoints. If that fails, we simply use a list ``[0..m-1]`` :: sage: G = Graph([(0, 1), (0, 2), (1, 2)]) sage: M = Matroid(G) sage: sorted(M.groundset()) [(0, 1), (0, 2), (1, 2)] sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)]) sage: M = Matroid(G) sage: sorted(M.groundset()) [0, 1, 2, 3] When the ``graph`` keyword is used, a variety of inputs can be converted to a graph automatically. The following uses a graph6 string (see the :class:`Graph <sage.graphs.graph.Graph>` method's documentation):: sage: Matroid(graph=':I`AKGsaOs`cI]Gb~') Regular matroid of rank 9 on 17 elements with 4004 bases However, this method is no more clever than ``Graph()``:: sage: Matroid(graph=41/2) Traceback (most recent call last): ... ValueError: input does not seem to represent a graph. #. Matrix: The basic input is a :mod:`Sage matrix <sage.matrix.constructor>`:: sage: A = Matrix(GF(2), [[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]]) sage: M = Matroid(matrix=A) sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True Various shortcuts are possible:: sage: M1 = Matroid(matrix=[[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]], ring=GF(2)) sage: M2 = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], ring=GF(2)) sage: M3 = Matroid(groundset=[0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: ring=GF(2)) sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M4 = Matroid([0, 1, 2, 3, 4, 5], A) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True However, with unnamed arguments the input has to be a ``Matrix`` instance, or the function will try to interpret it as a set of bases:: sage: Matroid([0, 1, 2], [[1, 0, 1], [0, 1, 1]]) Traceback (most recent call last): ... ValueError: basis has wrong cardinality. If the groundset size equals number of rows plus number of columns, an identity matrix is prepended. Otherwise the groundset size must equal the number of columns:: sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M = Matroid([0, 1, 2], A) sage: N = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.rank() 2 sage: N.rank() 3 We automatically create an optimized subclass, if available:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2)) Binary matroid of rank 3 on 6 elements, type (2, 7) sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(3)) Ternary matroid of rank 3 on 6 elements, type 0- sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(4, 'x')) Quaternary matroid of rank 3 on 6 elements sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2), regular=True) Regular matroid of rank 3 on 6 elements with 16 bases Otherwise the generic LinearMatroid class is used:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(83)) Linear matroid of rank 3 on 6 elements represented over the Finite Field of size 83 An integer matrix is automatically converted to a matrix over `\QQ`. If you really want integers, you can specify the ring explicitly:: sage: A = Matrix([[1, 1, 0], [1, 0, 1], [0, 1, -1]]) sage: A.base_ring() Integer Ring sage: M = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.base_ring() Rational Field sage: M = Matroid([0, 1, 2, 3, 4, 5], A, ring=ZZ) sage: M.base_ring() Integer Ring #. Rank function: Any function mapping subsets to integers can be used as input:: sage: def f(X): ....: return min(len(X), 2) ....: sage: M = Matroid('abcd', rank_function=f) sage: M Matroid of rank 2 on 4 elements sage: M.is_isomorphic(matroids.Uniform(2, 4)) True #. Circuit closures: This is often a really concise way to specify a matroid. The usual way is a dictionary of lists:: sage: M = Matroid(circuit_closures={3: ['edfg', 'acdg', 'bcfg', ....: 'cefh', 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'], ....: 4: ['abcdefgh']}) sage: M.equals(matroids.named_matroids.P8()) True You can also input tuples `(k, X)` where `X` is the closure of a circuit, and `k` the rank of `X`:: sage: M = Matroid(circuit_closures=[(2, 'abd'), (3, 'abcdef'), ....: (2, 'bce')]) sage: M.equals(matroids.named_matroids.Q6()) True #. Matroid: Most of the time, the matroid itself is returned:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M) sage: N is M True But it can be useful with the ``regular`` option:: sage: M = Matroid(circuit_closures={2:['adb', 'bec', 'cfa', ....: 'def'], 3:['abcdef']}) sage: N = Matroid(M, regular=True) sage: N Regular matroid of rank 3 on 6 elements with 16 bases sage: Matrix(N) [1 0 0 1 1 0] [0 1 0 1 1 1] [0 0 1 0 1 1] The ``regular`` option: :: sage: M = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], regular=True) sage: M Regular matroid of rank 3 on 6 elements with 16 bases sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True By default we check if the resulting matroid is actually regular. To increase speed, this check can be skipped:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M, regular=True) Traceback (most recent call last): ... ValueError: input does not correspond to a valid regular matroid. sage: N = Matroid(M, regular=True, check=False) sage: N Regular matroid of rank 3 on 7 elements with 32 bases sage: N.is_valid() False Sometimes the output is regular, but represents a different matroid from the one you intended:: sage: M = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]])) sage: N = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]]), ....: regular=True) sage: N.is_valid() True sage: N.is_isomorphic(M) False """ # These are the valid arguments: inputS = set([ 'groundset', 'bases', 'independent_sets', 'circuits', 'graph', 'matrix', 'reduced_matrix', 'rank_function', 'circuit_closures', 'matroid' ]) # process options if 'regular' in kwds: want_regular = kwds['regular'] kwds.pop('regular') else: want_regular = False if 'check' in kwds: want_check = kwds['check'] kwds.pop('check') else: want_check = True base_ring = None have_field = False if 'field' in kwds: base_ring = kwds['field'] kwds.pop('field') have_field = True if 'ring' in kwds: raise ValueError("only one of ring and field can be specified.") try: if not base_ring.is_field(): raise TypeError("specified ``field`` is not a field.") except AttributeError: raise TypeError("specified ``field`` is not a field.") if 'ring' in kwds: base_ring = kwds['ring'] kwds.pop('ring') try: if not base_ring.is_ring(): raise TypeError("specified ``ring`` is not a ring.") except AttributeError: raise TypeError("specified ``ring`` is not a ring.") # Process unnamed arguments if len(args) > 0: if 'groundset' in kwds: raise ValueError( 'when using unnamed arguments, groundset must be the first unnamed argument or be implicit.' ) if len(args) > 2: raise ValueError('at most two unnamed arguments are allowed.') if len(args) == 2 or len(kwds) > 0: # First argument should be the groundset kwds['groundset'] = args[0] # Check for unnamed data dataindex = -1 if len(args) == 2: dataindex = 1 if len(args) == 1 and len(kwds) == 0: dataindex = 0 if dataindex > -1: # One unnamed argument, no named arguments if isinstance(args[dataindex], sage.graphs.graph.Graph): kwds['graph'] = args[dataindex] elif isinstance(args[dataindex], sage.matrix.matrix.Matrix): kwds['matrix'] = args[dataindex] elif isinstance(args[dataindex], sage.matroids.matroid.Matroid): kwds['matroid'] = args[dataindex] else: kwds['independent_sets'] = args[dataindex] # Check for multiple types of input if len(set(kwds).difference(inputS)) > 0: raise ValueError("unknown input argument") if ('grondset' in kwds and len(kwds) != 2) or ('groundset' not in kwds and len(kwds) > 1): raise ValueError("only one type of input may be specified.") # Bases: if 'bases' in kwds: if 'groundset' not in kwds: gs = set() for B in kwds['bases']: gs.update(B) kwds['groundset'] = gs M = BasisMatroid(groundset=kwds['groundset'], bases=kwds['bases']) # Independent sets: if 'independent_sets' in kwds: # Convert to list of bases first rk = -1 bases = [] for I in kwds['independent_sets']: if len(I) == rk: bases.append(I) elif len(I) > rk: bases = [I] rk = len(I) if 'groundset' not in kwds: gs = set() for B in bases: gs.update(B) kwds['groundset'] = gs M = BasisMatroid(groundset=kwds['groundset'], bases=bases) # Circuits: if 'circuits' in kwds: # Convert to list of bases first # Determine groundset (note that this cannot detect coloops) if 'groundset' not in kwds: gs = set() for C in kwds['circuits']: gs.update(C) kwds['groundset'] = gs # determine the rank by computing a basis B = set(kwds['groundset']) for C in kwds['circuits']: 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(kwds['groundset'], rk) if not any([frozenset(C).issubset(B) for C in kwds['circuits']]) ] M = BasisMatroid(groundset=kwds['groundset'], bases=BB) # Graphs: if 'graph' in kwds: # Construct the incidence matrix # NOTE: we are not using Sage's built-in method because # 1) we would need to fix the loops anyway # 2) Sage will sort the columns, making it impossible to keep labels! G = kwds['graph'] if not isinstance(G, sage.graphs.generic_graph.GenericGraph): try: G = Graph(G) except (ValueError, TypeError, NetworkXError): raise ValueError("input does not seem to represent a graph.") V = G.vertices() E = G.edges() n = G.num_verts() m = G.num_edges() A = Matrix(ZZ, n, m, 0) mm = 0 for i, j, k in G.edge_iterator(): A[V.index(i), mm] = -1 A[V.index(j), mm] += 1 # So loops get 0 mm += 1 # Decide on the groundset if 'groundset' not in kwds: # 1. Attempt to use edge labels. sl = G.edge_labels() if len(sl) == len(set(sl)): kwds['groundset'] = sl # 2. If simple, use vertex tuples elif not G.has_multiple_edges(): kwds['groundset'] = [(i, j) for i, j, k in G.edge_iterator()] else: # 3. Use numbers kwds['groundset'] = range(m) M = RegularMatroid(matrix=A, groundset=kwds['groundset']) want_regular = False # Save some time, since result is already regular # Matrices: if 'matrix' in kwds or 'reduced_matrix' in kwds: if 'matrix' in kwds: A = kwds['matrix'] if 'reduced_matrix' in kwds: A = kwds['reduced_matrix'] # Fix the representation if not isinstance(A, sage.matrix.matrix.Matrix): try: if base_ring is not None: A = Matrix(base_ring, A) else: A = Matrix(A) except ValueError: raise ValueError("input does not seem to contain a matrix.") # Fix the ring if base_ring is not None: if A.base_ring() != base_ring: A = A.change_ring(base_ring) elif A.base_ring( ) == ZZ and not want_regular: # Usually a rational matrix is intended, we presume. A = A.change_ring(QQ) base_ring = QQ else: base_ring = A.base_ring() # Determine groundset: if 'matrix' in kwds: if 'groundset' in kwds: if len(kwds['groundset']) == A.nrows() + A.ncols(): kwds['reduced_matrix'] = A kwds.pop('matrix') else: if len(kwds['groundset']) != A.ncols(): raise ValueError( "groundset size does not correspond to matrix size." ) else: kwds['groundset'] = range(A.ncols()) if 'reduced_matrix' in kwds: if 'groundset' in kwds: if len(kwds['groundset']) != A.nrows() + A.ncols(): raise ValueError( "groundset size does not correspond to matrix size.") else: kwds['groundset'] = range(A.nrows() + A.ncols()) if 'matrix' in kwds: if base_ring == GF(2): M = BinaryMatroid(groundset=kwds['groundset'], matrix=A) elif base_ring == GF(3): M = TernaryMatroid(groundset=kwds['groundset'], matrix=A) elif base_ring.is_field() and base_ring.order( ) == 4: # GF(4) can have different generators. M = QuaternaryMatroid(groundset=kwds['groundset'], matrix=A) else: M = LinearMatroid(groundset=kwds['groundset'], matrix=A, ring=base_ring) if 'reduced_matrix' in kwds: if A.base_ring() == GF(2): M = BinaryMatroid(groundset=kwds['groundset'], reduced_matrix=A) elif A.base_ring() == GF(3): M = TernaryMatroid(groundset=kwds['groundset'], reduced_matrix=A) elif A.base_ring().is_field() and A.base_ring().order( ) == 4: # GF(4) can have different generators. M = QuaternaryMatroid(groundset=kwds['groundset'], reduced_matrix=A) else: M = LinearMatroid(groundset=kwds['groundset'], reduced_matrix=A, ring=base_ring) # Rank functions: if 'rank_function' in kwds: if 'groundset' not in kwds: raise ValueError( 'for rank functions, groundset needs to be specified.') M = RankMatroid(groundset=kwds['groundset'], rank_function=kwds['rank_function']) # Circuit closures: if 'circuit_closures' in kwds: if 'groundset' not in kwds: E = set() if isinstance(kwds['circuit_closures'], dict): for X in kwds['circuit_closures'].itervalues(): for Y in X: E.update(Y) else: for X in kwds['circuit_closures']: E.update(X[1]) else: E = kwds['groundset'] if not isinstance(kwds['circuit_closures'], dict): # Convert to dictionary CC = {} for X in kwds['circuit_closures']: if X[0] not in CC: CC[X[0]] = [] CC[X[0]].append(X[1]) else: CC = kwds['circuit_closures'] M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) # Matroids: if 'matroid' in kwds: M = kwds['matroid'] if not isinstance(M, sage.matroids.matroid.Matroid): raise ValueError("input does not appear to be of Matroid type.") # Regular option: if want_regular: M = sage.matroids.utilities.make_regular_matroid_from_matroid(M) if want_check: if not M.is_valid(): raise ValueError( 'input does not correspond to a valid regular matroid.') return M
class BianchiDistributionElement(ModuleElement): r""" This class represents elements in an overconvergent Bianchi coefficient module. INPUT: - ``parent`` - An overconvergent coefficient module. - ``val`` - The value that it needs to store (default: 0). It can be another BianchiDistributionElement, in which case the values are copied. It can also be a column vector (or something coercible to a column vector) which represents the values of the element applied to the polynomials `1`, `x`, `y`, `x^2`, `xy`, `y^2`, ... ,`y^n`. - ``check`` - boolean (default: True). If set to False, no checks are done and ``val`` is assumed to be the a column vector. """ def __init__(self, parent, val=0, check=True, normalize=False): """ Initialisation function. Takes as input a vector val, which should have length equal to the dimension of the space, which is the nth triangle number, where n is the depth. This corresponds to the ordered basis for distributions (namely, the dual basis to the basis 1, x, y, x^2, xy, ...). Input: - parent: BianchiDistributions object of depth n - val : vector of length n^2 encoding the moments of the distribution """ ## Parents/precision ModuleElement.__init__(self, parent) self._parent = parent self._depth = self._parent._depth self._dimension = self._parent._dimension ## check multiple possibilities for input if not check: self._moments = val else: ## is input already a distribution? if isinstance(val, self.__class__): ## if depth is the same, then keep this if val._parent._depth == parent._depth: self._moments = val._moments else: ## depths are different, take the minimum d = min([val.nrows(), parent.dimension()]) self._moments = val._moments.submatrix(0, 0, nrows=d) elif isinstance(val, int) or isinstance(val, Integer): ## Initialise distribution to be trivial, i.e. constant moment = input and rest = 0 self._moments = MatrixSpace(self._parent._R, self._dimension, 1)(0) self._moments[0, 0] = val ## input is a vector storing the moments elif isinstance(val, Vector_integer_dense) or isinstance( val, FreeModuleElement_generic_dense): self._moments = MatrixSpace(self._parent._R, self._dimension, 1)(0) for i, o in enumerate(val.list()): self._moments[i, 0] = o ## input is a list storing the moments elif isinstance(val, list): self._moments = MatrixSpace(self._parent._R, self._dimension, 1)(0) for i, o in enumerate(val): self._moments[i, 0] = o else: try: self._moments = Matrix(self._parent._R, self._depth, 1, val) except (TypeError, ValueError): self._moments = self._parent._R(val) * MatrixSpace( self._parent._R, self._dimension, 1)(1) def __hash__(self): return hash(self._moments) def moment(self, ij): """ Returns the (i,j)th moment mu(x^iy^j). EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,4) sage: mu = D.basis_vector((2,3)) sage: mu.moment((2,3)) 1 + O(11^3) sage: mu.moment((2,1)) O(11^3) """ if isinstance(ij, tuple): idx = self._parent.index(ij) else: idx = ij return self._parent._Rmod(self._moments[idx, 0]) def moments(self): """ Returns the moments (as a column vector). EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,2) sage: mu = D.basis_vector((1,1)) sage: mu.moments() [0] [0] [0] [1] """ return self._moments def __getitem__(self, ij): r""" """ return self.moment(ij) def __setitem__(self, ij, val): r""" Sets the value of ``self`` on the polynomial `x^iy^j` to ``val``. INPUT: - ``r`` - an integer. The power of `x`. - ``val`` - a value. """ if isinstance(ij, tuple): idx = self._parent.index(ij[0], ij[1]) else: idx = ij self._moments[idx, 0] = val def element(self): r""" The element ``self`` represents. """ tmp = self.matrix_rep() return [tmp[ii, 0] for ii in range(tmp.nrows())] def list(self): r""" The element ``self`` represents. """ return self.element() def matrix_rep(self, B=None): r""" Returns a matrix representation of ``self``. """ #Express the element in terms of the basis B if B is None: B = self._parent.basis() A = Matrix(self._parent._R, self._parent.dimension(), self._parent.dimension(), [[b._moments[ii, 0] for b in B] for ii in range(self._dimension)]) tmp = A.solve_right(self._moments) return tmp def _add_(self, y): r""" Add two elements. EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,4) sage: mu1 = D.basis_vector((2,3)) sage: mu2 = D.basis_vector((1,2)) sage: mu1 + mu2 X*Y^2 + X^2*Y^3 """ val = self._moments + y._moments return self.__class__(self._parent, val, check=False) def _sub_(self, y): r""" Subtract two elements. """ val = self._moments - y._moments return self.__class__(self._parent, val, check=False) def _neg_(self): return self.__class__(self._parent, -self._moments, check=False) def _rmul_(self, a): #assume that a is a scalar return self.__class__(self._parent, a * self._moments, check=False) def _repr_(self): r""" Returns the representation of self as a string. The monomial X^iY^j is the dual basis vector to the monomial x^iy^j in the ring of analytic functions. EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,4) sage: mu = D.basis_vector((2,1)) + D.basis_vector((1,0)) sage: mu X + X^2*Y sage: D.basis_vector((1,1)) + 2*D.basis_vector((2,1)) X*Y + 2*X^2*Y """ R = self.parent()._repr_R s = str( sum([ ZZ(self._moments[idx, 0]) * self._parent.monomial_from_index(idx, R) for idx in range(self._moments.nrows()) ])) return s def __cmp__(self, other): return cmp(self._moments, other._moments) def __nonzero__(self): return self._moments != 0 def evaluate_at_poly(self, P, R=None, depth=None): r""" Evaluate ``self`` at a polynomial. The polynomial can be defined over ZZ or Zp. By default, this function picks the ring R to be a ring that coerces to both the base ring of the polynomial and the Bianchi distribution. You can specify depth, but this currently does nothing at all. EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,4) sage: x,y = D.analytic_vars() sage: mu = D.basis_vector((2,1)) + D.basis_vector((1,0)) sage: mu(x^2*y) 1 sage: mu(y) 0 sage: mu(x^2*y + x) 2 """ p = self._parent._p ## Currently empty functionality if depth is None: depth = self._depth # Define the ring R, if not specified if R is None: try: R = pushout(P.parent().base_ring(), self.parent().base_ring()) except AttributeError: R = self.parent().base_ring() ## Attempt to coerce the input into a form we can evaluate P = self.parent().analytic_functions()(P) ## For each monomial x^iy^j in the polynomial, multip] ly the coefficient of X^iY^j (in mu) by the ## coefficient of x^iy^j (in f) and take the sum. This is our final value ## --> P.coefficients is a dictionary which has monomials as keys; we generate monomials using exponents. ## --> self._moments takes as input an index and spits out the cofficient. So generate the index from the exponent. coefficient_list = [] for polx in P.padded_list(self._depth): coefficient_list.extend(polx.padded_list(self._depth)) return ZZ((Matrix(coefficient_list) * self.moments())[0, 0]) def __call__(self, P): r""" Call function; evaluates the distribution at an analytic function. """ return self.evaluate_at_poly(P) def reduce_mod(self, N=None): r""" Reduces all the moments modulo N. """ if N is None: N = self.parent()._pN self._moments = self._moments.apply_map(lambda x: x % N) return self def max_filtration_step(self): r""" Computes the maximal step of the filtration in which mu lives. """ ## Take min of v_p(mu(x^i*y^j)) + i + j p = self._parent._p min_modified_moment = min( (o[0].valuation(p) + sum(tuple(self.parent().ij_from_pos(n))) for n, o in enumerate(self._moments))) ## Last filtration step in which this appears is step r, where r is the min such that this min/2 - r >= 0 ## ..... IN THE SUPERSINGULAR CASE! ## In the ordinary case, the min r is sufficient *without* dividing by 2!!! return min_modified_moment ## QQ(min_modified_moment/2).floor() def normalize(self, r=None): r""" Adjust the moments to the precision given by the filtration step where self belongs. """ if r is None: r = self.max_filtration_step() V = self._moments p = self._parent._p for n in xrange(self._moments.nrows()): k = r - sum(tuple(self.parent().ij_from_pos(n))) self._moments[n, 0] = self._moments[n, 0] % p**k return self def valuation(self): r""" Returns the same as max_filtration_step, defining an element to have valuation r if Filr^(r,r) is the maximal filtration step in which it lives. """ return self.max_filtration_step()
def Matroid(*args, **kwds): r""" Construct a matroid. Matroids are combinatorial structures that capture the abstract properties of (linear/algebraic/...) dependence. Formally, a matroid is a pair `M = (E, I)` of a finite set `E`, the *groundset*, and a collection of subsets `I`, the independent sets, subject to the following axioms: * `I` contains the empty set * If `X` is a set in `I`, then each subset of `X` is in `I` * If two subsets `X`, `Y` are in `I`, and `|X| > |Y|`, then there exists `x \in X - Y` such that `Y + \{x\}` is in `I`. See the :wikipedia:`Wikipedia article on matroids <Matroid>` for more theory and examples. Matroids can be obtained from many types of mathematical structures, and Sage supports a number of them. There are two main entry points to Sage's matroid functionality. For built-in matroids, do the following: * Within a Sage session, type "matroids." (Do not press "Enter", and do not forget the final period ".") * Hit "tab". You will see a list of methods which will construct matroids. For example:: sage: F7 = matroids.named_matroids.Fano() sage: len(F7.nonspanning_circuits()) 7 or:: sage: U36 = matroids.Uniform(3, 6) sage: U36.equals(U36.dual()) True To define your own matroid, use the function ``Matroid()``. This function attempts to interpret its arguments to create an appropriate matroid. The following named arguments are supported: INPUT: - ``groundset`` -- If provided, the groundset of the matroid. If not provided, the function attempts to determine a groundset from the data. - ``bases`` -- The list of bases (maximal independent sets) of the matroid. - ``independent_sets`` -- The list of independent sets of the matroid. - ``circuits`` -- The list of circuits of the matroid. - ``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 ``reduced_matrix = A`` then the matroid is represented by `[I\ \ A]` where `I` is an appropriately sized identity matrix. - ``rank_function`` -- A function that computes the rank of each subset. Can only be provided together with a groundset. - ``circuit_closures`` -- Either a list of tuples ``(k, C)`` with ``C`` the closure of a circuit, and ``k`` the rank of ``C``, or a dictionary ``D`` with ``D[k]`` the set of closures of rank-``k`` circuits. - ``matroid`` -- An object that is already a matroid. Useful only with the ``regular`` option. Up to two unnamed arguments are allowed. - One unnamed argument, no named arguments other than ``regular`` -- the input should be either a graph, or a matrix, or a list of independent sets containing all bases, or a matroid. - Two unnamed arguments: the first is the groundset, the second a graph, or a matrix, or a list of independent sets containing all bases, or a matroid. - One unnamed argument, at least one named argument: the unnamed argument is the groundset, the named argument is as above (but must be different from ``groundset``). The examples section details how each of the input types deals with explicit or implicit groundset arguments. OPTIONS: - ``regular`` -- (default: ``False``) boolean. If ``True``, output a :class:`RegularMatroid <sage.matroids.linear_matroid.RegularMatroid>` instance such that, *if* the input defines a valid regular matroid, then the output represents this matroid. Note that this option can be combined with any type of input. - ``ring`` -- any ring. If provided, and the input is a ``matrix`` or ``reduced_matrix``, output will be a linear matroid over the ring or field ``ring``. - ``field`` -- any field. Same as ``ring``, but only fields are allowed. - ``check`` -- (default: ``True``) boolean. If ``True`` and ``regular`` is true, the output is checked to make sure it is a valid regular matroid. .. WARNING:: Except for regular matroids, the input is not checked for validity. If your data does not correspond to an actual matroid, the behavior of the methods is undefined and may cause strange errors. To ensure you have a matroid, run :meth:`M.is_valid() <sage.matroids.matroid.Matroid.is_valid>`. .. NOTE:: The ``Matroid()`` method will return instances of type :class:`BasisMatroid <sage.matroids.basis_matroid.BasisMatroid>`, :class:`CircuitClosuresMatroid <sage.matroids.circuit_closures_matroid.CircuitClosuresMatroid>`, :class:`LinearMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`BinaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`TernaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`QuaternaryMatroid <sage.matroids.linear_matroid.LinearMatroid>`, :class:`RegularMatroid <sage.matroids.linear_matroid.LinearMatroid>`, or :class:`RankMatroid <sage.matroids.rank_matroid.RankMatroid>`. To import these classes (and other useful functions) directly into Sage's main namespace, type:: sage: from sage.matroids.advanced import * See :mod:`sage.matroids.advanced <sage.matroids.advanced>`. EXAMPLES: Note that in these examples we will often use the fact that strings are iterable in these examples. So we type ``'abcd'`` to denote the list ``['a', 'b', 'c', 'd']``. #. List of bases: All of the following inputs are allowed, and equivalent:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'ac', 'ad', ....: 'bc', 'bd', 'cd']) sage: M2 = Matroid(bases=['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M3 = Matroid(['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M4 = Matroid('abcd', ['ab', 'ac', 'ad', 'bc', 'bd', 'cd']) sage: M5 = Matroid('abcd', bases=[['a', 'b'], ['a', 'c'], ....: ['a', 'd'], ['b', 'c'], ....: ['b', 'd'], ['c', 'd']]) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True sage: M1 == M5 True We do not check if the provided input forms an actual matroid:: sage: M1 = Matroid(groundset='abcd', bases=['ab', 'cd']) sage: M1.full_rank() 2 sage: M1.is_valid() False Bases may be repeated:: sage: M1 = Matroid(['ab', 'ac']) sage: M2 = Matroid(['ab', 'ac', 'ab']) sage: M1 == M2 True #. List of independent sets: :: sage: M1 = Matroid(groundset='abcd', ....: independent_sets=['', 'a', 'b', 'c', 'd', 'ab', ....: 'ac', 'ad', 'bc', 'bd', 'cd']) We only require that the list of independent sets contains each basis of the matroid; omissions of smaller independent sets and repetitions are allowed:: sage: M1 = Matroid(bases=['ab', 'ac']) sage: M2 = Matroid(independent_sets=['a', 'ab', 'b', 'ab', 'a', ....: 'b', 'ac']) sage: M1 == M2 True #. List of circuits: :: 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 <sage.matroids.basis_matroid.BasisMatroid>` internally:: sage: M = Matroid(groundset='abcd', circuits=['abc', 'abd', 'acd', ....: 'bcd']) sage: type(M) <type '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 :meth:`is_valid() <sage.matroids.matroid.Matroid.is_valid>` method. So always check whether your input makes sense! :: sage: M = Matroid('abcd', circuits=['ab', 'acd']) sage: M.is_valid() True sage: [sorted(C) for C in M.circuits()] [['a']] #. Graph: Sage has great support for graphs, see :mod:`sage.graphs.graph`. :: sage: G = graphs.PetersenGraph() sage: Matroid(G) Regular matroid of rank 9 on 15 elements with 2000 bases Note: if a groundset is specified, we assume it is in the same order as :meth:`G.edge_iterator() <sage.graphs.generic_graph.GenericGraph.edge_iterator>` provides:: sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)]) sage: M = Matroid('abcd', G) sage: M.rank(['b', 'c']) 1 If no groundset is provided, we attempt to use the edge labels:: sage: G = Graph([(0, 1, 'a'), (0, 2, 'b'), (1, 2, 'c')]) sage: M = Matroid(G) sage: sorted(M.groundset()) ['a', 'b', 'c'] If no edge labels are present and the graph is simple, we use the tuples ``(i, j)`` of endpoints. If that fails, we simply use a list ``[0..m-1]`` :: sage: G = Graph([(0, 1), (0, 2), (1, 2)]) sage: M = Matroid(G) sage: sorted(M.groundset()) [(0, 1), (0, 2), (1, 2)] sage: G = Graph([(0, 1), (0, 2), (0, 2), (1, 2)]) sage: M = Matroid(G) sage: sorted(M.groundset()) [0, 1, 2, 3] When the ``graph`` keyword is used, a variety of inputs can be converted to a graph automatically. The following uses a graph6 string (see the :class:`Graph <sage.graphs.graph.Graph>` method's documentation):: sage: Matroid(graph=':I`AKGsaOs`cI]Gb~') Regular matroid of rank 9 on 17 elements with 4004 bases However, this method is no more clever than ``Graph()``:: sage: Matroid(graph=41/2) Traceback (most recent call last): ... ValueError: input does not seem to represent a graph. #. Matrix: The basic input is a :mod:`Sage matrix <sage.matrix.constructor>`:: sage: A = Matrix(GF(2), [[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]]) sage: M = Matroid(matrix=A) sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True Various shortcuts are possible:: sage: M1 = Matroid(matrix=[[1, 0, 0, 1, 1, 0], ....: [0, 1, 0, 1, 0, 1], ....: [0, 0, 1, 0, 1, 1]], ring=GF(2)) sage: M2 = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], ring=GF(2)) sage: M3 = Matroid(groundset=[0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: ring=GF(2)) sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M4 = Matroid([0, 1, 2, 3, 4, 5], A) sage: M1 == M2 True sage: M1 == M3 True sage: M1 == M4 True However, with unnamed arguments the input has to be a ``Matrix`` instance, or the function will try to interpret it as a set of bases:: sage: Matroid([0, 1, 2], [[1, 0, 1], [0, 1, 1]]) Traceback (most recent call last): ... ValueError: basis has wrong cardinality. If the groundset size equals number of rows plus number of columns, an identity matrix is prepended. Otherwise the groundset size must equal the number of columns:: sage: A = Matrix(GF(2), [[1, 1, 0], [1, 0, 1], [0, 1, 1]]) sage: M = Matroid([0, 1, 2], A) sage: N = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.rank() 2 sage: N.rank() 3 We automatically create an optimized subclass, if available:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2)) Binary matroid of rank 3 on 6 elements, type (2, 7) sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(3)) Ternary matroid of rank 3 on 6 elements, type 0- sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(4, 'x')) Quaternary matroid of rank 3 on 6 elements sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(2), regular=True) Regular matroid of rank 3 on 6 elements with 16 bases Otherwise the generic LinearMatroid class is used:: sage: Matroid([0, 1, 2, 3, 4, 5], ....: matrix=[[1, 1, 0], [1, 0, 1], [0, 1, 1]], ....: field=GF(83)) Linear matroid of rank 3 on 6 elements represented over the Finite Field of size 83 An integer matrix is automatically converted to a matrix over `\QQ`. If you really want integers, you can specify the ring explicitly:: sage: A = Matrix([[1, 1, 0], [1, 0, 1], [0, 1, -1]]) sage: A.base_ring() Integer Ring sage: M = Matroid([0, 1, 2, 3, 4, 5], A) sage: M.base_ring() Rational Field sage: M = Matroid([0, 1, 2, 3, 4, 5], A, ring=ZZ) sage: M.base_ring() Integer Ring #. Rank function: Any function mapping subsets to integers can be used as input:: sage: def f(X): ....: return min(len(X), 2) ....: sage: M = Matroid('abcd', rank_function=f) sage: M Matroid of rank 2 on 4 elements sage: M.is_isomorphic(matroids.Uniform(2, 4)) True #. Circuit closures: This is often a really concise way to specify a matroid. The usual way is a dictionary of lists:: sage: M = Matroid(circuit_closures={3: ['edfg', 'acdg', 'bcfg', ....: 'cefh', 'afgh', 'abce', 'abdf', 'begh', 'bcdh', 'adeh'], ....: 4: ['abcdefgh']}) sage: M.equals(matroids.named_matroids.P8()) True You can also input tuples `(k, X)` where `X` is the closure of a circuit, and `k` the rank of `X`:: sage: M = Matroid(circuit_closures=[(2, 'abd'), (3, 'abcdef'), ....: (2, 'bce')]) sage: M.equals(matroids.named_matroids.Q6()) True #. Matroid: Most of the time, the matroid itself is returned:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M) sage: N is M True But it can be useful with the ``regular`` option:: sage: M = Matroid(circuit_closures={2:['adb', 'bec', 'cfa', ....: 'def'], 3:['abcdef']}) sage: N = Matroid(M, regular=True) sage: N Regular matroid of rank 3 on 6 elements with 16 bases sage: Matrix(N) [1 0 0 1 1 0] [0 1 0 1 1 1] [0 0 1 0 1 1] The ``regular`` option: :: sage: M = Matroid(reduced_matrix=[[1, 1, 0], ....: [1, 0, 1], ....: [0, 1, 1]], regular=True) sage: M Regular matroid of rank 3 on 6 elements with 16 bases sage: M.is_isomorphic(matroids.CompleteGraphic(4)) True By default we check if the resulting matroid is actually regular. To increase speed, this check can be skipped:: sage: M = matroids.named_matroids.Fano() sage: N = Matroid(M, regular=True) Traceback (most recent call last): ... ValueError: input does not correspond to a valid regular matroid. sage: N = Matroid(M, regular=True, check=False) sage: N Regular matroid of rank 3 on 7 elements with 32 bases sage: N.is_valid() False Sometimes the output is regular, but represents a different matroid from the one you intended:: sage: M = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]])) sage: N = Matroid(Matrix(GF(3), [[1, 0, 1, 1], [0, 1, 1, 2]]), ....: regular=True) sage: N.is_valid() True sage: N.is_isomorphic(M) False """ # These are the valid arguments: inputS = set(['groundset', 'bases', 'independent_sets', 'circuits', 'graph', 'matrix', 'reduced_matrix', 'rank_function', 'circuit_closures', 'matroid']) # process options if 'regular' in kwds: want_regular = kwds['regular'] kwds.pop('regular') else: want_regular = False if 'check' in kwds: want_check = kwds['check'] kwds.pop('check') else: want_check = True base_ring = None have_field = False if 'field' in kwds: base_ring = kwds['field'] kwds.pop('field') have_field = True if 'ring' in kwds: raise ValueError("only one of ring and field can be specified.") try: if not base_ring.is_field(): raise TypeError("specified ``field`` is not a field.") except AttributeError: raise TypeError("specified ``field`` is not a field.") if 'ring' in kwds: base_ring = kwds['ring'] kwds.pop('ring') try: if not base_ring.is_ring(): raise TypeError("specified ``ring`` is not a ring.") except AttributeError: raise TypeError("specified ``ring`` is not a ring.") # Process unnamed arguments if len(args) > 0: if 'groundset' in kwds: raise ValueError('when using unnamed arguments, groundset must be the first unnamed argument or be implicit.') if len(args) > 2: raise ValueError('at most two unnamed arguments are allowed.') if len(args) == 2 or len(kwds) > 0: # First argument should be the groundset kwds['groundset'] = args[0] # Check for unnamed data dataindex = -1 if len(args) == 2: dataindex = 1 if len(args) == 1 and len(kwds) == 0: dataindex = 0 if dataindex > -1: # One unnamed argument, no named arguments if isinstance(args[dataindex], sage.graphs.graph.Graph): kwds['graph'] = args[dataindex] elif isinstance(args[dataindex], sage.matrix.matrix.Matrix): kwds['matrix'] = args[dataindex] elif isinstance(args[dataindex], sage.matroids.matroid.Matroid): kwds['matroid'] = args[dataindex] else: kwds['independent_sets'] = args[dataindex] # Check for multiple types of input if len(set(kwds).difference(inputS)) > 0: raise ValueError("unknown input argument") if ('grondset' in kwds and len(kwds) != 2) or ('groundset' not in kwds and len(kwds) > 1): raise ValueError("only one type of input may be specified.") # Bases: if 'bases' in kwds: if 'groundset' not in kwds: gs = set() for B in kwds['bases']: gs.update(B) kwds['groundset'] = gs M = BasisMatroid(groundset=kwds['groundset'], bases=kwds['bases']) # Independent sets: if 'independent_sets' in kwds: # Convert to list of bases first rk = -1 bases = [] for I in kwds['independent_sets']: if len(I) == rk: bases.append(I) elif len(I) > rk: bases = [I] rk = len(I) if 'groundset' not in kwds: gs = set() for B in bases: gs.update(B) kwds['groundset'] = gs M = BasisMatroid(groundset=kwds['groundset'], bases=bases) # Circuits: if 'circuits' in kwds: # Convert to list of bases first # Determine groundset (note that this cannot detect coloops) if 'groundset' not in kwds: gs = set() for C in kwds['circuits']: gs.update(C) kwds['groundset'] = gs # determine the rank by computing a basis B = set(kwds['groundset']) for C in kwds['circuits']: 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(kwds['groundset'], rk) if not any([frozenset(C).issubset(B) for C in kwds['circuits']])] M = BasisMatroid(groundset=kwds['groundset'], bases=BB) # Graphs: if 'graph' in kwds: # Construct the incidence matrix # NOTE: we are not using Sage's built-in method because # 1) we would need to fix the loops anyway # 2) Sage will sort the columns, making it impossible to keep labels! G = kwds['graph'] if not isinstance(G, sage.graphs.generic_graph.GenericGraph): try: G = Graph(G) except (ValueError, TypeError, NetworkXError): raise ValueError("input does not seem to represent a graph.") V = G.vertices() E = G.edges() n = G.num_verts() m = G.num_edges() A = Matrix(ZZ, n, m, 0) mm = 0 for i, j, k in G.edge_iterator(): A[V.index(i), mm] = -1 A[V.index(j), mm] += 1 # So loops get 0 mm += 1 # Decide on the groundset if 'groundset' not in kwds: # 1. Attempt to use edge labels. sl = G.edge_labels() if len(sl) == len(set(sl)): kwds['groundset'] = sl # 2. If simple, use vertex tuples elif not G.has_multiple_edges(): kwds['groundset'] = [(i, j) for i, j, k in G.edge_iterator()] else: # 3. Use numbers kwds['groundset'] = range(m) M = RegularMatroid(matrix=A, groundset=kwds['groundset']) want_regular = False # Save some time, since result is already regular # Matrices: if 'matrix' in kwds or 'reduced_matrix' in kwds: if 'matrix' in kwds: A = kwds['matrix'] if 'reduced_matrix' in kwds: A = kwds['reduced_matrix'] # Fix the representation if not isinstance(A, sage.matrix.matrix.Matrix): try: if base_ring is not None: A = Matrix(base_ring, A) else: A = Matrix(A) except ValueError: raise ValueError("input does not seem to contain a matrix.") # Fix the ring if base_ring is not None: if A.base_ring() != base_ring: A = A.change_ring(base_ring) elif A.base_ring() == ZZ and not want_regular: # Usually a rational matrix is intended, we presume. A = A.change_ring(QQ) base_ring = QQ else: base_ring = A.base_ring() # Determine groundset: if 'matrix' in kwds: if 'groundset' in kwds: if len(kwds['groundset']) == A.nrows() + A.ncols(): kwds['reduced_matrix'] = A kwds.pop('matrix') else: if len(kwds['groundset']) != A.ncols(): raise ValueError("groundset size does not correspond to matrix size.") else: kwds['groundset'] = range(A.ncols()) if 'reduced_matrix' in kwds: if 'groundset' in kwds: if len(kwds['groundset']) != A.nrows() + A.ncols(): raise ValueError("groundset size does not correspond to matrix size.") else: kwds['groundset'] = range(A.nrows() + A.ncols()) if 'matrix' in kwds: if base_ring == GF(2): M = BinaryMatroid(groundset=kwds['groundset'], matrix=A) elif base_ring == GF(3): M = TernaryMatroid(groundset=kwds['groundset'], matrix=A) elif base_ring.is_field() and base_ring.order() == 4: # GF(4) can have different generators. M = QuaternaryMatroid(groundset=kwds['groundset'], matrix=A) else: M = LinearMatroid(groundset=kwds['groundset'], matrix=A, ring=base_ring) if 'reduced_matrix' in kwds: if A.base_ring() == GF(2): M = BinaryMatroid(groundset=kwds['groundset'], reduced_matrix=A) elif A.base_ring() == GF(3): M = TernaryMatroid(groundset=kwds['groundset'], reduced_matrix=A) elif A.base_ring().is_field() and A.base_ring().order() == 4: # GF(4) can have different generators. M = QuaternaryMatroid(groundset=kwds['groundset'], reduced_matrix=A) else: M = LinearMatroid(groundset=kwds['groundset'], reduced_matrix=A, ring=base_ring) # Rank functions: if 'rank_function' in kwds: if 'groundset' not in kwds: raise ValueError('for rank functions, groundset needs to be specified.') M = RankMatroid(groundset=kwds['groundset'], rank_function=kwds['rank_function']) # Circuit closures: if 'circuit_closures' in kwds: if 'groundset' not in kwds: E = set() if isinstance(kwds['circuit_closures'], dict): for X in kwds['circuit_closures'].itervalues(): for Y in X: E.update(Y) else: for X in kwds['circuit_closures']: E.update(X[1]) else: E = kwds['groundset'] if not isinstance(kwds['circuit_closures'], dict): # Convert to dictionary CC = {} for X in kwds['circuit_closures']: if X[0] not in CC: CC[X[0]] = [] CC[X[0]].append(X[1]) else: CC = kwds['circuit_closures'] M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) # Matroids: if 'matroid' in kwds: M = kwds['matroid'] if not isinstance(M, sage.matroids.matroid.Matroid): raise ValueError("input does not appear to be of Matroid type.") # Regular option: if want_regular: M = sage.matroids.utilities.make_regular_matroid_from_matroid(M) if want_check: if not M.is_valid(): raise ValueError('input does not correspond to a valid regular matroid.') return M
def apply_Up(self,c,group = None,scale = 1,parallelize = False,times = 0,progress_bar = False,method = 'naive', repslocal = None, Up_reps = None, steps = 1): r""" Apply the Up Hecke operator operator to ``c``. """ assert steps >= 1 V = self.coefficient_module() R = V.base_ring() gammas = self.group().gens() if Up_reps is None: Up_reps = self.S_arithgroup().get_Up_reps() if repslocal is None: try: prec = V.base_ring().precision_cap() except AttributeError: prec = None repslocal = self.get_Up_reps_local(prec) i = 0 if method == 'naive': assert times == 0 G = self.S_arithgroup() Gn = G.large_group() if self.use_shapiro(): if self.coefficient_module().trivial_action(): def calculate_Up_contribution(lst, c, i, j): return sum([c.evaluate_and_identity(tt) for sk, tt in lst]) else: def calculate_Up_contribution(lst, c, i, j): return sum([sk * c.evaluate_and_identity(tt) for sk, tt in lst]) input_vec = [] for j, gamma in enumerate(gammas): for i, xi in enumerate(G.coset_reps()): delta = Gn(G.get_coset_ti(set_immutable(xi * gamma.quaternion_rep))[0]) input_vec.append(([(sk, Gn.get_hecke_ti(g,delta)) for sk, g in zip(repslocal, Up_reps)], c, i, j)) vals = [[V.coefficient_module()(0,normalize=False) for xi in G.coset_reps()] for gamma in gammas] if parallelize: for inp, outp in parallel(calculate_Up_contribution)(input_vec): vals[inp[0][-1]][inp[0][-2]] += outp else: for inp in input_vec: outp = calculate_Up_contribution(*inp) vals[inp[-1]][inp[-2]] += outp ans = self([V(o) for o in vals]) else: Gpn = G.small_group() if self.trivial_action(): def calculate_Up_contribution(lst,c,num_gamma): return sum([c.evaluate(tt) for sk, tt in lst], V(0,normalize=False)) else: def calculate_Up_contribution(lst,c,num_gamma,pb_fraction=None): i = 0 ans = V(0, normalize=False) for sk, tt in lst: ans += sk * c.evaluate(tt) update_progress(i * pb_fraction, 'Up action') return ans input_vec = [] for j,gamma in enumerate(gammas): input_vec.append(([(sk, Gpn.get_hecke_ti(g,gamma)) for sk, g in zip(repslocal, Up_reps)], c, j)) vals = [V(0,normalize=False) for gamma in gammas] if parallelize: for inp,outp in parallel(calculate_Up_contribution)(input_vec): vals[inp[0][-1]] += outp else: for counter, inp in enumerate(input_vec): outp = calculate_Up_contribution(*inp, pb_fraction=float(1)/float(len(repslocal) * len(input_vec))) vals[inp[-1]] += outp ans = self(vals) if scale != 1: ans = scale * ans else: assert method == 'bigmatrix' verbose('Getting Up matrices...') try: N = len(V(0)._moments.list()) except AttributeError: N = 1 nreps = len(Up_reps) ngens = len(self.group().gens()) NN = ngens * N A = Matrix(ZZ,NN,NN,0) total_counter = ngens**2 counter = 0 iS = 0 for i,gi in enumerate(self.group().gens()): ti = [tuple(self.group().get_hecke_ti(sk,gi).word_rep) for sk in Up_reps] jS = 0 for ans in find_newans(self,repslocal,ti): A.set_block(iS,jS,ans) jS += N if progress_bar: counter +=1 update_progress(float(counter)/float(total_counter),'Up matrix') iS += N verbose('Computing 2^(%s)-th power of a %s x %s matrix'%(times,A.nrows(),A.ncols())) for i in range(times): A = A**2 if N != 0: A = A.apply_map(lambda x: x % self._pN) update_progress(float(i+1)/float(times),'Exponentiating matrix') verbose('Done computing 2^(%s)-th power'%times) if times > 0: scale_factor = ZZ(scale).powermod(2**times,self._pN) else: scale_factor = ZZ(scale) bvec = Matrix(R,NN,1,[o for b in c._val for o in b._moments.list()]) if scale_factor != 1: bvec = scale_factor * bvec valmat = A * bvec appr_module = V.approx_module(N) ans = self([V(appr_module(valmat.submatrix(row=i,nrows = N).list())) for i in xrange(0,valmat.nrows(),N)]) if steps <= 1: return ans else: return self.apply_Up(ans, group = group,scale = scale,parallelize = parallelize,times = times,progress_bar = progress_bar,method = method, repslocal = repslocal, steps = steps -1)