def __init__(self, parent, A, b=0, convert=True, check=True): r""" Create element of an affine group. TESTS:: sage: G = AffineGroup(4, GF(5)) sage: g = G.random_element() sage: TestSuite(g).run() """ try: A = A.matrix() except AttributeError: pass if is_Matrix(A) and A.nrows() == A.ncols() == parent.degree()+1: g = A d = parent.degree() A = g.submatrix(0, 0, d, d) b = [ g[i,d] for i in range(d) ] convert = True if convert: A = parent.matrix_space()(A) b = parent.vector_space()(b) if check: # Note: the coercion framework expects that we raise TypeError for invalid input if not is_Matrix(A): raise TypeError('A must be a matrix') if not (A.parent() is parent.matrix_space()): raise TypeError('A must be an element of '+str(parent.matrix_space())) if not (b.parent() is parent.vector_space()): raise TypeError('b must be an element of '+str(parent.vector_space())) parent._element_constructor_check(A, b) super(AffineGroupElement, self).__init__(parent) self._A = A self._b = b
def __classcall_private__(self, arg0, arg1=None, names=None): """ Choose the correct parent based upon input. TESTS: We check arguments with passing in an associative algebra:: sage: cat = Algebras(QQ).WithBasis().FiniteDimensional() sage: C = CombinatorialFreeModule(QQ, ['x','y','z'], category=cat) sage: J1 = JordanAlgebra(C, names=['a','b','c']) sage: J2.<a,b,c> = JordanAlgebra(C) sage: J1 is J2 True We check with passing in a symmetric bilinear form:: sage: m = matrix([[0,1],[1,1]]) sage: J1 = JordanAlgebra(m) sage: J2 = JordanAlgebra(QQ, m) sage: J3 = JordanAlgebra(m, QQ) sage: J1 is J2 False sage: J2 is J3 True sage: J4 = JordanAlgebra(ZZ, m) sage: J1 is J4 True sage: m = matrix(QQ, [[0,1],[1,1]]) sage: J1 = JordanAlgebra(m) sage: J1 is J2 True """ if names is not None: if isinstance(names, str): names = names.split(',') names = tuple(names) if arg1 is None: if not is_Matrix(arg0): if arg0.base_ring().characteristic() == 2: raise ValueError( "the base ring cannot have characteristic 2") return SpecialJordanAlgebra(arg0, names) arg0, arg1 = arg0.base_ring(), arg0 elif is_Matrix(arg0): arg0, arg1 = arg1, arg0 # arg0 is the base ring and arg1 is a matrix if not arg1.is_symmetric(): raise ValueError("the bilinear form is not symmetric") arg1 = arg1.change_ring(arg0) # This makes a copy arg1.set_immutable() return JordanAlgebraSymmetricBilinear(arg0, arg1, names=names)
def __classcall_private__(self, arg0, arg1=None, names=None): """ Choose the correct parent based upon input. TESTS: We check arguments with passing in an associative algebra:: sage: cat = Algebras(QQ).WithBasis().FiniteDimensional() sage: C = CombinatorialFreeModule(QQ, ['x','y','z'], category=cat) sage: J1 = JordanAlgebra(C, names=['a','b','c']) sage: J2.<a,b,c> = JordanAlgebra(C) sage: J1 is J2 True We check with passing in a symmetric bilinear form:: sage: m = matrix([[0,1],[1,1]]) sage: J1 = JordanAlgebra(m) sage: J2 = JordanAlgebra(QQ, m) sage: J3 = JordanAlgebra(m, QQ) sage: J1 is J2 False sage: J2 is J3 True sage: J4 = JordanAlgebra(ZZ, m) sage: J1 is J4 True sage: m = matrix(QQ, [[0,1],[1,1]]) sage: J1 = JordanAlgebra(m) sage: J1 is J2 True """ if names is not None: if isinstance(names, str): names = names.split(',') names = tuple(names) if arg1 is None: if not is_Matrix(arg0): if arg0.base_ring().characteristic() == 2: raise ValueError("the base ring cannot have characteristic 2") return SpecialJordanAlgebra(arg0, names) arg0, arg1 = arg0.base_ring(), arg0 elif is_Matrix(arg0): arg0, arg1 = arg1, arg0 # arg0 is the base ring and arg1 is a matrix if not arg1.is_symmetric(): raise ValueError("the bilinear form is not symmetric") arg1 = arg1.change_ring(arg0) # This makes a copy arg1.set_immutable() return JordanAlgebraSymmetricBilinear(arg0, arg1, names=names)
def sudoku(m): r""" Solves Sudoku puzzles described by matrices. INPUT: - ``m`` - a square Sage matrix over `\ZZ`, where zeros are blank entries OUTPUT: A Sage matrix over `\ZZ` containing the first solution found, otherwise ``None``. This function matches the behavior of the prior Sudoku solver and is included only to replicate that behavior. It could be safely deprecated, since all of its functionality is included in the :class:`~sage.games.sudoku.Sudoku` class. EXAMPLES: An example that was used in previous doctests. :: sage: A = matrix(ZZ,9,[5,0,0, 0,8,0, 0,4,9, 0,0,0, 5,0,0, 0,3,0, 0,6,7, 3,0,0, 0,0,1, 1,5,0, 0,0,0, 0,0,0, 0,0,0, 2,0,8, 0,0,0, 0,0,0, 0,0,0, 0,1,8, 7,0,0, 0,0,4, 1,5,0, 0,3,0, 0,0,2, 0,0,0, 4,9,0, 0,5,0, 0,0,3]) sage: A [5 0 0 0 8 0 0 4 9] [0 0 0 5 0 0 0 3 0] [0 6 7 3 0 0 0 0 1] [1 5 0 0 0 0 0 0 0] [0 0 0 2 0 8 0 0 0] [0 0 0 0 0 0 0 1 8] [7 0 0 0 0 4 1 5 0] [0 3 0 0 0 2 0 0 0] [4 9 0 0 5 0 0 0 3] sage: sudoku(A) [5 1 3 6 8 7 2 4 9] [8 4 9 5 2 1 6 3 7] [2 6 7 3 4 9 5 8 1] [1 5 8 4 6 3 9 7 2] [9 7 4 2 1 8 3 6 5] [3 2 6 7 9 5 4 1 8] [7 8 2 9 3 4 1 5 6] [6 3 5 1 7 2 8 9 4] [4 9 1 8 5 6 7 2 3] Using inputs that are possible with the :class:`~sage.games.sudoku.Sudoku` class, other than a matrix, will cause an error. :: sage: sudoku('.4..32....14..3.') Traceback (most recent call last): ... ValueError: sudoku function expects puzzle to be a matrix, perhaps use the Sudoku class """ from sage.structure.element import is_Matrix if not is_Matrix(m): raise ValueError( 'sudoku function expects puzzle to be a matrix, perhaps use the Sudoku class' ) solution = next(Sudoku(m).solve(algorithm='dlx')) return (solution.to_matrix() if solution else None)
def from_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): r""" Fill ``G`` with the data of an incidence matrix. INPUT: - ``G`` -- a graph - ``M`` -- an incidence matrix - ``loops``, ``multiedges``, ``weighted`` -- booleans (default: ``False``); whether to consider the graph as having loops, multiple edges, or weights EXAMPLES:: sage: from sage.graphs.graph_input import from_incidence_matrix sage: g = Graph() sage: from_incidence_matrix(g, graphs.PetersenGraph().incidence_matrix()) sage: g.is_isomorphic(graphs.PetersenGraph()) True """ from sage.structure.element import is_Matrix assert is_Matrix(M) oriented = any(M[pos] < 0 for pos in M.nonzero_positions(copy=False)) positions = [] for i in range(M.ncols()): NZ = M.nonzero_positions_in_column(i) if len(NZ) == 1: if oriented: raise ValueError( "column {} of the (oriented) incidence " "matrix contains only one nonzero value".format(i)) elif M[NZ[0], i] != 2: raise ValueError( "each column of a non-oriented incidence " "matrix must sum to 2, but column {} does not".format(i)) if loops is None: loops = True positions.append((NZ[0], NZ[0])) elif len(NZ) != 2 or \ (oriented and not ((M[NZ[0], i] == +1 and M[NZ[1], i] == -1) or \ (M[NZ[0], i] == -1 and M[NZ[1], i] == +1))) or \ (not oriented and (M[NZ[0], i] != 1 or M[NZ[1], i] != 1)): msg = "there must be one or two nonzero entries per column in an incidence matrix, " msg += "got entries {} in column {}".format([M[j, i] for j in NZ], i) raise ValueError(msg) else: positions.append(tuple(NZ)) if weighted is None: G._weighted = False if multiedges is None: total = len(positions) multiedges = len(set(positions)) < total G.allow_loops(False if loops is None else loops, check=False) G.allow_multiple_edges(multiedges, check=False) G.add_vertices(range(M.nrows())) G.add_edges(positions)
def __call__(self, f, check=True, unitary=True): """ Construct a homomorphism. .. TODO:: Implement taking generator images and converting them to a matrix. EXAMPLES:: sage: A = FiniteDimensionalAlgebra(QQ, [Matrix([1])]) sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: H = Hom(A, B) sage: H(Matrix([[1, 0]])) Morphism from Finite-dimensional algebra of degree 1 over Rational Field to Finite-dimensional algebra of degree 2 over Rational Field given by matrix [1 0] """ if isinstance(f, FiniteDimensionalAlgebraMorphism): if f.parent() is self: return f if f.parent() == self: return FiniteDimensionalAlgebraMorphism(self, f._matrix, check, unitary) elif is_Matrix(f): return FiniteDimensionalAlgebraMorphism(self, f, check, unitary) try: from sage.matrix.constructor import Matrix return FiniteDimensionalAlgebraMorphism(self, Matrix(f), check, unitary) except Exception: return RingHomset_generic.__call__(self, f, check)
def __call__(self, f, check=True, unitary=True): """ Construct a homomorphism. .. TODO:: Implement taking generator images and converting them to a matrix. EXAMPLES:: sage: A = FiniteDimensionalAlgebra(QQ, [Matrix([1])]) sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: H = Hom(A, B) sage: H(Matrix([[1, 0]])) Morphism from Finite-dimensional algebra of degree 1 over Rational Field to Finite-dimensional algebra of degree 2 over Rational Field given by matrix [1 0] """ if isinstance(f, FiniteDimensionalAlgebraMorphism): if f.parent() is self: return f if f.parent() == self: return FiniteDimensionalAlgebraMorphism( self, f._matrix, check, unitary) elif is_Matrix(f): return FiniteDimensionalAlgebraMorphism(self, f, check, unitary) try: from sage.matrix.constructor import Matrix return FiniteDimensionalAlgebraMorphism(self, Matrix(f), check, unitary) except Exception: return RingHomset_generic.__call__(self, f, check)
def __rmul__(self,matrix): r""" EXAMPLES:: sage: from flatsurf import * sage: s=translation_surfaces.infinite_staircase() sage: s.underlying_surface() The infinite staircase sage: m=Matrix([[1,2],[0,1]]) sage: s2=m*s sage: TestSuite(s2).run(skip='_test_pickling') sage: s2.polygon(0) Polygon: (0, 0), (1, 0), (3, 1), (2, 1) Testing multiplication by a matrix with negative determinant:: sage: from flatsurf import * sage: ds1 = dilation_surfaces.genus_two_square(1/2, 1/3, 1/4, 1/5) sage: ds1.polygon(0) Polygon: (0, 0), (1/2, 0), (1, 1/3), (1, 1), (3/4, 1), (0, 4/5) sage: m = matrix(QQ, [[0, 1], [1, 0]]) # maps (x,y) to (y, x) sage: ds2 = m*ds1 sage: ds2.polygon(0) Polygon: (0, 0), (4/5, 0), (1, 3/4), (1, 1), (1/3, 1), (0, 1/2) """ if not is_Matrix(matrix): raise NotImplementedError("Only implemented for matrices.") if not matrix.dimensions!=(2,2): raise NotImplementedError("Only implemented for 2x2 matrices.") return self.__class__(GL2RImageSurface(self,matrix)).copy()
def __rmul__(self, other): r""" Implement the action of matrices on points of hyperbolic space. EXAMPLES:: sage: A = matrix(2, [0, 1, 1, 0]) sage: A = HyperbolicPlane().UHP().get_isometry(A) sage: A * HyperbolicPlane().UHP().get_point(2 + I) Point in UHP 1/5*I + 2/5 We also lift matrices into isometries:: sage: B = diagonal_matrix([-1, -1, 1]) sage: B = HyperbolicPlane().HM().get_isometry(B) sage: B * HyperbolicPlane().HM().get_point((0, 1, sqrt(2))) Point in HM (0, -1, sqrt(2)) """ if isinstance(other, HyperbolicIsometry): return other(self) elif is_Matrix(other): # TODO: Currently the __mul__ from the matrices gets called first # and returns an error instead of calling this method A = self.parent().get_isometry(other) return A(self) else: raise TypeError("unsupported operand type(s) for *:" "{0} and {1}".format(self, other))
def __init__(self, A, gens=None, given_by_matrix=False): """ EXAMPLES:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: I = A.ideal(A([0,1])) sage: TestSuite(I).run(skip="_test_category") # Currently ideals are not using the category framework """ k = A.base_ring() n = A.degree() if given_by_matrix: self._basis_matrix = gens gens = gens.rows() elif gens is None: self._basis_matrix = Matrix(k, 0, n) elif isinstance(gens, (list, tuple)): B = [ FiniteDimensionalAlgebraIdeal(A, x).basis_matrix() for x in gens ] B = reduce(lambda x, y: x.stack(y), B, Matrix(k, 0, n)) self._basis_matrix = B.echelon_form().image().basis_matrix() elif is_Matrix(gens): gens = FiniteDimensionalAlgebraElement(A, gens) elif isinstance(gens, FiniteDimensionalAlgebraElement): gens = gens.vector() B = Matrix([(gens * b).list() for b in A.table()]) self._basis_matrix = B.echelon_form().image().basis_matrix() Ideal_generic.__init__(self, A, gens)
def __init__(self, A, gens=None, given_by_matrix=False): """ EXAMPLES:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: I = A.ideal(A([0,1])) sage: TestSuite(I).run(skip="_test_category") # Currently ideals are not using the category framework """ k = A.base_ring() n = A.degree() if given_by_matrix: self._basis_matrix = gens gens = gens.rows() elif gens is None: self._basis_matrix = Matrix(k, 0, n) elif isinstance(gens, (list, tuple)): B = [FiniteDimensionalAlgebraIdeal(A, x).basis_matrix() for x in gens] B = reduce(lambda x, y: x.stack(y), B, Matrix(k, 0, n)) self._basis_matrix = B.echelon_form().image().basis_matrix() elif is_Matrix(gens): gens = FiniteDimensionalAlgebraElement(A, gens) elif isinstance(gens, FiniteDimensionalAlgebraElement): gens = gens.vector() B = Matrix([(gens * b).list() for b in A.table()]) self._basis_matrix = B.echelon_form().image().basis_matrix() Ideal_generic.__init__(self, A, gens)
def normalize_square_matrices(matrices): """ Find a common space for all matrices. OUTPUT: A list of matrices, all elements of the same matrix space. EXAMPLES:: sage: from sage.groups.matrix_gps.finitely_generated import normalize_square_matrices sage: m1 = [[1,2],[3,4]] sage: m2 = [2, 3, 4, 5] sage: m3 = matrix(QQ, [[1/2,1/3],[1/4,1/5]]) sage: m4 = MatrixGroup(m3).gen(0) sage: normalize_square_matrices([m1, m2, m3, m4]) [ [1 2] [2 3] [1/2 1/3] [1/2 1/3] [3 4], [4 5], [1/4 1/5], [1/4 1/5] ] """ deg = [] gens = [] for m in matrices: if is_MatrixGroupElement(m): deg.append(m.parent().degree()) gens.append(m.matrix()) continue if is_Matrix(m): if not m.is_square(): raise TypeError('matrix must be square') deg.append(m.ncols()) gens.append(m) continue try: m = list(m) except TypeError: gens.append(m) continue if isinstance(m[0], (list, tuple)): m = [list(_) for _ in m] degree = ZZ(len(m)) else: degree, rem = ZZ(len(m)).sqrtrem() if rem != 0: raise ValueError( 'list of plain numbers must have square integer length') deg.append(degree) gens.append(matrix(degree, degree, m)) deg = set(deg) if len(set(deg)) != 1: raise ValueError('not all matrices have the same size') gens = Sequence(gens, immutable=True) MS = gens.universe() if not is_MatrixSpace(MS): raise TypeError('all generators must be matrices') if MS.nrows() != MS.ncols(): raise ValueError('matrices must be square') return gens
def sudoku(m): r""" Solves Sudoku puzzles described by matrices. INPUT: - ``m`` - a square Sage matrix over `\ZZ`, where zeros are blank entries OUTPUT: A Sage matrix over `\ZZ` containing the first solution found, otherwise ``None``. This function matches the behavior of the prior Sudoku solver and is included only to replicate that behavior. It could be safely deprecated, since all of its functionality is included in the :class:`~sage.games.sudoku.Sudoku` class. EXAMPLES: An example that was used in previous doctests. :: sage: A = matrix(ZZ,9,[5,0,0, 0,8,0, 0,4,9, 0,0,0, 5,0,0, 0,3,0, 0,6,7, 3,0,0, 0,0,1, 1,5,0, 0,0,0, 0,0,0, 0,0,0, 2,0,8, 0,0,0, 0,0,0, 0,0,0, 0,1,8, 7,0,0, 0,0,4, 1,5,0, 0,3,0, 0,0,2, 0,0,0, 4,9,0, 0,5,0, 0,0,3]) sage: A [5 0 0 0 8 0 0 4 9] [0 0 0 5 0 0 0 3 0] [0 6 7 3 0 0 0 0 1] [1 5 0 0 0 0 0 0 0] [0 0 0 2 0 8 0 0 0] [0 0 0 0 0 0 0 1 8] [7 0 0 0 0 4 1 5 0] [0 3 0 0 0 2 0 0 0] [4 9 0 0 5 0 0 0 3] sage: sudoku(A) [5 1 3 6 8 7 2 4 9] [8 4 9 5 2 1 6 3 7] [2 6 7 3 4 9 5 8 1] [1 5 8 4 6 3 9 7 2] [9 7 4 2 1 8 3 6 5] [3 2 6 7 9 5 4 1 8] [7 8 2 9 3 4 1 5 6] [6 3 5 1 7 2 8 9 4] [4 9 1 8 5 6 7 2 3] Using inputs that are possible with the :class:`~sage.games.sudoku.Sudoku` class, other than a matrix, will cause an error. :: sage: sudoku('.4..32....14..3.') Traceback (most recent call last): ... ValueError: sudoku function expects puzzle to be a matrix, perhaps use the Sudoku class """ from sage.structure.element import is_Matrix if not is_Matrix(m): raise ValueError('sudoku function expects puzzle to be a matrix, perhaps use the Sudoku class') solution = next(Sudoku(m).solve(algorithm='dlx')) return (solution.to_matrix() if solution else None)
def __init__(self, parent, matrix, inhomogeneous, is_zero=lambda p: False, relations=[]): ## Checking the input of matrix and vector if (not SAGE_element.is_Matrix(matrix)): matrix = Matrix(matrix) if (not SAGE_element.is_Vector(inhomogeneous)): inhomogeneous = vector(inhomogeneous) if (isinstance(parent, Wrap_w_Sequence_Ring)): parent = parent.base() ## Building the parent of the matrix and the vector pmatrix = matrix.parent().base() pinhom = inhomogeneous.parent().base() ## Setting the variables for the matrix, the parent and the vector self.__parent = pushout(pmatrix, pinhom) self.__matrix = matrix.change_ring(self.__parent) self.__inhomogeneous = inhomogeneous.change_ring(self.__parent) ## Setting the parent for the solutions if (not pushout(parent, self.__parent) == parent): try: self.__matrix = self.__matrix.change_ring(parent) self.__inhomogeneous = self.__inhomogeneous.change_ring(parent) self.__parent = parent except: raise TypeError( "The parent for the solutions must be an extension of the parent for the input" ) self.__solution_parent = parent ## Setting other variables if (relations is None): relations = [] self.__relations = [self.__parent(el) for el in relations] try: self.__gb = ideal(self.parent(), self.__relations).groebner_basis() except AttributeError: try: self.__gb = [ideal(self.parent(), self.__relations).gen()] except: self.__gb = [0] self.__is_zero = is_zero ## Creating the variables for the echelon form self.__echelon = None self.__transformation = None ## Creating the variables for the solutions self.__solution = None self.__syzygy = None
def normalize_square_matrices(matrices): """ Find a common space for all matrices. OUTPUT: A list of matrices, all elements of the same matrix space. EXAMPLES:: sage: from sage.groups.matrix_gps.finitely_generated import normalize_square_matrices sage: m1 = [[1,2],[3,4]] sage: m2 = [2, 3, 4, 5] sage: m3 = matrix(QQ, [[1/2,1/3],[1/4,1/5]]) sage: m4 = MatrixGroup(m3).gen(0) sage: normalize_square_matrices([m1, m2, m3, m4]) [ [1 2] [2 3] [1/2 1/3] [1/2 1/3] [3 4], [4 5], [1/4 1/5], [1/4 1/5] ] """ deg = [] gens = [] for m in matrices: if is_MatrixGroupElement(m): deg.append(m.parent().degree()) gens.append(m.matrix()) continue if is_Matrix(m): if not m.is_square(): raise TypeError('matrix must be square') deg.append(m.ncols()) gens.append(m) continue try: m = list(m) except TypeError: gens.append(m) continue if isinstance(m[0], (list, tuple)): m = [list(_) for _ in m] degree = ZZ(len(m)) else: degree, rem = ZZ(len(m)).sqrtrem() if rem!=0: raise ValueError('list of plain numbers must have square integer length') deg.append(degree) gens.append(matrix(degree, degree, m)) deg = set(deg) if len(set(deg)) != 1: raise ValueError('not all matrices have the same size') gens = Sequence(gens, immutable=True) MS = gens.universe() if not is_MatrixSpace(MS): raise TypeError('all generators must be matrices') if MS.nrows() != MS.ncols(): raise ValueError('matrices must be square') return gens
def from_oriented_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): r""" Fill ``G`` with the data of an *oriented* incidence matrix. An oriented incidence matrix is the incidence matrix of a directed graph, in which each non-loop edge corresponds to a `+1` and a `-1`, indicating its source and destination. INPUT: - ``G`` -- a :class:`DiGraph` - ``M`` -- an incidence matrix - ``loops``, ``multiedges``, ``weighted`` -- booleans (default: ``False``); whether to consider the graph as having loops, multiple edges, or weights EXAMPLES:: sage: from sage.graphs.graph_input import from_oriented_incidence_matrix sage: g = DiGraph() sage: from_oriented_incidence_matrix(g, digraphs.Circuit(10).incidence_matrix()) sage: g.is_isomorphic(digraphs.Circuit(10)) True TESTS: Fix bug reported in :trac:`22985`:: sage: DiGraph(matrix ([[1,0,0,1],[0,0,1,1],[0,0,1,1]]).transpose()) Traceback (most recent call last): ... ValueError: each column represents an edge: -1 goes to 1 """ from sage.structure.element import is_Matrix assert is_Matrix(M) positions = [] for c in M.columns(): NZ = c.nonzero_positions() if len(NZ) != 2: raise ValueError("there must be two nonzero entries (-1 & 1) per column") L = sorted(set(c.list())) if L != [-1, 0, 1]: raise ValueError("each column represents an edge: -1 goes to 1") if c[NZ[0]] == -1: positions.append(tuple(NZ)) else: positions.append((NZ[1], NZ[0])) if weighted is None: weighted = False if multiedges is None: total = len(positions) multiedges = len(set(positions)) < total G.allow_loops(True if loops else False, check=False) G.allow_multiple_edges(multiedges, check=False) G.add_vertices(range(M.nrows())) G.add_edges(positions)
def from_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): r""" Fill ``G`` with the data of an incidence matrix. INPUT: - ``G`` -- a graph - ``M`` -- an incidence matrix - ``loops``, ``multiedges``, ``weighted`` -- booleans (default: ``False``); whether to consider the graph as having loops, multiple edges, or weights EXAMPLES:: sage: from sage.graphs.graph_input import from_incidence_matrix sage: g = Graph() sage: from_incidence_matrix(g, graphs.PetersenGraph().incidence_matrix()) sage: g.is_isomorphic(graphs.PetersenGraph()) True """ from sage.structure.element import is_Matrix assert is_Matrix(M) oriented = any(M[pos] < 0 for pos in M.nonzero_positions(copy=False)) positions = [] for i in range(M.ncols()): NZ = M.nonzero_positions_in_column(i) if len(NZ) == 1: if oriented: raise ValueError("column {} of the (oriented) incidence " "matrix contains only one nonzero value".format(i)) elif M[NZ[0],i] != 2: raise ValueError("each column of a non-oriented incidence " "matrix must sum to 2, but column {} does not".format(i)) if loops is None: loops = True positions.append((NZ[0], NZ[0])) elif len(NZ) != 2 or \ (oriented and not ((M[NZ[0], i] == +1 and M[NZ[1], i] == -1) or \ (M[NZ[0], i] == -1 and M[NZ[1], i] == +1))) or \ (not oriented and (M[NZ[0], i] != 1 or M[NZ[1], i] != 1)): msg = "there must be one or two nonzero entries per column in an incidence matrix, " msg += "got entries {} in column {}".format([M[j, i] for j in NZ], i) raise ValueError(msg) else: positions.append(tuple(NZ)) if weighted is None: G._weighted = False if multiedges is None: total = len(positions) multiedges = len(set(positions)) < total G.allow_loops(False if loops is None else loops, check=False) G.allow_multiple_edges(multiedges, check=False) G.add_vertices(range(M.nrows())) G.add_edges(positions)
def from_matrix_representation(w, base_field=None, basis=None): r""" Return a vector representation of a matrix ``w`` over ``base_field`` in terms of ``basis``. Given an `m \times n` matrix over `F_q` and some ``basis`` of `F_{q^m}` over `F_q`, we can represent each of its columns as an element of `F_{q^m}`, yielding a vector of length `n` over `F_q`. In case ``base_field`` is not given, we take `F_{q^m}`, the field extension of `F_q` of degree `m`, the number of rows of ``w``. INPUT: - ``w`` -- a matrix over some field `F_q` - ``base_field`` -- (default: ``None``) an extension field of `F_q`. If not specified, it is the field `F_{q^m}`, where `m` is the number of rows of ``w``. - ``basis`` -- (default: ``None``) a basis of `F_{q^m}` as a vector space over ``F_q``. If not specified, given that `q = p^s`, let `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to represent `F_{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. EXAMPLES:: sage: from sage.coding.linear_rank_metric import from_matrix_representation sage: m = Matrix(GF(4), [[1, 1, 1], [1, 1, 0], [0, 0, 0]]) sage: from_matrix_representation(m) (z6 + 1, z6 + 1, 1) sage: v = vector(GF(4), (1, 0, 0)) sage: from_matrix_representation(v) Traceback (most recent call last): ... TypeError: Input must be a matrix """ if not is_Matrix(w): raise TypeError("Input must be a matrix") sub_field = w.base_ring() if not base_field: base_field = sub_field.extension(w.nrows()) v = [] extension, to_big_field, from_big_field = base_field.vector_space( sub_field, basis, map=True) for i in range(w.ncols()): v.append(to_big_field(w.column(i))) return vector(v)
def from_seidel_adjacency_matrix(G, M): r""" Fill ``G`` with the data of a Seidel adjacency matrix. INPUT: - ``G`` -- a graph - ``M`` -- a Seidel adjacency matrix EXAMPLES:: sage: from sage.graphs.graph_input import from_seidel_adjacency_matrix sage: g = Graph() sage: from_seidel_adjacency_matrix(g, graphs.PetersenGraph().seidel_adjacency_matrix()) sage: g.is_isomorphic(graphs.PetersenGraph()) True """ from sage.structure.element import is_Matrix from sage.rings.integer_ring import ZZ assert is_Matrix(M) if M.base_ring() != ZZ: try: M = M.change_ring(ZZ) except TypeError: raise ValueError("Graph's Seidel adjacency matrix must" + " have only 0,1,-1 integer entries") if M.is_sparse(): entries = set(M[i, j] for i, j in M.nonzero_positions()) else: entries = set(M.list()) if any(e < -1 or e > 1 for e in entries): raise ValueError("Graph's Seidel adjacency matrix must" + " have only 0,1,-1 integer entries") if any(i == j for i, j in M.nonzero_positions()): raise ValueError("Graph's Seidel adjacency matrix must" + " have 0s on the main diagonal") if not M.is_symmetric(): raise ValueError("Graph's Seidel adjacency matrix must" + " be symmetric") G.add_vertices(range(M.nrows())) e = [] for i, j in M.nonzero_positions(): if i <= j and M[i, j] < 0: e.append((i, j)) G.add_edges(e)
def __contains__(self, x): r""" Tests if ``x`` is an element of ``self``. INPUT: - ``x`` -- matrix EXAMPLES:: sage: from sage.combinat.integer_matrices import IntegerMatrices sage: IM = IntegerMatrices([4], [1,2,1]) sage: matrix([[1, 2, 1]]) in IM True sage: matrix(QQ, [[1, 2, 1]]) in IM True sage: matrix([[2, 1, 1]]) in IM False TESTS:: sage: from sage.combinat.integer_matrices import IntegerMatrices sage: IM = IntegerMatrices([4], [1,2,1]) sage: [1, 2, 1] in IM False sage: matrix([[-1, 3, 1]]) in IM False """ from sage.structure.element import is_Matrix if not is_Matrix(x): return False row_sums = [ZZ.zero()] * x.nrows() col_sums = [ZZ.zero()] * x.ncols() for i in range(x.nrows()): for j in range(x.ncols()): x_ij = x[i, j] if x_ij not in ZZ or x_ij < 0: return False row_sums[i] += x_ij col_sums[j] += x_ij if row_sums[i] != self._row_sums[i]: return False if col_sums != self._col_sums: return False return True
def from_seidel_adjacency_matrix(G, M): r""" Fill ``G`` with the data of a Seidel adjacency matrix. INPUT: - ``G`` -- a graph - ``M`` -- a Seidel adjacency matrix EXAMPLES:: sage: from sage.graphs.graph_input import from_seidel_adjacency_matrix sage: g = Graph() sage: from_seidel_adjacency_matrix(g, graphs.PetersenGraph().seidel_adjacency_matrix()) sage: g.is_isomorphic(graphs.PetersenGraph()) True """ from sage.structure.element import is_Matrix from sage.rings.integer_ring import ZZ assert is_Matrix(M) if M.base_ring() != ZZ: try: M = M.change_ring(ZZ) except TypeError: raise ValueError("the adjacency matrix of a Seidel graph must" + " have only 0,1,-1 integer entries") if M.is_sparse(): entries = set(M[i,j] for i,j in M.nonzero_positions()) else: entries = set(M.list()) if any(e < -1 or e > 1 for e in entries): raise ValueError("the adjacency matrix of a Seidel graph must" + " have only 0,1,-1 integer entries") if any(i == j for i, j in M.nonzero_positions()): raise ValueError("the adjacency matrix of a Seidel graph must" + " have 0s on the main diagonal") if not M.is_symmetric(): raise ValueError("the adjacency matrix of a Seidel graph must be symmetric") G.add_vertices(range(M.nrows())) G.add_edges((i, j) for i, j in M.nonzero_positions() if i <= j and M[i,j] < 0)
def jacobian(functions, variables): """ Return the Jacobian matrix, which is the matrix of partial derivatives in which the i,j entry of the Jacobian matrix is the partial derivative diff(functions[i], variables[j]). EXAMPLES:: sage: x,y = var('x,y') sage: g=x^2-2*x*y sage: jacobian(g, (x,y)) [2*x - 2*y -2*x] The Jacobian of the Jacobian should give us the "second derivative", which is the Hessian matrix:: sage: jacobian(jacobian(g, (x,y)), (x,y)) [ 2 -2] [-2 0] sage: g.hessian() [ 2 -2] [-2 0] sage: f=(x^3*sin(y), cos(x)*sin(y), exp(x)) sage: jacobian(f, (x,y)) [ 3*x^2*sin(y) x^3*cos(y)] [-sin(x)*sin(y) cos(x)*cos(y)] [ e^x 0] sage: jacobian(f, (y,x)) [ x^3*cos(y) 3*x^2*sin(y)] [ cos(x)*cos(y) -sin(x)*sin(y)] [ 0 e^x] """ if is_Matrix(functions) and (functions.nrows() == 1 or functions.ncols() == 1): functions = functions.list() elif not (isinstance(functions, (tuple, list)) or is_Vector(functions)): functions = [functions] if not isinstance(variables, (tuple, list)) and not is_Vector(variables): variables = [variables] return matrix([[diff(f, v) for v in variables] for f in functions])
def __rmul__(self, matrix): r""" EXAMPLES:: sage: from flatsurf import * sage: s=translation_surfaces.infinite_staircase() sage: s.underlying_surface() The infinite staircase sage: m=Matrix([[1,2],[0,1]]) sage: s2=m*s sage: TestSuite(s2).run(skip='_test_pickling') sage: s2.polygon(0) Polygon: (0, 0), (1, 0), (3, 1), (2, 1) """ if not is_Matrix(matrix): raise NotImplementedError("Only implemented for matrices.") if not matrix.dimensions != (2, 2): raise NotImplementedError("Only implemented for 2x2 matrices.") return self.__class__(GL2RImageSurface(self, matrix)).copy()
def __init__(self, A, B, Title, C=[]): if not A.parent().is_exact(): raise MustBeExact("RungeKutta: parent of A is not exact") if not B.parent().is_exact(): raise MustBeExact("RungeKutta: parent of B is not exact") if not is_Matrix(A): raise NotA("RungeKutta: A is not a matrix") if not is_Vector(B): raise NotA("RungeKutta: B is not a vector") if A.dimensions()[0] != A.dimensions()[1]\ or A.dimensions()[0] != len(B): raise DimensionsAreIncompatible(A, B, C) if C != [] and len(C) != A.dimensions()[0]: raise DimensionsAreIncompatible(A, B, C) self.A = A self.B = B self.Title = Title self.C = C
def __rmul__(self,matrix): r""" EXAMPLES:: sage: from flatsurf import * sage: s=translation_surfaces.infinite_staircase() sage: s.underlying_surface() The infinite staircase sage: m=Matrix([[1,2],[0,1]]) sage: s2=m*s sage: TestSuite(s2).run(skip='_test_pickling') sage: s2.polygon(0) Polygon: (0, 0), (1, 0), (3, 1), (2, 1) """ if not is_Matrix(matrix): raise NotImplementedError("Only implemented for matrices.") if not matrix.dimensions!=(2,2): raise NotImplementedError("Only implemented for 2x2 matrices.") return self.__class__(GL2RImageSurface(self,matrix)).copy()
def jacobian(functions, variables): """ Return the Jacobian matrix, which is the matrix of partial derivatives in which the i,j entry of the Jacobian matrix is the partial derivative diff(functions[i], variables[j]). EXAMPLES:: sage: x,y = var('x,y') sage: g=x^2-2*x*y sage: jacobian(g, (x,y)) [2*x - 2*y -2*x] The Jacobian of the Jacobian should give us the "second derivative", which is the Hessian matrix:: sage: jacobian(jacobian(g, (x,y)), (x,y)) [ 2 -2] [-2 0] sage: g.hessian() [ 2 -2] [-2 0] sage: f=(x^3*sin(y), cos(x)*sin(y), exp(x)) sage: jacobian(f, (x,y)) [ 3*x^2*sin(y) x^3*cos(y)] [-sin(x)*sin(y) cos(x)*cos(y)] [ e^x 0] sage: jacobian(f, (y,x)) [ x^3*cos(y) 3*x^2*sin(y)] [ cos(x)*cos(y) -sin(x)*sin(y)] [ 0 e^x] """ if is_Matrix(functions) and (functions.nrows()==1 or functions.ncols()==1): functions = functions.list() elif not (isinstance(functions, (tuple, list)) or is_Vector(functions)): functions = [functions] if not isinstance(variables, (tuple, list)) and not is_Vector(variables): variables = [variables] return matrix([[diff(f, v) for v in variables] for f in functions])
def __init__(self, parent, A): r""" Initialise an element from a matrix. This *must* be over the base ring of self and have the right size. This is a bit overkill as similar checks will be performed by the call and coerce methods of the parent of self, but it can't hurt to be paranoid. Any fancy coercion / base_extension / etc happens there, not here. TESTS:: sage: T = ModularForms(Gamma0(7), 4).hecke_algebra() sage: M = sage.modular.hecke.hecke_operator.HeckeAlgebraElement_matrix(T, matrix(QQ,3,[2,3,0,1,2,3,7,8,9])); M Hecke operator on Modular Forms space of dimension 3 for Congruence Subgroup Gamma0(7) of weight 4 over Rational Field defined by: [2 3 0] [1 2 3] [7 8 9] sage: loads(dumps(M)) == M True sage: sage.modular.hecke.hecke_operator.HeckeAlgebraElement_matrix(T, matrix(Integers(2),3,[2,3,0,1,2,3,7,8,9])) Traceback (most recent call last): ... TypeError: base ring of matrix (Ring of integers modulo 2) does not match base ring of space (Rational Field) sage: sage.modular.hecke.hecke_operator.HeckeAlgebraElement_matrix(T, matrix(QQ,2,[2,3,0,1])) Traceback (most recent call last): ... TypeError: A must be a square matrix of rank 3 """ HeckeAlgebraElement.__init__(self, parent) from sage.structure.element import is_Matrix if not is_Matrix(A): raise TypeError("A must be a matrix") if not A.base_ring() == self.parent().base_ring(): raise TypeError( "base ring of matrix (%s) does not match base ring of space (%s)" % (A.base_ring(), self.parent().base_ring())) if not A.nrows() == A.ncols() == self.parent().module().rank(): raise TypeError("A must be a square matrix of rank %s" % self.parent().module().rank()) self.__matrix = A
def is_generalized_cartan_matrix(M): """ Return ``True`` if ``M`` is a generalized Cartan matrix. For a definition of a generalized Cartan matrix, see :class:`CartanMatrix`. EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import is_generalized_cartan_matrix sage: M = matrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) sage: is_generalized_cartan_matrix(M) True sage: M = matrix([[2,-1,-2], [-1,2,-1], [0,-1,2]]) sage: is_generalized_cartan_matrix(M) False sage: M = matrix([[1,-1,-2], [-1,2,-1], [-2,-1,2]]) sage: is_generalized_cartan_matrix(M) False A non-symmetrizable example:: sage: M = matrix([[2,-1,-2], [-1,2,-1], [-1,-1,2]]) sage: is_generalized_cartan_matrix(M) True """ if not is_Matrix(M): return False if not M.is_square(): return False n = M.ncols() for i in range(n): if M[i,i] != 2: return False for j in range(i+1, n): if M[i,j] > 0 or M[j,i] > 0: return False elif M[i,j] == 0 and M[j,i] != 0: return False elif M[j,i] == 0 and M[i,j] != 0: return False return True
def is_generalized_cartan_matrix(M): """ Return ``True`` if ``M`` is a generalized Cartan matrix. For a definition of a generalized Cartan matrix, see :class:`CartanMatrix`. EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import is_generalized_cartan_matrix sage: M = matrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) sage: is_generalized_cartan_matrix(M) True sage: M = matrix([[2,-1,-2], [-1,2,-1], [0,-1,2]]) sage: is_generalized_cartan_matrix(M) False sage: M = matrix([[1,-1,-2], [-1,2,-1], [-2,-1,2]]) sage: is_generalized_cartan_matrix(M) False A non-symmetrizable example:: sage: M = matrix([[2,-1,-2], [-1,2,-1], [-1,-1,2]]) sage: is_generalized_cartan_matrix(M) True """ if not is_Matrix(M): return False if not M.is_square(): return False n = M.ncols() for i in range(n): if M[i, i] != 2: return False for j in range(i + 1, n): if M[i, j] > 0 or M[j, i] > 0: return False elif M[i, j] == 0 and M[j, i] != 0: return False elif M[j, i] == 0 and M[i, j] != 0: return False return True
def is_borcherds_cartan_matrix(M): """ Return ``True`` if ``M`` is an even, integral Borcherds-Cartan matrix. For a definition of such a matrix, see :class:`CartanMatrix`. EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import is_borcherds_cartan_matrix sage: M = Matrix([[2,-1],[-1,2]]) sage: is_borcherds_cartan_matrix(M) True sage: N = Matrix([[2,-1],[-1,0]]) sage: is_borcherds_cartan_matrix(N) False sage: O = Matrix([[2,-1],[-1,-2]]) sage: is_borcherds_cartan_matrix(O) True sage: O = Matrix([[2,-1],[-1,-3]]) sage: is_borcherds_cartan_matrix(O) False """ if not is_Matrix(M): return False if not M.is_square(): return False n = M.ncols() for i in range(n): if M[i,i] == 0: return False if M[i,i] % 2 == 1: return False for j in range(i+1, n): if M[i,j] > 0 or M[j,i] > 0: return False elif M[i,j] == 0 and M[j,i] != 0: return False elif M[j,i] == 0 and M[i,j] != 0: return False return True
def is_borcherds_cartan_matrix(M): """ Return ``True`` if ``M`` is an even, integral Borcherds-Cartan matrix. For a definition of such a matrix, see :class:`CartanMatrix`. EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import is_borcherds_cartan_matrix sage: M = Matrix([[2,-1],[-1,2]]) sage: is_borcherds_cartan_matrix(M) True sage: N = Matrix([[2,-1],[-1,0]]) sage: is_borcherds_cartan_matrix(N) False sage: O = Matrix([[2,-1],[-1,-2]]) sage: is_borcherds_cartan_matrix(O) True sage: O = Matrix([[2,-1],[-1,-3]]) sage: is_borcherds_cartan_matrix(O) False """ if not is_Matrix(M): return False if not M.is_square(): return False n = M.ncols() for i in range(n): if M[i, i] == 0: return False if M[i, i] % 2 == 1: return False for j in range(i + 1, n): if M[i, j] > 0 or M[j, i] > 0: return False elif M[i, j] == 0 and M[j, i] != 0: return False elif M[j, i] == 0 and M[i, j] != 0: return False return True
def __init__(self, parent, A): r""" Initialise an element from a matrix. This *must* be over the base ring of self and have the right size. This is a bit overkill as similar checks will be performed by the call and coerce methods of the parent of self, but it can't hurt to be paranoid. Any fancy coercion / base_extension / etc happens there, not here. TESTS:: sage: T = ModularForms(Gamma0(7), 4).hecke_algebra() sage: M = sage.modular.hecke.hecke_operator.HeckeAlgebraElement_matrix(T, matrix(QQ,3,[2,3,0,1,2,3,7,8,9])); M Hecke operator on Modular Forms space of dimension 3 for Congruence Subgroup Gamma0(7) of weight 4 over Rational Field defined by: [2 3 0] [1 2 3] [7 8 9] sage: loads(dumps(M)) == M True sage: sage.modular.hecke.hecke_operator.HeckeAlgebraElement_matrix(T, matrix(Integers(2),3,[2,3,0,1,2,3,7,8,9])) Traceback (most recent call last): ... TypeError: base ring of matrix (Ring of integers modulo 2) does not match base ring of space (Rational Field) sage: sage.modular.hecke.hecke_operator.HeckeAlgebraElement_matrix(T, matrix(QQ,2,[2,3,0,1])) Traceback (most recent call last): ... TypeError: A must be a square matrix of rank 3 """ HeckeAlgebraElement.__init__(self, parent) from sage.structure.element import is_Matrix if not is_Matrix(A): raise TypeError("A must be a matrix") if not A.base_ring() == self.parent().base_ring(): raise TypeError("base ring of matrix (%s) does not match base ring of space (%s)" % (A.base_ring(), self.parent().base_ring())) if not A.nrows() == A.ncols() == self.parent().module().rank(): raise TypeError("A must be a square matrix of rank %s" % self.parent().module().rank()) self.__matrix = A
def write_matrix(self, mat, filename): r""" Write the matrix ``mat`` to the file ``filename`` in 4ti2 format. INPUT: - ``mat`` -- A matrix of integers or something that can be converted to that. - ``filename`` -- A file name not including a path. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 sage: four_ti_2.write_matrix([[1,2],[3,4]], "test_file") """ from sage.matrix.constructor import matrix from sage.structure.element import is_Matrix if not is_Matrix(mat): mat = matrix(ZZ, mat) if mat.base_ring() != ZZ: mat = mat.change_ring(ZZ) self.write_array(mat, mat.nrows(), mat.ncols(), filename)
def simplify(self, obj): r''' Method to simplify an object using the relations found. This method simplifies an object (either an element, vector, list, tuple or matrix) using the relations that we found during the computation of the solutions of this linear system. WARNING: repeated executions of this method may return different outputs since we may have found more relations. ''' if (SAGE_element.is_Matrix(obj)): return Matrix(obj.parent().base(), [[self.simplify(el) for el in row] for row in obj]) elif (SAGE_element.is_Vector(obj)): return vector(obj.parent().base(), [self.simplify(el) for el in obj]) elif (isinstance(obj, list)): return [self.simplify(el) for el in obj] elif (isinstance(obj, tuple)): return tuple([self.simplify(el) for el in obj]) elif (self.have_ideal()): return obj.reduce(self.__gb) return obj
def _acted_upon_(self, g, self_on_left): r""" Implement the left action of `SL_2(\ZZ)` on self. EXAMPLES:: sage: g = matrix(ZZ, 2, [1,1,0,1]); g [1 1] [0 1] sage: g * Cusp(2,5) 7/5 sage: Cusp(2,5) * g Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for *: 'Set P^1(QQ) of all cusps' and 'Full MatrixSpace of 2 by 2 dense matrices over Integer Ring' sage: h = matrix(ZZ, 2, [12,3,-100,7]) sage: h * Cusp(2,5) -13/55 sage: Cusp(2,5)._acted_upon_(h, False) -13/55 sage: (h*g) * Cusp(3,7) == h * (g * Cusp(3,7)) True sage: cm = sage.structure.element.get_coercion_model() sage: cm.explain(MatrixSpace(ZZ, 2), Cusps) Action discovered. Left action by Full MatrixSpace of 2 by 2 dense matrices over Integer Ring on Set P^1(QQ) of all cusps Result lives in Set P^1(QQ) of all cusps Set P^1(QQ) of all cusps """ if not self_on_left: if (is_Matrix(g) and g.base_ring() is ZZ and g.ncols() == 2 == g.nrows()): a, b, c, d = g.list() return Cusp(a * self.__a + b * self.__b, c * self.__a + d * self.__b)
def __init__(self, homspace, A): r""" Create a linear transformation, a morphism between vector spaces. INPUT: - ``homspace`` - a homspace (of vector spaces) to serve as a parent for the linear transformation and a home for the domain and codomain of the morphism - ``A`` - a matrix representing the linear transformation, which will act on vectors placed to the left of the matrix EXAMPLES: Nominally, we require a homspace to hold the domain and codomain and a matrix representation of the morphism (linear transformation). :: sage: from sage.modules.vector_space_homspace import VectorSpaceHomspace sage: from sage.modules.vector_space_morphism import VectorSpaceMorphism sage: H = VectorSpaceHomspace(QQ^3, QQ^2) sage: A = matrix(QQ, 3, 2, range(6)) sage: zeta = VectorSpaceMorphism(H, A) sage: zeta Vector space morphism represented by the matrix: [0 1] [2 3] [4 5] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field See the constructor, :func:`sage.modules.vector_space_morphism.linear_transformation` for another way to create linear transformations. The ``.hom()`` method of a vector space will create a vector space morphism. :: sage: V = QQ^3; W = V.subspace_with_basis([[1,2,3], [-1,2,5/3], [0,1,-1]]) sage: phi = V.hom(matrix(QQ, 3, range(9)), codomain=W) # indirect doctest sage: type(phi) <class 'sage.modules.vector_space_morphism.VectorSpaceMorphism'> A matrix may be coerced into a vector space homspace to create a vector space morphism. :: sage: from sage.modules.vector_space_homspace import VectorSpaceHomspace sage: H = VectorSpaceHomspace(QQ^3, QQ^2) sage: A = matrix(QQ, 3, 2, range(6)) sage: rho = H(A) # indirect doctest sage: type(rho) <class 'sage.modules.vector_space_morphism.VectorSpaceMorphism'> """ if not vector_space_homspace.is_VectorSpaceHomspace(homspace): raise TypeError('homspace must be a vector space hom space, not {0}'.format(homspace)) if isinstance(A, matrix_morphism.MatrixMorphism): A = A.matrix() if not is_Matrix(A): msg = 'input must be a matrix representation or another matrix morphism, not {0}' raise TypeError(msg.format(A)) # now have a vector space homspace, and a matrix, check compatibility if homspace.domain().dimension() != A.nrows(): raise TypeError('domain dimension is incompatible with matrix size') if homspace.codomain().dimension() != A.ncols(): raise TypeError('codomain dimension is incompatible with matrix size') A = homspace._matrix_space()(A) free_module_morphism.FreeModuleMorphism.__init__(self, homspace, A)
def _element_constructor_(self, x, check=True): """ Construct a scheme morphism. INPUT: - `x` -- anything that defines a morphism of toric varieties. A matrix, fan morphism, or a list or tuple of homogeneous polynomials that define a morphism. - ``check`` -- boolean (default: ``True``) passed onto functions called by this to be more careful about input argument type checking OUTPUT: The morphism of toric varieties determined by ``x``. EXAMPLES: First, construct from fan morphism:: sage: dP8.<t,x0,x1,x2> = toric_varieties.dP8() sage: P2.<y0,y1,y2> = toric_varieties.P2() sage: hom_set = dP8.Hom(P2) sage: fm = FanMorphism(identity_matrix(2), dP8.fan(), P2.fan()) sage: hom_set(fm) # calls hom_set._element_constructor_() Scheme morphism: From: 2-d CPR-Fano toric variety covered by 4 affine patches To: 2-d CPR-Fano toric variety covered by 3 affine patches Defn: Defined by sending Rational polyhedral fan in 2-d lattice N to Rational polyhedral fan in 2-d lattice N. A matrix will automatically be converted to a fan morphism:: sage: hom_set(identity_matrix(2)) Scheme morphism: From: 2-d CPR-Fano toric variety covered by 4 affine patches To: 2-d CPR-Fano toric variety covered by 3 affine patches Defn: Defined by sending Rational polyhedral fan in 2-d lattice N to Rational polyhedral fan in 2-d lattice N. Alternatively, one can use homogeneous polynomials to define morphisms:: sage: P2.inject_variables() Defining y0, y1, y2 sage: dP8.inject_variables() Defining t, x0, x1, x2 sage: hom_set([x0,x1,x2]) Scheme morphism: From: 2-d CPR-Fano toric variety covered by 4 affine patches To: 2-d CPR-Fano toric variety covered by 3 affine patches Defn: Defined on coordinates by sending [t : x0 : x1 : x2] to [x0 : x1 : x2] A morphism of the coordinate ring will also work:: sage: ring_hom = P2.coordinate_ring().hom([x0,x1,x2], dP8.coordinate_ring()) sage: ring_hom Ring morphism: From: Multivariate Polynomial Ring in y0, y1, y2 over Rational Field To: Multivariate Polynomial Ring in t, x0, x1, x2 over Rational Field Defn: y0 |--> x0 y1 |--> x1 y2 |--> x2 sage: hom_set(ring_hom) Scheme morphism: From: 2-d CPR-Fano toric variety covered by 4 affine patches To: 2-d CPR-Fano toric variety covered by 3 affine patches Defn: Defined on coordinates by sending [t : x0 : x1 : x2] to [x0 : x1 : x2] """ from sage.schemes.toric.morphism import SchemeMorphism_polynomial_toric_variety if isinstance(x, (list, tuple)): return SchemeMorphism_polynomial_toric_variety(self, x, check=check) from sage.categories.map import Map from sage.categories.all import Rings if isinstance(x, Map) and x.category_for().is_subcategory(Rings()): # x is a morphism of Rings assert x.domain() is self.codomain().coordinate_ring() assert x.codomain() is self.domain().coordinate_ring() return SchemeMorphism_polynomial_toric_variety(self, x.im_gens(), check=check) if is_Matrix(x): x = FanMorphism(x, self.domain().fan(), self.codomain().fan()) if isinstance(x, FanMorphism): if x.is_dominant(): from sage.schemes.toric.morphism import SchemeMorphism_fan_toric_variety_dominant return SchemeMorphism_fan_toric_variety_dominant(self, x, check=check) else: from sage.schemes.toric.morphism import SchemeMorphism_fan_toric_variety return SchemeMorphism_fan_toric_variety(self, x, check=check) raise TypeError( "x must be a fan morphism or a list/tuple of polynomials")
def hom(self, x, Y=None): r""" Return the scheme morphism from ``self`` to ``Y`` defined by ``x``. Here ``x`` can be a matrix or a sequence of polynomials. If ``Y`` is omitted, then a natural image is found if possible. EXAMPLES: Here are a few Morphisms given by matrices. In the first example, ``Y`` is omitted, in the second example, ``Y`` is specified. :: sage: c = Conic([-1, 1, 1]) sage: h = c.hom(Matrix([[1,1,0],[0,1,0],[0,0,1]])); h Scheme morphism: From: Projective Conic Curve over Rational Field defined by -x^2 + y^2 + z^2 To: Projective Conic Curve over Rational Field defined by -x^2 + 2*x*y + z^2 Defn: Defined on coordinates by sending (x : y : z) to (x + y : y : z) sage: h([-1, 1, 0]) (0 : 1 : 0) sage: c = Conic([-1, 1, 1]) sage: d = Conic([4, 1, -1]) sage: c.hom(Matrix([[0, 0, 1/2], [0, 1, 0], [1, 0, 0]]), d) Scheme morphism: From: Projective Conic Curve over Rational Field defined by -x^2 + y^2 + z^2 To: Projective Conic Curve over Rational Field defined by 4*x^2 + y^2 - z^2 Defn: Defined on coordinates by sending (x : y : z) to (1/2*z : y : x) ``ValueError`` is raised if the wrong codomain ``Y`` is specified: :: sage: c = Conic([-1, 1, 1]) sage: c.hom(Matrix([[0, 0, 1/2], [0, 1, 0], [1, 0, 0]]), c) Traceback (most recent call last): ... ValueError: The matrix x (= [ 0 0 1/2] [ 0 1 0] [ 1 0 0]) does not define a map from self (= Projective Conic Curve over Rational Field defined by -x^2 + y^2 + z^2) to Y (= Projective Conic Curve over Rational Field defined by -x^2 + y^2 + z^2) The identity map between two representations of the same conic: :: sage: C = Conic([1,2,3,4,5,6]) sage: D = Conic([2,4,6,8,10,12]) sage: C.hom(identity_matrix(3), D) Scheme morphism: From: Projective Conic Curve over Rational Field defined by x^2 + 2*x*y + 4*y^2 + 3*x*z + 5*y*z + 6*z^2 To: Projective Conic Curve over Rational Field defined by 2*x^2 + 4*x*y + 8*y^2 + 6*x*z + 10*y*z + 12*z^2 Defn: Defined on coordinates by sending (x : y : z) to (x : y : z) An example not over the rational numbers: :: sage: P.<t> = QQ[] sage: C = Conic([1,0,0,t,0,1/t]) sage: D = Conic([1/t^2, 0, -2/t^2, t, 0, (t + 1)/t^2]) sage: T = Matrix([[t,0,1],[0,1,0],[0,0,1]]) sage: C.hom(T, D) Scheme morphism: From: Projective Conic Curve over Fraction Field of Univariate Polynomial Ring in t over Rational Field defined by x^2 + t*y^2 + 1/t*z^2 To: Projective Conic Curve over Fraction Field of Univariate Polynomial Ring in t over Rational Field defined by 1/t^2*x^2 + t*y^2 + (-2/t^2)*x*z + ((t + 1)/t^2)*z^2 Defn: Defined on coordinates by sending (x : y : z) to (t*x + z : y : z) """ if is_Matrix(x): from .constructor import Conic y = x.inverse() A = y.transpose()*self.matrix()*y im = Conic(A) if Y is None: Y = im elif not Y == im: raise ValueError("The matrix x (= %s) does not define a " \ "map from self (= %s) to Y (= %s)" % \ (x, self, Y)) x = Sequence(x*vector(self.ambient_space().gens())) return self.Hom(Y)(x, check = False) return ProjectivePlaneCurve.hom(self, x, Y)
def __call__(self, M): r""" Create a homomorphism in this space from M. M can be any of the following: - a Morphism of abelian varieties - a matrix of the appropriate size (i.e. 2\*self.domain().dimension() x 2\*self.codomain().dimension()) whose entries are coercible into self.base_ring() - anything that can be coerced into self.matrix_space() EXAMPLES:: sage: H = Hom(J0(11), J0(22)) sage: phi = H(matrix(ZZ,2,4,[5..12])) ; phi Abelian variety morphism: From: Abelian variety J0(11) of dimension 1 To: Abelian variety J0(22) of dimension 2 sage: phi.matrix() [ 5 6 7 8] [ 9 10 11 12] sage: phi.matrix().parent() Full MatrixSpace of 2 by 4 dense matrices over Integer Ring :: sage: H = J0(22).Hom(J0(11)*J0(11)) sage: m1 = J0(22).degeneracy_map(11,1).matrix() ; m1 [ 0 1] [-1 1] [-1 0] [ 0 -1] sage: m2 = J0(22).degeneracy_map(11,2).matrix() ; m2 [ 1 -2] [ 0 -2] [ 1 -1] [ 0 -1] sage: m = m1.transpose().stack(m2.transpose()).transpose() ; m [ 0 1 1 -2] [-1 1 0 -2] [-1 0 1 -1] [ 0 -1 0 -1] sage: phi = H(m) ; phi Abelian variety morphism: From: Abelian variety J0(22) of dimension 2 To: Abelian variety J0(11) x J0(11) of dimension 2 sage: phi.matrix() [ 0 1 1 -2] [-1 1 0 -2] [-1 0 1 -1] [ 0 -1 0 -1] """ if isinstance(M, morphism.Morphism): if M.parent() is self: return M elif M.domain() == self.domain() and M.codomain() == self.codomain(): M = M.matrix() else: raise ValueError("cannot convert %s into %s" % (M, self)) elif is_Matrix(M): if M.base_ring() != ZZ: M = M.change_ring(ZZ) if M.nrows() != 2*self.domain().dimension() or M.ncols() != 2*self.codomain().dimension(): raise TypeError("matrix has wrong dimension") elif self.matrix_space().has_coerce_map_from(parent(M)): M = self.matrix_space()(M) else: raise TypeError("can only coerce in matrices or morphisms") return self.element_class(self, M)
def DynkinDiagram(*args, **kwds): r""" Return the Dynkin diagram corresponding to the input. INPUT: The input can be one of the following: - empty to obtain an empty Dynkin diagram - a Cartan type - a Cartan matrix - a Cartan matrix and an indexing set One can also input an indexing set by passing a tuple using the optional argument ``index_set``. The edge multiplicities are encoded as edge labels. For the corresponding Cartan matrices, this uses the convention in Hong and Kang, Kac, Fulton and Harris, and crystals. This is the **opposite** convention in Bourbaki and Wikipedia's Dynkin diagram (:wikipedia:`Dynkin_diagram`). That is for `i \neq j`:: i <--k-- j <==> a_ij = -k <==> -scalar(coroot[i], root[j]) = k <==> multiple arrows point from the longer root to the shorter one For example, in type `C_2`, we have:: sage: C2 = DynkinDiagram(['C',2]); C2 O=<=O 1 2 C2 sage: C2.cartan_matrix() [ 2 -2] [-1 2] However Bourbaki would have the Cartan matrix as: .. MATH:: \begin{bmatrix} 2 & -1 \\ -2 & 2 \end{bmatrix}. EXAMPLES:: sage: DynkinDiagram(['A', 4]) O---O---O---O 1 2 3 4 A4 sage: DynkinDiagram(['A',1],['A',1]) O 1 O 2 A1xA1 sage: R = RootSystem("A2xB2xF4") sage: DynkinDiagram(R) O---O 1 2 O=>=O 3 4 O---O=>=O---O 5 6 7 8 A2xB2xF4 sage: R = RootSystem("A2xB2xF4") sage: CM = R.cartan_matrix(); CM [ 2 -1| 0 0| 0 0 0 0] [-1 2| 0 0| 0 0 0 0] [-----+-----+-----------] [ 0 0| 2 -1| 0 0 0 0] [ 0 0|-2 2| 0 0 0 0] [-----+-----+-----------] [ 0 0| 0 0| 2 -1 0 0] [ 0 0| 0 0|-1 2 -1 0] [ 0 0| 0 0| 0 -2 2 -1] [ 0 0| 0 0| 0 0 -1 2] sage: DD = DynkinDiagram(CM); DD O---O 1 2 O=>=O 3 4 O---O=>=O---O 5 6 7 8 A2xB2xF4 sage: DD.cartan_matrix() [ 2 -1 0 0 0 0 0 0] [-1 2 0 0 0 0 0 0] [ 0 0 2 -1 0 0 0 0] [ 0 0 -2 2 0 0 0 0] [ 0 0 0 0 2 -1 0 0] [ 0 0 0 0 -1 2 -1 0] [ 0 0 0 0 0 -2 2 -1] [ 0 0 0 0 0 0 -1 2] We can also create Dynkin diagrams from arbitrary Cartan matrices:: sage: C = CartanMatrix([[2, -3], [-4, 2]]) sage: DynkinDiagram(C) Dynkin diagram of rank 2 sage: C.index_set() (0, 1) sage: CI = CartanMatrix([[2, -3], [-4, 2]], [3, 5]) sage: DI = DynkinDiagram(CI) sage: DI.index_set() (3, 5) sage: CII = CartanMatrix([[2, -3], [-4, 2]]) sage: DII = DynkinDiagram(CII, ('y', 'x')) sage: DII.index_set() ('x', 'y') .. SEEALSO:: :func:`CartanType` for a general discussion on Cartan types and in particular node labeling conventions. TESTS: Check that :trac:`15277` is fixed by not having edges from 0's:: sage: CM = CartanMatrix([[2,-1,0,0],[-3,2,-2,-2],[0,-1,2,-1],[0,-1,-1,2]]) sage: CM [ 2 -1 0 0] [-3 2 -2 -2] [ 0 -1 2 -1] [ 0 -1 -1 2] sage: CM.dynkin_diagram().edges() [(0, 1, 3), (1, 0, 1), (1, 2, 1), (1, 3, 1), (2, 1, 2), (2, 3, 1), (3, 1, 2), (3, 2, 1)] """ if len(args) == 0: return DynkinDiagram_class() mat = args[0] if is_Matrix(mat): mat = CartanMatrix(*args) if isinstance(mat, CartanMatrix): if mat.cartan_type() is not mat: try: return mat.cartan_type().dynkin_diagram() except AttributeError: ct = CartanType(*args) raise ValueError("Dynkin diagram data not yet hardcoded for type %s"%ct) if len(args) > 1: index_set = tuple(args[1]) elif "index_set" in kwds: index_set = tuple(kwds["index_set"]) else: index_set = mat.index_set() D = DynkinDiagram_class(index_set=index_set) for (i, j) in mat.nonzero_positions(): if i != j: D.add_edge(index_set[i], index_set[j], -mat[j, i]) return D ct = CartanType(*args) try: return ct.dynkin_diagram() except AttributeError: raise ValueError("Dynkin diagram data not yet hardcoded for type %s"%ct)
def __classcall_private__(cls, k, table, names='e', assume_associative=False, category=None): """ Normalize input. TESTS:: sage: table = [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])] sage: A1 = FiniteDimensionalAlgebra(GF(3), table) sage: A2 = FiniteDimensionalAlgebra(GF(3), table, names='e') sage: A3 = FiniteDimensionalAlgebra(GF(3), table, names=['e0', 'e1']) sage: A1 is A2 and A2 is A3 True The ``assume_associative`` keyword is built into the category:: sage: from sage.categories.magmatic_algebras import MagmaticAlgebras sage: cat = MagmaticAlgebras(GF(3)).FiniteDimensional().WithBasis() sage: A1 = FiniteDimensionalAlgebra(GF(3), table, category=cat.Associative()) sage: A2 = FiniteDimensionalAlgebra(GF(3), table, assume_associative=True) sage: A1 is A2 True Uniqueness depends on the category:: sage: cat = Algebras(GF(3)).FiniteDimensional().WithBasis() sage: A1 = FiniteDimensionalAlgebra(GF(3), table) sage: A2 = FiniteDimensionalAlgebra(GF(3), table, category=cat) sage: A1 == A2 False sage: A1 is A2 False Checking that equality is still as expected:: sage: A = FiniteDimensionalAlgebra(GF(3), table) sage: B = FiniteDimensionalAlgebra(GF(5), [Matrix([0])]) sage: A == A True sage: B == B True sage: A == B False sage: A != A False sage: B != B False sage: A != B True """ n = len(table) table = [b.base_extend(k) for b in table] for b in table: b.set_immutable() if not (is_Matrix(b) and b.dimensions() == (n, n)): raise ValueError("input is not a multiplication table") table = tuple(table) cat = MagmaticAlgebras(k).FiniteDimensional().WithBasis() cat = cat.or_subcategory(category) if assume_associative: cat = cat.Associative() names = normalize_names(n, names) return super(FiniteDimensionalAlgebra, cls).__classcall__(cls, k, table, names, category=cat)
def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): """ Compute echelon form of 3-term relation matrix, and read off each generator in terms of basis. INPUT: - ``syms`` -- :class:`ManinSymbolList` - ``relation_matrix`` - as output by ``__compute_T_relation_matrix(self, mod)`` - ``mod`` - quotient of modular symbols modulo the 2-term S (and possibly I) relations - ``field`` - base field - ``sparse`` - (bool): whether or not matrix should be sparse OUTPUT: - ``matrix`` - a matrix whose ith row expresses the Manin symbol generators in terms of a basis of Manin symbols (modulo the S, (possibly I,) and T rels) Note that the entries of the matrix need not be integers. - ``list`` - integers i, such that the Manin symbols `x_i` are a basis. EXAMPLES:: sage: from sage.modular.modsym.relation_matrix import sparse_2term_quotient, T_relation_matrix_wtk_g0, gens_to_basis_matrix, modS_relations sage: L = sage.modular.modsym.manin_symbol_list.ManinSymbolList_gamma1(4, 3) sage: modS = sparse_2term_quotient(modS_relations(L), 24, GF(3)) sage: gens_to_basis_matrix(L, T_relation_matrix_wtk_g0(L, modS, GF(3), 24), modS, GF(3), True) (24 x 2 sparse matrix over Finite Field of size 3, [13, 23]) """ from sage.structure.element import is_Matrix if not is_Matrix(relation_matrix): raise TypeError("relation_matrix must be a matrix") if not isinstance(mod, list): raise TypeError("mod must be a list") misc.verbose(str(relation_matrix.parent())) try: h = relation_matrix.height() except AttributeError: h = 9999999 tm = misc.verbose("putting relation matrix in echelon form (height = %s)"%h) if h < 10: A = relation_matrix.echelon_form(algorithm='multimodular', height_guess=1) else: A = relation_matrix.echelon_form() A.set_immutable() tm = misc.verbose('finished echelon', tm) tm = misc.verbose("Now creating gens --> basis mapping") basis_set = set(A.nonpivots()) pivots = A.pivots() basis_mod2 = set([j for j,c in mod if c != 0]) basis_set = basis_set.intersection(basis_mod2) basis = sorted(basis_set) ONE = field(1) misc.verbose("done doing setup",tm) tm = misc.verbose("now forming quotient matrix") M = matrix_space.MatrixSpace(field, len(syms), len(basis), sparse=sparse) B = M(0) cols_index = dict([(basis[i], i) for i in range(len(basis))]) for i in basis_mod2: t, l = search(basis, i) if t: B[i,l] = ONE else: _, r = search(pivots, i) # so pivots[r] = i # Set row i to -(row r of A), but where we only take # the non-pivot columns of A: B._set_row_to_negative_of_row_of_A_using_subset_of_columns(i, A, r, basis, cols_index) misc.verbose("done making quotient matrix",tm) # The following is very fast (over Q at least). tm = misc.verbose('now filling in the rest of the matrix') k = 0 for i in range(len(mod)): j, s = mod[i] if j != i and s != 0: # ignored in the above matrix k += 1 B.set_row_to_multiple_of_row(i, j, s) misc.verbose("set %s rows"%k) tm = misc.verbose("time to fill in rest of matrix", tm) return B, basis
def from_oriented_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): r""" Fill ``G`` with the data of an *oriented* incidence matrix. An oriented incidence matrix is the incidence matrix of a directed graph, in which each non-loop edge corresponds to a `+1` and a `-1`, indicating its source and destination. INPUT: - ``G`` -- a :class:`DiGraph` - ``M`` -- an incidence matrix - ``loops``, ``multiedges``, ``weighted`` -- booleans (default: ``False``); whether to consider the graph as having loops, multiple edges, or weights EXAMPLES:: sage: from sage.graphs.graph_input import from_oriented_incidence_matrix sage: g = DiGraph() sage: from_oriented_incidence_matrix(g, digraphs.Circuit(10).incidence_matrix()) sage: g.is_isomorphic(digraphs.Circuit(10)) True TESTS: Fix bug reported in :trac:`22985`:: sage: DiGraph(matrix ([[1,0,0,1],[0,0,1,1],[0,0,1,1]]).transpose()) Traceback (most recent call last): ... ValueError: each column represents an edge: -1 goes to 1 Handle incidence matrix containing a column with only zeros (:trac:`29275`):: sage: m = Matrix([[0,1],[0,-1],[0,0]]) sage: m [ 0 1] [ 0 -1] [ 0 0] sage: G = DiGraph(m,format='incidence_matrix') sage: list(G.edges(labels=False)) [(1, 0)] Handle incidence matrix [[1],[-1]] (:trac:`29275`):: sage: m = Matrix([[1],[-1]]) sage: m [ 1] [-1] sage: G = DiGraph(m,format='incidence_matrix') sage: list(G.edges(labels=False)) [(1, 0)] """ from sage.structure.element import is_Matrix assert is_Matrix(M) positions = [] for c in M.columns(): NZ = c.nonzero_positions() if not NZ: continue if len(NZ) != 2: raise ValueError( "there must be two nonzero entries (-1 & 1) per column") L = sorted([c[i] for i in NZ]) if L != [-1, 1]: raise ValueError("each column represents an edge: -1 goes to 1") if c[NZ[0]] == -1: positions.append(tuple(NZ)) else: positions.append((NZ[1], NZ[0])) if weighted is None: weighted = False if multiedges is None: total = len(positions) multiedges = len(set(positions)) < total G.allow_loops(True if loops else False, check=False) G.allow_multiple_edges(multiedges, check=False) G.add_vertices(range(M.nrows())) G.add_edges(positions)
def from_adjacency_matrix(G, M, loops=False, multiedges=False, weighted=False): r""" Fill ``G`` with the data of an adjacency matrix. INPUT: - ``G`` -- a :class:`Graph` or :class:`DiGraph` - ``M`` -- an adjacency matrix - ``loops``, ``multiedges``, ``weighted`` -- booleans (default: ``False``); whether to consider the graph as having loops, multiple edges, or weights EXAMPLES:: sage: from sage.graphs.graph_input import from_adjacency_matrix sage: g = Graph() sage: from_adjacency_matrix(g, graphs.PetersenGraph().adjacency_matrix()) sage: g.is_isomorphic(graphs.PetersenGraph()) True """ from sage.structure.element import is_Matrix from sage.rings.integer_ring import ZZ assert is_Matrix(M) # note: the adjacency matrix might be weighted and hence not # necessarily consists of integers if not weighted and M.base_ring() != ZZ: try: M = M.change_ring(ZZ) except TypeError: if weighted is False: raise ValueError("the adjacency matrix of a non-weighted graph" + " must have only nonnegative integer entries") weighted = True if M.is_sparse(): entries = set(M[i,j] for i,j in M.nonzero_positions()) else: entries = set(M.list()) if not weighted and any(e < 0 for e in entries): if weighted is False: raise ValueError("the adjacency matrix of a non-weighted graph" + " must have only nonnegative integer entries") weighted = True if multiedges is None: multiedges = False if weighted is None: weighted = False if multiedges is None: multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) if not loops and any(M[i,i] for i in range(M.nrows())): if loops is False: raise ValueError("the adjacency matrix of a non-weighted graph" + " must have zeroes on the diagonal") loops = True if loops is None: loops = False G.allow_loops(loops, check=False) G.allow_multiple_edges(multiedges, check=False) G.add_vertices(range(M.nrows())) if G.is_directed(): pairs = M.nonzero_positions() else: pairs = ((i, j) for i, j in M.nonzero_positions() if i <= j) if weighted: G.add_edges((i, j, M[i][j]) for i, j in pairs) elif multiedges: G.add_edges((i, j) for i, j in pairs for _ in range(int(M[i][j]))) else: G.add_edges((i, j) for i, j in pairs) G._weighted = weighted
def __init__(self, data=None, partition=None, check=True, *args, **kwds): """ Create a bipartite graph. See documentation ``BipartiteGraph?`` for detailed information. EXAMPLE:: sage: P = graphs.PetersenGraph() sage: partition = [range(5), range(5,10)] sage: B = BipartiteGraph(P, partition, check=False) """ if data is None: if partition != None and check: if partition[0] or partition[1]: raise ValueError("Invalid partition.") Graph.__init__(self, **kwds) self.left = set() self.right = set() return # need to turn off partition checking for Graph.__init__() adding # vertices and edges; methods are restored ad the end of big "if" # statement below import types self.add_vertex = types.MethodType(Graph.add_vertex, self, BipartiteGraph) self.add_vertices = types.MethodType(Graph.add_vertices, self, BipartiteGraph) self.add_edge = types.MethodType(Graph.add_edge, self, BipartiteGraph) from sage.structure.element import is_Matrix if isinstance(data, BipartiteGraph): Graph.__init__(self, data, *args, **kwds) self.left = set(data.left) self.right = set(data.right) elif isinstance(data, str): Graph.__init__(self, *args, **kwds) # will call self.load_afile after restoring add_vertex() instance # methods; initialize left and right attributes self.left = set() self.right = set() elif is_Matrix(data): # sanity check for mutually exclusive keywords if kwds.get("multiedges", False) and kwds.get("weighted", False): raise TypeError( "Weighted multi-edge bipartite graphs from reduced " + "adjacency matrix not supported.") Graph.__init__(self, *args, **kwds) ncols = data.ncols() nrows = data.nrows() self.left = set(xrange(ncols)) self.right = set(xrange(ncols, nrows + ncols)) # ensure that the vertices exist even if there # are no associated edges (trac #10356) self.add_vertices(self.left) self.add_vertices(self.right) if kwds.get("multiedges", False): for ii in range(ncols): for jj in range(nrows): if data[jj][ii] != 0: self.add_edges([(ii, jj + ncols)] * data[jj][ii]) elif kwds.get("weighted", False): for ii in range(ncols): for jj in range(nrows): if data[jj][ii] != 0: self.add_edge((ii, jj + ncols, data[jj][ii])) else: for ii in range(ncols): for jj in range(nrows): if data[jj][ii] != 0: self.add_edge((ii, jj + ncols)) elif (isinstance(data, Graph) and partition != None): from copy import copy left, right = partition left = copy(left) right = copy(right) verts = set(left) | set(right) if set(data.vertices()) != verts: data = data.subgraph(list(verts)) Graph.__init__(self, data, *args, **kwds) if check: while len(left) > 0: a = left.pop(0) if len(set(data.neighbors(a)) & set(left)) != 0: raise TypeError( "Input graph is not bipartite with " + "respect to the given partition!") while len(right) > 0: a = right.pop(0) if len(set(data.neighbors(a)) & set(right)) != 0: raise TypeError( "Input graph is not bipartite with " + "respect to the given partition!") else: while len(left) > 0: a = left.pop(0) a_nbrs = set(data.neighbors(a)) & set(left) if len(a_nbrs) != 0: self.delete_edges([(a, b) for b in a_nbrs]) while len(right) > 0: a = right.pop(0) a_nbrs = set(data.neighbors(a)) & set(right) if len(a_nbrs) != 0: self.delete_edges([(a, b) for b in a_nbrs]) self.left, self.right = set(partition[0]), set(partition[1]) elif isinstance(data, Graph): Graph.__init__(self, data, *args, **kwds) try: self.left, self.right = self.bipartite_sets() except StandardError: raise TypeError("Input graph is not bipartite!") else: import networkx Graph.__init__(self, data, *args, **kwds) if isinstance(data, (networkx.MultiGraph, networkx.Graph)): if hasattr(data, "node_type"): # Assume the graph is bipartite self.left = set() self.right = set() for v in data.nodes_iter(): if data.node_type[v] == "Bottom": self.left.add(v) elif data.node_type[v] == "Top": self.right.add(v) else: raise TypeError( "NetworkX node_type defies bipartite " + "assumption (is not 'Top' or 'Bottom')") # make sure we found a bipartition if not (hasattr(self, "left") and hasattr(self, "right")): try: self.left, self.right = self.bipartite_sets() except StandardError: raise TypeError("Input graph is not bipartite!") # restore vertex partition checking self.add_vertex = types.MethodType(BipartiteGraph.add_vertex, self, BipartiteGraph) self.add_vertices = types.MethodType(BipartiteGraph.add_vertices, self, BipartiteGraph) self.add_edge = types.MethodType(BipartiteGraph.add_edge, self, BipartiteGraph) # post-processing if isinstance(data, str): self.load_afile(data) return
def Conic(base_field, F=None, names=None, unique=True): r""" Return the plane projective conic curve defined by ``F`` over ``base_field``. The input form ``Conic(F, names=None)`` is also accepted, in which case the fraction field of the base ring of ``F`` is used as base field. INPUT: - ``base_field`` -- The base field of the conic. - ``names`` -- a list, tuple, or comma separated string of three variable names specifying the names of the coordinate functions of the ambient space `\Bold{P}^3`. If not specified or read off from ``F``, then this defaults to ``'x,y,z'``. - ``F`` -- a polynomial, list, matrix, ternary quadratic form, or list or tuple of 5 points in the plane. If ``F`` is a polynomial or quadratic form, then the output is the curve in the projective plane defined by ``F = 0``. If ``F`` is a polynomial, then it must be a polynomial of degree at most 2 in 2 variables, or a homogeneous polynomial in of degree 2 in 3 variables. If ``F`` is a matrix, then the output is the zero locus of `(x,y,z) F (x,y,z)^t`. If ``F`` is a list of coefficients, then it has length 3 or 6 and gives the coefficients of the monomials `x^2, y^2, z^2` or all 6 monomials `x^2, xy, xz, y^2, yz, z^2` in lexicographic order. If ``F`` is a list of 5 points in the plane, then the output is a conic through those points. - ``unique`` -- Used only if ``F`` is a list of points in the plane. If the conic through the points is not unique, then raise ``ValueError`` if and only if ``unique`` is True OUTPUT: A plane projective conic curve defined by ``F`` over a field. EXAMPLES: Conic curves given by polynomials :: sage: X,Y,Z = QQ['X,Y,Z'].gens() sage: Conic(X^2 - X*Y + Y^2 - Z^2) Projective Conic Curve over Rational Field defined by X^2 - X*Y + Y^2 - Z^2 sage: x,y = GF(7)['x,y'].gens() sage: Conic(x^2 - x + 2*y^2 - 3, 'U,V,W') Projective Conic Curve over Finite Field of size 7 defined by U^2 + 2*V^2 - U*W - 3*W^2 Conic curves given by matrices :: sage: Conic(matrix(QQ, [[1, 2, 0], [4, 0, 0], [7, 0, 9]]), 'x,y,z') Projective Conic Curve over Rational Field defined by x^2 + 6*x*y + 7*x*z + 9*z^2 sage: x,y,z = GF(11)['x,y,z'].gens() sage: C = Conic(x^2+y^2-2*z^2); C Projective Conic Curve over Finite Field of size 11 defined by x^2 + y^2 - 2*z^2 sage: Conic(C.symmetric_matrix(), 'x,y,z') Projective Conic Curve over Finite Field of size 11 defined by x^2 + y^2 - 2*z^2 Conics given by coefficients :: sage: Conic(QQ, [1,2,3]) Projective Conic Curve over Rational Field defined by x^2 + 2*y^2 + 3*z^2 sage: Conic(GF(7), [1,2,3,4,5,6], 'X') Projective Conic Curve over Finite Field of size 7 defined by X0^2 + 2*X0*X1 - 3*X1^2 + 3*X0*X2 - 2*X1*X2 - X2^2 The conic through a set of points :: sage: C = Conic(QQ, [[10,2],[3,4],[-7,6],[7,8],[9,10]]); C Projective Conic Curve over Rational Field defined by x^2 + 13/4*x*y - 17/4*y^2 - 35/2*x*z + 91/4*y*z - 37/2*z^2 sage: C.rational_point() (10 : 2 : 1) sage: C.point([3,4]) (3 : 4 : 1) sage: a=AffineSpace(GF(13),2) sage: Conic([a([x,x^2]) for x in range(5)]) Projective Conic Curve over Finite Field of size 13 defined by x^2 - y*z """ if not (is_IntegralDomain(base_field) or base_field == None): if names is None: names = F F = base_field base_field = None if isinstance(F, (list,tuple)): if len(F) == 1: return Conic(base_field, F[0], names) if names == None: names = 'x,y,z' if len(F) == 5: L=[] for f in F: if isinstance(f, SchemeMorphism_point_affine): C = Sequence(f, universe = base_field) if len(C) != 2: raise TypeError, "points in F (=%s) must be planar"%F C.append(1) elif isinstance(f, SchemeMorphism_point_projective_field): C = Sequence(f, universe = base_field) elif isinstance(f, (list, tuple)): C = Sequence(f, universe = base_field) if len(C) == 2: C.append(1) else: raise TypeError, "F (=%s) must be a sequence of planar " \ "points" % F if len(C) != 3: raise TypeError, "points in F (=%s) must be planar" % F P = C.universe() if not is_IntegralDomain(P): raise TypeError, "coordinates of points in F (=%s) must " \ "be in an integral domain" % F L.append(Sequence([C[0]**2, C[0]*C[1], C[0]*C[2], C[1]**2, C[1]*C[2], C[2]**2], P.fraction_field())) M=Matrix(L) if unique and M.rank() != 5: raise ValueError, "points in F (=%s) do not define a unique " \ "conic" % F con = Conic(base_field, Sequence(M.right_kernel().gen()), names) con.point(F[0]) return con F = Sequence(F, universe = base_field) base_field = F.universe().fraction_field() temp_ring = PolynomialRing(base_field, 3, names) (x,y,z) = temp_ring.gens() if len(F) == 3: return Conic(F[0]*x**2 + F[1]*y**2 + F[2]*z**2) if len(F) == 6: return Conic(F[0]*x**2 + F[1]*x*y + F[2]*x*z + F[3]*y**2 + \ F[4]*y*z + F[5]*z**2) raise TypeError, "F (=%s) must be a sequence of 3 or 6" \ "coefficients" % F if is_QuadraticForm(F): F = F.matrix() if is_Matrix(F) and F.is_square() and F.ncols() == 3: if names == None: names = 'x,y,z' temp_ring = PolynomialRing(F.base_ring(), 3, names) F = vector(temp_ring.gens()) * F * vector(temp_ring.gens()) if not is_MPolynomial(F): raise TypeError, "F (=%s) must be a three-variable polynomial or " \ "a sequence of points or coefficients" % F if F.total_degree() != 2: raise TypeError, "F (=%s) must have degree 2" % F if base_field == None: base_field = F.base_ring() if not is_IntegralDomain(base_field): raise ValueError, "Base field (=%s) must be a field" % base_field base_field = base_field.fraction_field() if names == None: names = F.parent().variable_names() pol_ring = PolynomialRing(base_field, 3, names) if F.parent().ngens() == 2: (x,y,z) = pol_ring.gens() F = pol_ring(F(x/z,y/z)*z**2) if F == 0: raise ValueError, "F must be nonzero over base field %s" % base_field if F.total_degree() != 2: raise TypeError, "F (=%s) must have degree 2 over base field %s" % \ (F, base_field) if F.parent().ngens() == 3: P2 = ProjectiveSpace(2, base_field, names) if is_PrimeFiniteField(base_field): return ProjectiveConic_prime_finite_field(P2, F) if is_FiniteField(base_field): return ProjectiveConic_finite_field(P2, F) if is_RationalField(base_field): return ProjectiveConic_rational_field(P2, F) if is_NumberField(base_field): return ProjectiveConic_number_field(P2, F) return ProjectiveConic_field(P2, F) raise TypeError, "Number of variables of F (=%s) must be 2 or 3" % F
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 is_Matrix(x): try: from sage.structure.element import is_Matrix except ImportError: return False return is_Matrix(x)
def from_adjacency_matrix(G, M, loops=False, multiedges=False, weighted=False): r""" Fill ``G`` with the data of an adjacency matrix. INPUT: - ``G`` -- a :class:`Graph` or :class:`DiGraph` - ``M`` -- an adjacency matrix - ``loops``, ``multiedges``, ``weighted`` -- booleans (default: ``False``); whether to consider the graph as having loops, multiple edges, or weights EXAMPLES:: sage: from sage.graphs.graph_input import from_adjacency_matrix sage: g = Graph() sage: from_adjacency_matrix(g, graphs.PetersenGraph().adjacency_matrix()) sage: g.is_isomorphic(graphs.PetersenGraph()) True """ from sage.structure.element import is_Matrix from sage.rings.integer_ring import ZZ assert is_Matrix(M) # note: the adjacency matrix might be weighted and hence not # necessarily consists of integers if not weighted and M.base_ring() != ZZ: try: M = M.change_ring(ZZ) except TypeError: if weighted is False: raise ValueError( "the adjacency matrix of a non-weighted graph" + " must have only nonnegative integer entries") weighted = True if M.is_sparse(): entries = set(M[i, j] for i, j in M.nonzero_positions()) else: entries = set(M.list()) if not weighted and any(e < 0 for e in entries): if weighted is False: raise ValueError("the adjacency matrix of a non-weighted graph" + " must have only nonnegative integer entries") weighted = True if multiedges is None: multiedges = False if weighted is None: weighted = False if multiedges is None: multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) if not loops and any(M[i, i] for i in range(M.nrows())): if loops is False: raise ValueError("the adjacency matrix of a non-weighted graph" + " must have zeroes on the diagonal") loops = True if loops is None: loops = False G.allow_loops(loops, check=False) G.allow_multiple_edges(multiedges, check=False) G.add_vertices(range(M.nrows())) if G.is_directed(): pairs = M.nonzero_positions() else: pairs = ((i, j) for i, j in M.nonzero_positions() if i <= j) if weighted: G.add_edges((i, j, M[i][j]) for i, j in pairs) elif multiedges: G.add_edges((i, j) for i, j in pairs for _ in range(int(M[i][j]))) else: G.add_edges((i, j) for i, j in pairs) G._weighted = weighted
def linear_transformation(arg0, arg1=None, arg2=None, side='left'): r""" Create a linear transformation from a variety of possible inputs. FORMATS: In the following, ``D`` and ``C`` are vector spaces over the same field that are the domain and codomain (respectively) of the linear transformation. ``side`` is a keyword that is either 'left' or 'right'. When a matrix is used to specify a linear transformation, as in the first two call formats below, you may specify if the function is given by matrix multiplication with the vector on the left, or the vector on the right. The default is 'left'. Internally representations are always carried as the 'left' version, and the default text representation is this version. However, the matrix representation may be obtained as either version, no matter how it is created. - ``linear_transformation(A, side='left')`` Where ``A`` is a matrix. The domain and codomain are inferred from the dimension of the matrix and the base ring of the matrix. The base ring must be a field, or have its fraction field implemented in Sage. - ``linear_transformation(D, C, A, side='left')`` ``A`` is a matrix that behaves as above. However, now the domain and codomain are given explicitly. The matrix is checked for compatibility with the domain and codomain. Additionally, the domain and codomain may be supplied with alternate ("user") bases and the matrix is interpreted as being a representation relative to those bases. - ``linear_transformation(D, C, f)`` ``f`` is any function that can be applied to the basis elements of the domain and that produces elements of the codomain. The linear transformation returned is the unique linear transformation that extends this mapping on the basis elements. ``f`` may come from a function defined by a Python ``def`` statement, or may be defined as a ``lambda`` function. Alternatively, ``f`` may be specified by a callable symbolic function, see the examples below for a demonstration. - ``linear_transformation(D, C, images)`` ``images`` is a list, or tuple, of codomain elements, equal in number to the size of the basis of the domain. Each basis element of the domain is mapped to the corresponding element of the ``images`` list, and the linear transformation returned is the unique linear transformation that extends this mapping. OUTPUT: A linear transformation described by the input. This is a "vector space morphism", an object of the class :class:`sage.modules.vector_space_morphism`. EXAMPLES: We can define a linear transformation with just a matrix, understood to act on a vector placed on one side or the other. The field for the vector spaces used as domain and codomain is obtained from the base ring of the matrix, possibly promoting to a fraction field. :: sage: A = matrix(ZZ, [[1, -1, 4], [2, 0, 5]]) sage: phi = linear_transformation(A) sage: phi Vector space morphism represented by the matrix: [ 1 -1 4] [ 2 0 5] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 3 over Rational Field sage: phi([1/2, 5]) (21/2, -1/2, 27) sage: B = matrix(Integers(7), [[1, 2, 1], [3, 5, 6]]) sage: rho = linear_transformation(B, side='right') sage: rho Vector space morphism represented by the matrix: [1 3] [2 5] [1 6] Domain: Vector space of dimension 3 over Ring of integers modulo 7 Codomain: Vector space of dimension 2 over Ring of integers modulo 7 sage: rho([2, 4, 6]) (2, 6) We can define a linear transformation with a matrix, while explicitly giving the domain and codomain. Matrix entries will be coerced into the common field of scalars for the vector spaces. :: sage: D = QQ^3 sage: C = QQ^2 sage: A = matrix([[1, 7], [2, -1], [0, 5]]) sage: A.parent() Full MatrixSpace of 3 by 2 dense matrices over Integer Ring sage: zeta = linear_transformation(D, C, A) sage: zeta.matrix().parent() Full MatrixSpace of 3 by 2 dense matrices over Rational Field sage: zeta Vector space morphism represented by the matrix: [ 1 7] [ 2 -1] [ 0 5] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field Matrix representations are relative to the bases for the domain and codomain. :: sage: u = vector(QQ, [1, -1]) sage: v = vector(QQ, [2, 3]) sage: D = (QQ^2).subspace_with_basis([u, v]) sage: x = vector(QQ, [2, 1]) sage: y = vector(QQ, [-1, 4]) sage: C = (QQ^2).subspace_with_basis([x, y]) sage: A = matrix(QQ, [[2, 5], [3, 7]]) sage: psi = linear_transformation(D, C, A) sage: psi Vector space morphism represented by the matrix: [2 5] [3 7] Domain: Vector space of degree 2 and dimension 2 over Rational Field User basis matrix: [ 1 -1] [ 2 3] Codomain: Vector space of degree 2 and dimension 2 over Rational Field User basis matrix: [ 2 1] [-1 4] sage: psi(u) == 2*x + 5*y True sage: psi(v) == 3*x + 7*y True Functions that act on the domain may be used to compute images of the domain's basis elements, and this mapping can be extended to a unique linear transformation. The function may be a Python function (via ``def`` or ``lambda``) or a Sage symbolic function. :: sage: def g(x): ....: return vector(QQ, [2*x[0]+x[2], 5*x[1]]) sage: phi = linear_transformation(QQ^3, QQ^2, g) sage: phi Vector space morphism represented by the matrix: [2 0] [0 5] [1 0] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field sage: f = lambda x: vector(QQ, [2*x[0]+x[2], 5*x[1]]) sage: rho = linear_transformation(QQ^3, QQ^2, f) sage: rho Vector space morphism represented by the matrix: [2 0] [0 5] [1 0] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field sage: x, y, z = var('x y z') sage: h(x, y, z) = [2*x + z, 5*y] sage: zeta = linear_transformation(QQ^3, QQ^2, h) sage: zeta Vector space morphism represented by the matrix: [2 0] [0 5] [1 0] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field sage: phi == rho True sage: rho == zeta True We create a linear transformation relative to non-standard bases, and capture its representation relative to standard bases. With this, we can build functions that create the same linear transformation relative to the nonstandard bases. :: sage: u = vector(QQ, [1, -1]) sage: v = vector(QQ, [2, 3]) sage: D = (QQ^2).subspace_with_basis([u, v]) sage: x = vector(QQ, [2, 1]) sage: y = vector(QQ, [-1, 4]) sage: C = (QQ^2).subspace_with_basis([x, y]) sage: A = matrix(QQ, [[2, 5], [3, 7]]) sage: psi = linear_transformation(D, C, A) sage: rho = psi.restrict_codomain(QQ^2).restrict_domain(QQ^2) sage: rho.matrix() [ -4/5 97/5] [ 1/5 -13/5] sage: f = lambda x: vector(QQ, [(-4/5)*x[0] + (1/5)*x[1], (97/5)*x[0] + (-13/5)*x[1]]) sage: psi = linear_transformation(D, C, f) sage: psi.matrix() [2 5] [3 7] sage: s, t = var('s t') sage: h(s, t) = [(-4/5)*s + (1/5)*t, (97/5)*s + (-13/5)*t] sage: zeta = linear_transformation(D, C, h) sage: zeta.matrix() [2 5] [3 7] Finally, we can give an explicit list of images for the basis elements of the domain. :: sage: x = polygen(QQ) sage: F.<a> = NumberField(x^3+x+1) sage: u = vector(F, [1, a, a^2]) sage: v = vector(F, [a, a^2, 2]) sage: w = u + v sage: D = F^3 sage: C = F^3 sage: rho = linear_transformation(D, C, [u, v, w]) sage: rho.matrix() [ 1 a a^2] [ a a^2 2] [ a + 1 a^2 + a a^2 + 2] sage: C = (F^3).subspace_with_basis([u, v]) sage: D = (F^3).subspace_with_basis([u, v]) sage: psi = linear_transformation(C, D, [u+v, u-v]) sage: psi.matrix() [ 1 1] [ 1 -1] TESTS: We test some bad inputs. First, the wrong things in the wrong places. :: sage: linear_transformation('junk') Traceback (most recent call last): ... TypeError: first argument must be a matrix or a vector space, not junk sage: linear_transformation(QQ^2, QQ^3, 'stuff') Traceback (most recent call last): ... TypeError: third argument must be a matrix, function, or list of images, not stuff sage: linear_transformation(QQ^2, 'garbage') Traceback (most recent call last): ... TypeError: if first argument is a vector space, then second argument must be a vector space, not garbage sage: linear_transformation(QQ^2, Integers(7)^2) Traceback (most recent call last): ... TypeError: vector spaces must have the same field of scalars, not Rational Field and Ring of integers modulo 7 Matrices must be over a field (or a ring that can be promoted to a field), and of the right size. :: sage: linear_transformation(matrix(Integers(6), [[2, 3],[4, 5]])) Traceback (most recent call last): ... TypeError: matrix must have entries from a field, or a ring with a fraction field, not Ring of integers modulo 6 sage: A = matrix(QQ, 3, 4, range(12)) sage: linear_transformation(QQ^4, QQ^4, A) Traceback (most recent call last): ... TypeError: domain dimension is incompatible with matrix size sage: linear_transformation(QQ^3, QQ^3, A, side='right') Traceback (most recent call last): ... TypeError: domain dimension is incompatible with matrix size sage: linear_transformation(QQ^3, QQ^3, A) Traceback (most recent call last): ... TypeError: codomain dimension is incompatible with matrix size sage: linear_transformation(QQ^4, QQ^4, A, side='right') Traceback (most recent call last): ... TypeError: codomain dimension is incompatible with matrix size Lists of images can be of the wrong number, or not really elements of the codomain. :: sage: linear_transformation(QQ^3, QQ^2, [vector(QQ, [1,2])]) Traceback (most recent call last): ... ValueError: number of images should equal the size of the domain's basis (=3), not 1 sage: C = (QQ^2).subspace_with_basis([vector(QQ, [1,1])]) sage: linear_transformation(QQ^1, C, [vector(QQ, [1,2])]) Traceback (most recent call last): ... ArithmeticError: some proposed image is not in the codomain, because element [1, 2] is not in free module Functions may not apply properly to domain elements, or return values outside the codomain. :: sage: f = lambda x: vector(QQ, [x[0], x[4]]) sage: linear_transformation(QQ^3, QQ^2, f) Traceback (most recent call last): ... ValueError: function cannot be applied properly to some basis element because vector index out of range sage: f = lambda x: vector(QQ, [x[0], x[1]]) sage: C = (QQ^2).span([vector(QQ, [1, 1])]) sage: linear_transformation(QQ^2, C, f) Traceback (most recent call last): ... ArithmeticError: some image of the function is not in the codomain, because element [1, 0] is not in free module A Sage symbolic function can come in a variety of forms that are not representative of a linear transformation. :: sage: x, y = var('x, y') sage: f(x, y) = [y, x, y] sage: linear_transformation(QQ^3, QQ^3, f) Traceback (most recent call last): ... ValueError: symbolic function has the wrong number of inputs for domain sage: linear_transformation(QQ^2, QQ^2, f) Traceback (most recent call last): ... ValueError: symbolic function has the wrong number of outputs for codomain sage: x, y = var('x y') sage: f(x, y) = [y, x*y] sage: linear_transformation(QQ^2, QQ^2, f) Traceback (most recent call last): ... ValueError: symbolic function must be linear in all the inputs: unable to convert y to a rational sage: x, y = var('x y') sage: f(x, y) = [x, 2*y] sage: C = (QQ^2).span([vector(QQ, [1, 1])]) sage: linear_transformation(QQ^2, C, f) Traceback (most recent call last): ... ArithmeticError: some image of the function is not in the codomain, because element [1, 0] is not in free module """ from sage.matrix.constructor import matrix from sage.modules.module import is_VectorSpace from sage.modules.free_module import VectorSpace from sage.categories.homset import Hom from sage.symbolic.ring import SR from sage.modules.vector_callable_symbolic_dense import Vector_callable_symbolic_dense from inspect import isfunction if not side in ['left', 'right']: raise ValueError("side must be 'left' or 'right', not {0}".format(side)) if not (is_Matrix(arg0) or is_VectorSpace(arg0)): raise TypeError('first argument must be a matrix or a vector space, not {0}'.format(arg0)) if is_Matrix(arg0): R = arg0.base_ring() if not R.is_field(): try: R = R.fraction_field() except (NotImplementedError, TypeError): msg = 'matrix must have entries from a field, or a ring with a fraction field, not {0}' raise TypeError(msg.format(R)) if side == 'right': arg0 = arg0.transpose() side = 'left' arg2 = arg0 arg0 = VectorSpace(R, arg2.nrows()) arg1 = VectorSpace(R, arg2.ncols()) elif is_VectorSpace(arg0): if not is_VectorSpace(arg1): msg = 'if first argument is a vector space, then second argument must be a vector space, not {0}' raise TypeError(msg.format(arg1)) if arg0.base_ring() != arg1.base_ring(): msg = 'vector spaces must have the same field of scalars, not {0} and {1}' raise TypeError(msg.format(arg0.base_ring(), arg1.base_ring())) # Now arg0 = domain D, arg1 = codomain C, and # both are vector spaces with common field of scalars # use these to make a VectorSpaceHomSpace # arg2 might be a matrix that began in arg0 D = arg0 C = arg1 H = Hom(D, C, category=None) # Examine arg2 as the "rule" for the linear transformation # Pass on matrices, Python functions and lists to homspace call # Convert symbolic function here, to a matrix if is_Matrix(arg2): if side == 'right': arg2 = arg2.transpose() elif isinstance(arg2, (list, tuple)): pass elif isfunction(arg2): pass elif isinstance(arg2, Vector_callable_symbolic_dense): args = arg2.parent().base_ring()._arguments exprs = arg2.change_ring(SR) m = len(args) n = len(exprs) if m != D.degree(): raise ValueError('symbolic function has the wrong number of inputs for domain') if n != C.degree(): raise ValueError('symbolic function has the wrong number of outputs for codomain') arg2 = [[e.coefficient(a) for e in exprs] for a in args] try: arg2 = matrix(D.base_ring(), m, n, arg2) except TypeError as e: msg = 'symbolic function must be linear in all the inputs:\n' + e.args[0] raise ValueError(msg) # have matrix with respect to standard bases, now consider user bases images = [v*arg2 for v in D.basis()] try: arg2 = matrix([C.coordinates(C(a)) for a in images]) except (ArithmeticError, TypeError) as e: msg = 'some image of the function is not in the codomain, because\n' + e.args[0] raise ArithmeticError(msg) else: msg = 'third argument must be a matrix, function, or list of images, not {0}' raise TypeError(msg.format(arg2)) # arg2 now compatible with homspace H call method # __init__ will check matrix sizes versus domain/codomain dimensions return H(arg2)
def _element_constructor_(self, *args, **kwds): r""" TESTS:: sage: from flatsurf.geometry.similarity import SimilarityGroup sage: S = SimilarityGroup(QQ) sage: S((1,1)) # translation (x, y) |-> (x + 1, y + 1) sage: V = QQ^2 sage: S(V((1,-1))) (x, y) |-> (x + 1, y - 1) sage: S(vector((1,1))) (x, y) |-> (x + 1, y + 1) """ if len(args) == 1: x = args[0] else: x = args a = self._field.one() b = s = t = self._field.zero() sign = ZZ_1 # TODO: 2x2 and 3x3 matrix input if isinstance(x, (tuple,list)): if len(x) == 2: s,t = map(self._field, x) elif len(x) == 4: a,b,s,t = map(self._field, x) elif len(x) == 5: a,b,s,t = map(self._field, x[:4]) sign = ZZ(x[4]) else: raise ValueError("can not construct a similarity from a list of length {}".format(len(x))) elif is_Matrix(x): # a -sb # b sa if x.nrows() == x.ncols() == 2: a,c,b,d = x.list() if a == d and b == -c: sign = ZZ_1 elif a == -d and b == c: sign = ZZ_m1 else: raise ValueError("not a similarity matrix") elif x.nrows() == x.ncols() == 3: raise NotImplementedError else: raise ValueError("invalid dimension for matrix input") elif isinstance(x, FreeModuleElement): if len(x) == 2: if x.base_ring() is self._field: s,t = x else: s,t = map(self._field, x) else: raise ValueError("invalid dimension for vector input") else: p = parent(x) if self._field.has_coerce_map_from(p): a = self._field(x) else: raise ValueError if (a*a + b*b).is_zero(): raise ValueError("not invertible") return self.element_class(self, a, b, s, t, sign)
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