diff --git a/src/sage/matroids/advanced.py b/src/sage/matroids/advanced.py index 15c25afadde..6762af2d818 100644 --- a/src/sage/matroids/advanced.py +++ b/src/sage/matroids/advanced.py @@ -52,7 +52,7 @@ from rank_matroid import RankMatroid from circuit_closures_matroid import CircuitClosuresMatroid from basis_matroid import BasisMatroid -from linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid, lift_cross_ratios -from utilities import setprint, newlabel, get_nonisomorphic_matroids +from linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid +from utilities import setprint, newlabel, get_nonisomorphic_matroids, lift_cross_ratios import lean_matrix from extension import LinearSubclasses, MatroidExtensions diff --git a/src/sage/matroids/linear_matroid.pyx b/src/sage/matroids/linear_matroid.pyx index a1e4e1d7578..052df5b29fc 100644 --- a/src/sage/matroids/linear_matroid.pyx +++ b/src/sage/matroids/linear_matroid.pyx @@ -114,7 +114,7 @@ from sage.matroids.matroid cimport Matroid from basis_exchange_matroid cimport BasisExchangeMatroid from lean_matrix cimport LeanMatrix, GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, IntegerMatrix, generic_identity from set_system cimport SetSystem -from utilities import newlabel +from utilities import newlabel, lift_cross_ratios from sage.matrix.matrix2 cimport Matrix import sage.matrix.constructor @@ -2700,169 +2700,6 @@ cdef class LinearMatroid(BasisExchangeMatroid): data = (A, gs, reduced, getattr(self, '__custom_name')) return sage.matroids.unpickling.unpickle_linear_matroid, (version, data) -def lift_cross_ratios(A, lift_map = None): - """ - Return a matrix which arises from the given matrix by lifting cross ratios. - - INPUT: - - - ``A`` -- a matrix over a ring ``source_ring``. - - ``lift_map`` -- a python dictionary, mapping each cross ratio of ``A`` to some element - of a target ring, and such that ``lift_map[source_ring(1)] = target_ring(1)``. - - OUTPUT: - - - ``Z`` -- a matrix over the ring ``target_ring``. - - The intended use of this method is to create a (reduced) matrix representation of a - matroid ``M`` over a ring ``target_ring``, given a (reduced) matrix representation of - ``A`` of ``M`` over a ring ``source_ring`` and a map ``lift_map`` from ``source_ring`` - to ``target_ring``. - - This method will create a unique candidate representation ``Z``, but will not verify - if ``Z`` is indeed a representation of ``M``. However, this is guaranteed if the - conditions of the lift theorem hold for the lift_map in combination with the matrix - ``A``. These conditions that all cross ratios of ``A`` as well as ``1`` are keys of - ``lift_map``, and - - - if ``x, y`` are keys of ``lift_map``, and ``x+y == 1`` then - ``lift_map[x] + lift_map[y] = lift_map[1]`` - - if ``x, y, z`` are keys of ``lift_map``, and ``x*y == z`` then - ``lift_map[x]*lift_map[y] = lift_map[z]`` - - if ``x, y`` are keys of ``lift_map``, and ``x*y`` is not, then there is no key of - ``lift_map`` such that ``lift_map[x]*lift_map[y] = lift_map[z]`` - Finally, there are global conditions on the target field which depend on the matroid - ``M`` represented by ``[ I A ]``. If ``M`` has a Fano minor, then in the target ring we - must have ``1+1 == 0``. If ``M`` has a NonFano minor, then in the target ring we must - have ``1+1 != 0``. - - EXAMPLES:: - - sage: from sage.matroids.advanced import lift_cross_ratios, LinearMatroid - sage: R = GF(7) - sage: z = QQ['z'].0 - sage: S = NumberField(z^2-z+1, 'z') - sage: to_sixth_root_of_unity = { R(1): S(1), R(3): S(z), R(3)**(-1): S(z)**(-1)} - sage: A = Matrix(R, [[1, 0, 6, 1, 2],[6, 1, 0, 0, 1],[0, 6, 3, 6, 0]]) - sage: A - [1 0 6 1 2] - [6 1 0 0 1] - [0 6 3 6 0] - sage: Z = lift_cross_ratios(A, to_sixth_root_of_unity) - sage: Z - [ 1 0 1 1 1] - [ 1 1 0 0 z] - [ 0 1 -z -1 0] - sage: M = LinearMatroid(reduced_matrix = A) - sage: sorted(M.cross_ratios()) - [3, 5] - sage: N = LinearMatroid(reduced_matrix = Z) - sage: sorted(N.cross_ratios()) - [-z + 1, z] - sage: M.is_isomorphism(N, {e:e for e in M.groundset()}) - True - - """ - - for s,t in lift_map.iteritems(): - source_ring = s.parent() - target_ring = t.parent() - break - plus_one1 = source_ring(1) - minus_one1 = source_ring(-1) - plus_one2 = target_ring(1) - minus_one2 = target_ring(-1) - - G = sage.graphs.graph.Graph([((r,0),(c,1),(r,c)) for r,c in A.nonzero_positions()]) - - # write the entries of (a scaled version of) A as products of cross ratios of A - T = G.min_spanning_tree() - # - fix a tree of the support graph G to units (= empty dict, product of 0 terms) - F = {entry[2]: dict() for entry in T} - W = set(G.edges()) - set(T) - H = G.subgraph(edges = T) - while W: - # - find an edge in W to process, closing a circuit in H which is induced in G - edge = W.pop() - path = H.shortest_path(edge[0], edge[1]) - retry = True - while retry: - retry = False - for edge2 in W: - if edge2[0] in path and edge2[1] in path: - W.add(edge) - edge = edge2 - W.remove(edge) - path = H.shortest_path(edge[0], edge[1]) - retry = True - break - entry = edge[2] - entries = [] - for i in range(len(path) - 1): - v = path[i] - w = path[i+1] - if v[1] == 0: - entries.append((v[0],w[0])) - else: - entries.append((w[0],v[0])) - # - compute the cross ratio `cr` of this whirl - cr = A[entry] - div = True - for entry2 in entries: - if div: - cr = cr/A[entry2] - else: - cr = cr* A[entry2] - div = not div - - monomial = dict() - if len(path) % 4 == 0: - if not cr == plus_one1: - monomial[cr] = 1 - else: - cr = -cr - if not cr ==plus_one1: - monomial[cr] = 1 - if monomial.has_key(minus_one1): - monomial[minus_one1] = monomial[minus_one1] + 1 - else: - monomial[minus_one1] = 1 - - if cr != plus_one1 and not cr in lift_map: - raise ValueError("Input matrix has a cross ratio "+str(cr)+", which is not in the lift_map") - # - write the entry as a product of cross ratios of A - div = True - for entry2 in entries: - if div: - for cr, degree in F[entry2].iteritems(): - if monomial.has_key(cr): - monomial[cr] = monomial[cr]+ degree - else: - monomial[cr] = degree - else: - for cr, degree in F[entry2].iteritems(): - if monomial.has_key(cr): - monomial[cr] = monomial[cr] - degree - else: - monomial[cr] = -degree - div = not div - F[entry] = monomial - # - current edge is done, can be used in next iteration - H.add_edge(edge) - - # compute each entry of Z as the product of lifted cross ratios - Z = sage.matrix.constructor.Matrix(target_ring, A.nrows(), A.ncols()) - for entry, monomial in F.iteritems(): - Z[entry] = plus_one2 - for cr,degree in monomial.iteritems(): - if cr == minus_one1: - Z[entry] = Z[entry] * (minus_one2**degree) - else: - Z[entry] = Z[entry] * (lift_map[cr]**degree) - - return Z - - # Binary matroid cdef class BinaryMatroid(LinearMatroid): diff --git a/src/sage/matroids/utilities.py b/src/sage/matroids/utilities.py index eab0bc96873..194cba45e06 100644 --- a/src/sage/matroids/utilities.py +++ b/src/sage/matroids/utilities.py @@ -354,3 +354,189 @@ def get_nonisomorphic_matroids(MSet): if not seen: OutSet.append(M) return OutSet + + +# Partial fields and lifting + +def lift_cross_ratios(A, lift_map = None): + """ + Return a matrix which arises from the given matrix by lifting cross ratios. + + INPUT: + + - ``A`` -- a matrix over a ring ``source_ring``. + - ``lift_map`` -- a python dictionary, mapping each cross ratio of ``A`` to some element + of a target ring, and such that ``lift_map[source_ring(1)] = target_ring(1)``. + + OUTPUT: + + - ``Z`` -- a matrix over the ring ``target_ring``. + + The intended use of this method is to create a (reduced) matrix representation of a + matroid ``M`` over a ring ``target_ring``, given a (reduced) matrix representation of + ``A`` of ``M`` over a ring ``source_ring`` and a map ``lift_map`` from ``source_ring`` + to ``target_ring``. + + This method will create a unique candidate representation ``Z``, but will not verify + if ``Z`` is indeed a representation of ``M``. However, this is guaranteed if the + conditions of the lift theorem hold for the lift_map in combination with the matrix + ``A``. These conditions that all cross ratios of ``A`` as well as ``1`` are keys of + ``lift_map``, and + + - if ``x, y`` are keys of ``lift_map``, and ``x+y == 1`` then + ``lift_map[x] + lift_map[y] = lift_map[1]`` + - if ``x, y, z`` are keys of ``lift_map``, and ``x*y == z`` then + ``lift_map[x]*lift_map[y] = lift_map[z]`` + - if ``x, y`` are keys of ``lift_map``, and ``x*y`` is not, then there is no key ``z`` + of ``lift_map`` such that ``lift_map[x]*lift_map[y] = lift_map[z]`` + Finally, there are global conditions on the target ring which depend on the matroid + ``M`` represented by ``[ I A ]``. If ``M`` has a Fano minor, then in the target ring we + must have ``1+1 == 0``. If ``M`` has a NonFano minor, then in the target ring we must + have ``1+1 != 0``. + + EXAMPLES:: + + sage: from sage.matroids.advanced import lift_cross_ratios, LinearMatroid + sage: R = GF(7) + sage: z = QQ['z'].0 + sage: S = NumberField(z^2-z+1, 'z') + sage: to_sixth_root_of_unity = { R(1): S(1), R(3): S(z), R(3)**(-1): S(z)**(-1)} + sage: A = Matrix(R, [[1, 0, 6, 1, 2],[6, 1, 0, 0, 1],[0, 6, 3, 6, 0]]) + sage: A + [1 0 6 1 2] + [6 1 0 0 1] + [0 6 3 6 0] + sage: Z = lift_cross_ratios(A, to_sixth_root_of_unity) + sage: Z + [ 1 0 1 1 1] + [ 1 1 0 0 z] + [ 0 1 -z -1 0] + sage: M = LinearMatroid(reduced_matrix = A) + sage: sorted(M.cross_ratios()) + [3, 5] + sage: N = LinearMatroid(reduced_matrix = Z) + sage: sorted(N.cross_ratios()) + [-z + 1, z] + sage: M.is_isomorphism(N, {e:e for e in M.groundset()}) + True + + """ + + for s,t in lift_map.iteritems(): + source_ring = s.parent() + target_ring = t.parent() + break + plus_one1 = source_ring(1) + minus_one1 = source_ring(-1) + plus_one2 = target_ring(1) + minus_one2 = target_ring(-1) + + G = sage.graphs.graph.Graph([((r,0),(c,1),(r,c)) for r,c in A.nonzero_positions()]) + + # write the entries of (a scaled version of) A as products of cross ratios of A + T = G.min_spanning_tree() + # - fix a tree of the support graph G to units (= empty dict, product of 0 terms) + F = {entry[2]: dict() for entry in T} + W = set(G.edges()) - set(T) + H = G.subgraph(edges = T) + while W: + # - find an edge in W to process, closing a circuit in H which is induced in G + edge = W.pop() + path = H.shortest_path(edge[0], edge[1]) + retry = True + while retry: + retry = False + for edge2 in W: + if edge2[0] in path and edge2[1] in path: + W.add(edge) + edge = edge2 + W.remove(edge) + path = H.shortest_path(edge[0], edge[1]) + retry = True + break + entry = edge[2] + entries = [] + for i in range(len(path) - 1): + v = path[i] + w = path[i+1] + if v[1] == 0: + entries.append((v[0],w[0])) + else: + entries.append((w[0],v[0])) + # - compute the cross ratio `cr` of this whirl + cr = A[entry] + div = True + for entry2 in entries: + if div: + cr = cr/A[entry2] + else: + cr = cr* A[entry2] + div = not div + + monomial = dict() + if len(path) % 4 == 0: + if not cr == plus_one1: + monomial[cr] = 1 + else: + cr = -cr + if not cr ==plus_one1: + monomial[cr] = 1 + if monomial.has_key(minus_one1): + monomial[minus_one1] = monomial[minus_one1] + 1 + else: + monomial[minus_one1] = 1 + + if cr != plus_one1 and not cr in lift_map: + raise ValueError("Input matrix has a cross ratio "+str(cr)+", which is not in the lift_map") + # - write the entry as a product of cross ratios of A + div = True + for entry2 in entries: + if div: + for cr, degree in F[entry2].iteritems(): + if monomial.has_key(cr): + monomial[cr] = monomial[cr]+ degree + else: + monomial[cr] = degree + else: + for cr, degree in F[entry2].iteritems(): + if monomial.has_key(cr): + monomial[cr] = monomial[cr] - degree + else: + monomial[cr] = -degree + div = not div + F[entry] = monomial + # - current edge is done, can be used in next iteration + H.add_edge(edge) + + # compute each entry of Z as the product of lifted cross ratios + Z = sage.matrix.constructor.Matrix(target_ring, A.nrows(), A.ncols()) + for entry, monomial in F.iteritems(): + Z[entry] = plus_one2 + for cr,degree in monomial.iteritems(): + if cr == minus_one1: + Z[entry] = Z[entry] * (minus_one2**degree) + else: + Z[entry] = Z[entry] * (lift_map[cr]**degree) + + return Z + +def to_sixth_root_of_unity(): + R = GF(7) + z = QQ['z'].gen() + S = NumberField(z^2-z+1, 'z') + return { R(1): S(1), R(3): S(z), R(3)**(-1): S(z)**(-1)} + +def to_dyadic(): + R = GF(11) + return {R(1):QQ(1), R(-1):QQ(-1), R(2):QQ(2), R(6): QQ(1/2)} + +def to_golden_mean(): + R = GF(19) + t = QQ['t'].gen() + G = NumberField(t^2-t-1, 't') + return { R(1): G(1), R(5): G(t), R(1)/R(5): G(1)/G(t), R(-5): G(-t), + R(5)**(-1): G(t)**(-1), R(5)**2: G(t)**2, R(5)**(-2)): G(t)**(-2) } + + + + \ No newline at end of file