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() """ if is_Matrix(A) and A.nrows() == A.ncols() == parent.degree() + 1: g = A A = g.submatrix(0, 0, 2, 2) d = parent.degree() 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 __init__(self, A, b=0, parent=None, 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() """ if is_Matrix(A) and A.nrows() == A.ncols() == parent.degree()+1: g = A A = g.submatrix(0,0,2,2) d = parent.degree() 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 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) -- whether to consider the graph as having loops, multiple edges, or weights. Set to ``False`` by default. 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.matrix.matrix 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 __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 _acted_upon_(self, g, self_on_left): r""" Implements 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 and g.nrows() == 2): a, b, c, d = g.list() return Cusp(a*self.__a + b*self.__b, c*self.__a + d*self.__b)
def __call__(self, x): """ EXAMPLES:: sage: W = WeylGroup(['A',2]) sage: W(1) [1 0 0] [0 1 0] [0 0 1] :: sage: W(2) Traceback (most recent call last): ... TypeError: no way to coerce element into self. sage: W2 = WeylGroup(['A',3]) sage: W(1) in W2 # indirect doctest False """ if isinstance(x, self.element_class) and x.parent() is self: return x from sage.matrix.matrix import is_Matrix if not (x in ZZ or is_Matrix(x)): # this should be handled by self.matrix_space()(x) raise TypeError, "no way to coerce element into self" M = self.matrix_space()(x) # This is really bad, especially for infinite groups! # TODO: compute the image of rho, compose by s_i until back in # the fundamental chamber. Return True iff the matrix is the identity g = self._element_constructor_(M) if not gap(g) in gap(self): raise TypeError, "no way to coerce element into self." return g
def print_obj(out_stream, obj): """ Print an object. This function is used internally by the displayhook. EXAMPLES:: sage: import sage.misc.displayhook, sys For most objects, printing is done simply using their repr:: sage: sage.misc.displayhook.print_obj(sys.stdout, 'Hello, world!') 'Hello, world!' sage: sage.misc.displayhook.print_obj(sys.stdout, (1, 2, 3, 4)) (1, 2, 3, 4) We demonstrate the special format for lists of matrices:: sage: sage.misc.displayhook.print_obj(sys.stdout, \ [matrix([[1], [2]]), matrix([[3], [4]])]) [ [1] [3] [2], [4] ] """ # We only apply the special formatting to lists (or tuples) where the first # element is a matrix. This should cover most cases. if isinstance(obj, (tuple, list)): if len(obj) > 0 and is_Matrix(obj[0]): if _check_tall_list_and_print(out_stream, obj): return print >>out_stream, `obj`
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 __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 for b in A.table()]) self._basis_matrix = B.echelon_form().image().basis_matrix() Ideal_generic.__init__(self, A, gens)
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 _is_even_symmetric_matrix_(self, A, R=None): """ Tests if a matrix is symmetric, defined over R, and has even diagonal in R. INPUT: A -- matrix R -- ring EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [2,3,5]) sage: A = Q.matrix() sage: A [ 4 3] [ 3 10] sage: Q._is_even_symmetric_matrix_(A) True sage: A[0,0] = 1 sage: Q._is_even_symmetric_matrix_(A) False """ if not is_Matrix(A): raise TypeError("A is not a matrix.") ring_coerce_test = True if R == None: ## This allows us to omit the ring from the variables, and take it from the matrix R = A.base_ring() ring_coerce_test = False if not isinstance(R, Ring): raise TypeError("R is not a ring.") if not A.is_square(): return False ## Test that the matrix is symmetric n = A.nrows() for i in range(n): for j in range(i+1, n): if A[i,j] != A[j,i]: return False ## Test that all entries coerce to R if not ((A.base_ring() == R) or (ring_coerce_test == True)): try: for i in range(n): for j in range(i, n): x = R(A[i,j]) except Exception: return False ## Test that the diagonal is even (if 1/2 isn't in R) if not R(2).is_unit(): for i in range(n): if not is_even(R(A[i,i])): return False return True
def __init__(self, A, elt=None, check=True): """ TESTS:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1,0], [0,1]]), Matrix([[0,1], [0,0]])]) sage: A(QQ(4)) Traceback (most recent call last): ... TypeError: elt should be a vector, a matrix, or an element of the base field sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) sage: elt = B(Matrix([[1,1], [-1,1]])); elt e0 + e1 sage: TestSuite(elt).run() sage: B(Matrix([[0,1], [1,0]])) Traceback (most recent call last): ... ValueError: matrix does not define an element of the algebra """ AlgebraElement.__init__(self, A) k = A.base_ring() n = A.degree() if elt is None: self._vector = vector(k, n) self._matrix = Matrix(k, n) else: if isinstance(elt, int): elt = Integer(elt) elif isinstance(elt, list): elt = vector(elt) if A == elt.parent(): self._vector = elt._vector.base_extend(k) self._matrix = elt._matrix.base_extend(k) elif k.has_coerce_map_from(elt.parent()): e = k(elt) if e == 0: self._vector = vector(k, n) self._matrix = Matrix(k, n) elif A.is_unitary(): self._vector = A._one * e self._matrix = Matrix.identity(k, n) * e else: raise TypeError("algebra is not unitary") elif is_Vector(elt): self._vector = elt.base_extend(k) self._matrix = Matrix( k, sum([elt[i] * A.table()[i] for i in range(n)])) elif is_Matrix(elt): if not A.is_unitary(): raise TypeError("algebra is not unitary") self._vector = A._one * elt if not check or sum( [self._vector[i] * A.table()[i] for i in range(n)]) == elt: self._matrix = elt else: raise ValueError( "matrix does not define an element of the algebra") else: raise TypeError("elt should be a vector, a matrix, " + "or an element of the base field")
def print_obj(out_stream, obj): """ Print an object. This function is used internally by the displayhook. EXAMPLES:: sage: import sage.misc.displayhook, sys For most objects, printing is done simply using their repr:: sage: sage.misc.displayhook.print_obj(sys.stdout, 'Hello, world!') 'Hello, world!' sage: sage.misc.displayhook.print_obj(sys.stdout, (1, 2, 3, 4)) (1, 2, 3, 4) We demonstrate the special format for lists of matrices:: sage: sage.misc.displayhook.print_obj(sys.stdout, \ [matrix([[1], [2]]), matrix([[3], [4]])]) [ [1] [3] [2], [4] ] """ # We only apply the special formatting to lists (or tuples) where the first # element is a matrix. This should cover most cases. if isinstance(obj, (tuple, list)): if len(obj) > 0 and is_Matrix(obj[0]): if _check_tall_list_and_print(out_stream, obj): return print >> out_stream, ` obj `
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 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 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_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) -- whether to consider the graph as having loops, multiple edges, or weights. Set to ``False`` by default. EXAMPLE:: 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.matrix.matrix 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 __init__(self, A, elt=None, check=True): """ TESTS:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1,0], [0,1]]), Matrix([[0,1], [0,0]])]) sage: A(QQ(4)) Traceback (most recent call last): ... TypeError: elt should be a vector, a matrix, or an element of the base field sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) sage: elt = B(Matrix([[1,1], [-1,1]])); elt e0 + e1 sage: TestSuite(elt).run() sage: B(Matrix([[0,1], [1,0]])) Traceback (most recent call last): ... ValueError: matrix does not define an element of the algebra """ AlgebraElement.__init__(self, A) k = A.base_ring() n = A.degree() if elt is None: self._vector = vector(k, n) self._matrix = Matrix(k, n) else: if isinstance(elt, int): elt = Integer(elt) elif isinstance(elt, list): elt = vector(elt) if A == elt.parent(): self._vector = elt._vector.base_extend(k) self._matrix = elt._matrix.base_extend(k) elif k.has_coerce_map_from(elt.parent()): e = k(elt) if e == 0: self._vector = vector(k, n) self._matrix = Matrix(k, n) elif A.is_unitary(): self._vector = A._one * e self._matrix = Matrix.identity(k, n) * e else: raise TypeError("algebra is not unitary") elif is_Vector(elt): self._vector = elt.base_extend(k) self._matrix = Matrix(k, sum([elt[i] * A.table()[i] for i in xrange(n)])) elif is_Matrix(elt): if not A.is_unitary(): raise TypeError("algebra is not unitary") self._vector = A._one * elt if not check or sum([self._vector[i]*A.table()[i] for i in xrange(n)]) == elt: self._matrix = elt else: raise ValueError("matrix does not define an element of the algebra") else: raise TypeError("elt should be a vector, a matrix, " + "or an element of the base field")
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) -- whether to consider the graph as having loops, multiple edges, or weights. Set to ``False`` by default. 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.matrix.matrix 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_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 EXAMPLE:: 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.matrix.matrix 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 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.matrix.matrix 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 __init__(self, k, table, names='e', assume_associative=False, category=None): """ TESTS:: sage: A = FiniteDimensionalAlgebra(QQ, []) sage: A Finite-dimensional algebra of degree 0 over Rational Field sage: type(A) <class 'sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra.FiniteDimensionalAlgebra_with_category'> sage: TestSuite(A).run() sage: B = FiniteDimensionalAlgebra(GF(7), [Matrix([1])]) sage: B Finite-dimensional algebra of degree 1 over Finite Field of size 7 sage: TestSuite(B).run() sage: C = FiniteDimensionalAlgebra(CC, [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: C Finite-dimensional algebra of degree 2 over Complex Field with 53 bits of precision sage: TestSuite(C).run() sage: FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]])]) Traceback (most recent call last): ... ValueError: input is not a multiplication table sage: D.<a,b> = FiniteDimensionalAlgebra(RR, [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [-1, 0]])]) sage: D.gens() (a, b) sage: E = FiniteDimensionalAlgebra(QQ, [Matrix([0])]) sage: E.gens() (e,) """ n = len(table) self._table = [b.base_extend(k) for b in table] if not all([is_Matrix(b) and b.dimensions() == (n, n) for b in table]): raise ValueError("input is not a multiplication table") self._assume_associative = assume_associative # No further validity checks necessary! if category is None: category = FiniteDimensionalAlgebrasWithBasis(k) Algebra.__init__(self, base_ring=k, names=names, category=category)
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, M, check=True, convert=True): r""" Element of a matrix group over a generic ring. The group elements are implemented as Sage matrices. INPUT: - ``M`` -- a matrix. - ``parent`` -- the parent. - ``check`` -- bool (default: ``True``). If true does some type checking. - ``convert`` -- bool (default: ``True``). If true convert ``M`` to the right matrix space. TESTS:: sage: MS = MatrixSpace(GF(3),2,2) sage: G = MatrixGroup(MS([[1,0],[0,1]]), MS([[1,1],[0,1]])) sage: G.gen(0) [1 0] [0 1] sage: g = G.random_element() sage: TestSuite(g).run() """ if isinstance(M, GapElement): ElementLibGAP.__init__(self, parent, M) return if convert: M = parent.matrix_space()(M) from sage.libs.gap.libgap import libgap M_gap = libgap(M) if check: if not is_Matrix(M): raise TypeError("M must be a matrix") if M.parent() is not parent.matrix_space(): raise TypeError("M must be a in the matrix space of the group") parent._check_matrix(M, M_gap) ElementLibGAP.__init__(self, parent, M_gap)
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.matrix.matrix 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 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, M, parent, check=True, convert=True): r""" Element of a matrix group over a generic ring. The group elements are implemented as Sage matrices. INPUT: - ``M`` -- a matrix. - ``parent`` -- the parent. - ``check`` -- bool (default: ``True``). If true does some type checking. - ``convert`` -- bool (default: ``True``). If true convert ``M`` to the right matrix space. TESTS:: sage: MS = MatrixSpace(GF(3),2,2) sage: G = MatrixGroup(MS([[1,0],[0,1]]), MS([[1,1],[0,1]])) sage: G.gen(0) [1 0] [0 1] sage: g = G.random_element() sage: TestSuite(g).run() """ if isinstance(M, GapElement): ElementLibGAP.__init__(self, M, parent) return if convert: M = parent.matrix_space()(M) from sage.libs.gap.libgap import libgap M_gap = libgap(M) if check: if not is_Matrix(M): raise TypeError('M must be a matrix') if M.parent() is not parent.matrix_space(): raise TypeError('M must be a in the matrix space of the group') parent._check_matrix(M, M_gap) ElementLibGAP.__init__(self, M_gap, parent)
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.matrix.matrix 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 xrange(n): if M[i,i] != 2: return False for j in xrange(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, M, check=True, convert=True): r""" Element of a matrix group over a generic ring. The group elements are implemented as Sage matrices. INPUT: - ``M`` -- a matrix. - ``parent`` -- the parent. - ``check`` -- bool (default: ``True``). If true does some type checking. - ``convert`` -- bool (default: ``True``). If true convert ``M`` to the right matrix space. TESTS:: sage: F = GF(3); MS = MatrixSpace(F,2,2) sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] sage: G = MatrixGroup(gens) sage: g = G.random_element() sage: TestSuite(g).run() """ if convert: M = parent.matrix_space()(M) if check: if not is_Matrix(M): raise TypeError("M must be a matrix") if M.parent() is not parent.matrix_space(): raise TypeError("M must be a in the matrix space of the group") parent._check_matrix(M) super(MatrixGroupElement_generic, self).__init__(parent) if M.is_immutable(): self._matrix = M else: self._matrix = M.__copy__() self._matrix.set_immutable()
def __init__(self, M, parent, check=True, convert=True): r""" Element of a matrix group over a generic ring. The group elements are implemented as Sage matrices. INPUT: - ``M`` -- a matrix. - ``parent`` -- the parent. - ``check`` -- bool (default: ``True``). If true does some type checking. - ``convert`` -- bool (default: ``True``). If true convert ``M`` to the right matrix space. TESTS:: sage: F = GF(3); MS = MatrixSpace(F,2,2) sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] sage: G = MatrixGroup(gens) sage: g = G.random_element() sage: TestSuite(g).run() """ if convert: M = parent.matrix_space()(M) if check: if not is_Matrix(M): raise TypeError('M must be a matrix') if M.parent() is not parent.matrix_space(): raise TypeError('M must be a in the matrix space of the group') parent._check_matrix(M) super(MatrixGroupElement_generic, self).__init__(parent) if M.is_immutable(): self._matrix = M else: self._matrix = M.__copy__() self._matrix.set_immutable()
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.matrix.matrix 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 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.matrix.matrix 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 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 Also one can input an index_set by The edge multiplicities are encoded as edge labels. This uses the convention in Hong and Kang, Kac, Fulton 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 matrix_plot(mat, **options): r""" A plot of a given matrix or 2D array. If the matrix is dense, each matrix element is given a different color value depending on its relative size compared to the other elements in the matrix. If the matrix is sparse, colors only indicate whether an element is nonzero or zero, so the plot represents the sparsity pattern of the matrix. The tick marks drawn on the frame axes denote the row numbers (vertical ticks) and the column numbers (horizontal ticks) of the matrix. INPUT: - ``mat`` - a 2D matrix or array The following input must all be passed in as named parameters, if default not used: - ``cmap`` - a colormap (default: 'gray'), the name of a predefined colormap, a list of colors, or an instance of a matplotlib Colormap. Type: ``import matplotlib.cm; matplotlib.cm.datad.keys()`` for available colormap names. - ``colorbar`` -- boolean (default: False) Show a colorbar or not (dense matrices only). The following options are used to adjust the style and placement of colorbars. They have no effect if a colorbar is not shown. - ``colorbar_orientation`` -- string (default: 'vertical'), controls placement of the colorbar, can be either 'vertical' or 'horizontal' - ``colorbar_format`` -- a format string, this is used to format the colorbar labels. - ``colorbar_options`` -- a dictionary of options for the matplotlib colorbar API. Documentation for the :mod:`matplotlib.colorbar` module has details. - ``norm`` - If None (default), the value range is scaled to the interval [0,1]. If 'value', then the actual value is used with no scaling. A :class:`matplotlib.colors.Normalize` instance may also passed. - ``vmin`` - The minimum value (values below this are set to this value) - ``vmax`` - The maximum value (values above this are set to this value) - ``origin`` - If 'upper' (default), the first row of the matrix is on the top of the graph. If 'lower', the first row is on the bottom of the graph. - ``subdivisions`` - If True, plot the subdivisions of the matrix as lines. - ``subdivision_boundaries`` - a list of lists in the form ``[row_subdivisions, column_subdivisions]``, which specifies the row and column subdivisions to use. If not specified, defaults to the matrix subdivisions - ``subdivision_style`` - a dictionary of properties passed on to the :func:`~sage.plot.line.line2d` command for plotting subdivisions. If this is a two-element list or tuple, then it specifies the styles of row and column divisions, respectively. EXAMPLES: A matrix over `\ZZ` colored with different grey levels:: sage: matrix_plot(matrix([[1,3,5,1],[2,4,5,6],[1,3,5,7]])) Here we make a random matrix over `\RR` and use ``cmap='hsv'`` to color the matrix elements different RGB colors:: sage: matrix_plot(random_matrix(RDF, 50), cmap='hsv') By default, entries are scaled to the interval [0,1] before determining colors from the color map. That means the two plots below are the same:: sage: P = matrix_plot(matrix(2,[1,1,3,3])) sage: Q = matrix_plot(matrix(2,[2,2,3,3])) sage: P; Q However, we can specify which values scale to 0 or 1 with the ``vmin`` and ``vmax`` parameters (values outside the range are clipped). The two plots below are now distinguished:: sage: P = matrix_plot(matrix(2,[1,1,3,3]), vmin=0, vmax=3, colorbar=True) sage: Q = matrix_plot(matrix(2,[2,2,3,3]), vmin=0, vmax=3, colorbar=True) sage: P; Q We can also specify a norm function of 'value', which means that there is no scaling performed:: sage: matrix_plot(random_matrix(ZZ,10)*.05, norm='value', colorbar=True) Matrix subdivisions can be plotted as well:: sage: m=random_matrix(RR,10) sage: m.subdivide([2,4],[6,8]) sage: matrix_plot(m, subdivisions=True, subdivision_style=dict(color='red',thickness=3)) You can also specify your own subdivisions and separate styles for row or column subdivisions:: sage: m=random_matrix(RR,10) sage: matrix_plot(m, subdivisions=True, subdivision_boundaries=[[2,4],[6,8]], subdivision_style=[dict(color='red',thickness=3),dict(linestyle='--',thickness=6)]) Generally matrices are plotted with the (0,0) entry in the upper left. However, sometimes if we are plotting an image, we'd like the (0,0) entry to be in the lower left. We can do that with the ``origin`` argument:: sage: matrix_plot(identity_matrix(100), origin='lower') Another random plot, but over `\GF{389}`:: sage: m = random_matrix(GF(389), 10) sage: matrix_plot(m, cmap='Oranges') It also works if you lift it to the polynomial ring:: sage: matrix_plot(m.change_ring(GF(389)['x']), cmap='Oranges') We have several options for colorbars:: sage: matrix_plot(random_matrix(RDF, 50), colorbar=True, colorbar_orientation='horizontal') :: sage: matrix_plot(random_matrix(RDF, 50), colorbar=True, colorbar_format='%.3f') The length of a color bar and the length of the adjacent matrix plot dimension may be quite different. This example shows how to adjust the length of the colorbar by passing a dictionary of options to the matplotlib colorbar routines. :: sage: m = random_matrix(ZZ, 40, 80, x=-10, y=10) sage: m.plot(colorbar=True, colorbar_orientation='vertical', ... colorbar_options={'shrink':0.50}) Here we plot a random sparse matrix:: sage: sparse = matrix(dict([((randint(0, 10), randint(0, 10)), 1) for i in xrange(100)])) sage: matrix_plot(sparse) :: sage: A=random_matrix(ZZ,100000,density=.00001,sparse=True) sage: matrix_plot(A,marker=',') As with dense matrices, sparse matrix entries are automatically converted to floating point numbers before plotting. Thus the following works:: sage: b=random_matrix(GF(2),200,sparse=True,density=0.01) sage: matrix_plot(b) While this returns an error:: sage: b=random_matrix(CDF,200,sparse=True,density=0.01) sage: matrix_plot(b) Traceback (most recent call last): ... ValueError: can not convert entries to floating point numbers To plot the absolute value of a complex matrix, use the ``apply_map`` method:: sage: b=random_matrix(CDF,200,sparse=True,density=0.01) sage: matrix_plot(b.apply_map(abs)) Plotting lists of lists also works:: sage: matrix_plot([[1,3,5,1],[2,4,5,6],[1,3,5,7]]) As does plotting of NumPy arrays:: sage: import numpy sage: matrix_plot(numpy.random.rand(10, 10)) A plot title can be added to the matrix plot.:: sage: matrix_plot(identity_matrix(50), origin='lower', title='not identity') The title position is adjusted upwards if the ``origin`` keyword is set to ``"upper"`` (this is the default).:: sage: matrix_plot(identity_matrix(50), title='identity') TESTS:: sage: P.<t> = RR[] sage: matrix_plot(random_matrix(P, 3, 3)) Traceback (most recent call last): ... TypeError: cannot coerce nonconstant polynomial to float :: sage: matrix_plot([1,2,3]) Traceback (most recent call last): ... TypeError: mat must be a Matrix or a two dimensional array :: sage: matrix_plot([[sin(x), cos(x)], [1, 0]]) Traceback (most recent call last): ... TypeError: mat must be a Matrix or a two dimensional array Test that sparse matrices also work with subdivisions:: sage: matrix_plot(sparse, subdivisions=True, subdivision_boundaries=[[2,4],[6,8]]) """ import numpy as np import scipy.sparse as scipysparse from sage.plot.all import Graphics from sage.matrix.matrix import is_Matrix from sage.rings.all import RDF orig_mat = mat if is_Matrix(mat): sparse = mat.is_sparse() if sparse: entries = list(mat._dict().items()) try: data = np.asarray([d for _, d in entries], dtype=float) except StandardError: raise ValueError, "can not convert entries to floating point numbers" positions = np.asarray([[row for (row, col), _ in entries], [col for (row, col), _ in entries]], dtype=int) mat = scipysparse.coo_matrix((data, positions), shape=(mat.nrows(), mat.ncols())) else: mat = mat.change_ring(RDF).numpy() elif hasattr(mat, 'tocoo'): sparse = True else: sparse = False try: if sparse: xy_data_array = mat else: xy_data_array = np.asarray(mat, dtype=float) except TypeError: raise TypeError, "mat must be a Matrix or a two dimensional array" except ValueError: raise ValueError, "can not convert entries to floating point numbers" if len(xy_data_array.shape) < 2: raise TypeError, "mat must be a Matrix or a two dimensional array" xrange = (0, xy_data_array.shape[1]) yrange = (0, xy_data_array.shape[0]) if options['subdivisions'] and options['subdivision_options'][ 'boundaries'] is None: options['subdivision_options'][ 'boundaries'] = orig_mat.get_subdivisions() # Custom position the title. Otherwise it overlaps with tick labels if options['origin'] == 'upper' and 'title_pos' not in options: options['title_pos'] = (0.5, 1.05) g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) g.add_primitive(MatrixPlot(xy_data_array, xrange, yrange, options)) return g
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) if is_RingHomomorphism(x): 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) from sage.schemes.toric.morphism import SchemeMorphism_fan_toric_variety if isinstance(x, FanMorphism): return SchemeMorphism_fan_toric_variety(self, x, check=check) if is_Matrix(x): fm = FanMorphism(x, self.domain().fan(), self.codomain().fan()) return SchemeMorphism_fan_toric_variety(self, fm, check=check) raise TypeError, "x must be a fan morphism or a list/tuple of polynomials"
def extend_to_primitive(A_input): """ Given a matrix (resp. list of vectors), extend it to a square matrix (resp. list of vectors), such that its determinant is the gcd of its minors (i.e. extend the basis of a lattice to a "maximal" one in Z^n). Author(s): Gonzalo Tornaria and Jonathan Hanke. INPUT: a matrix, or a list of length n vectors (in the same space) OUTPUT: a square matrix, or a list of n vectors (resp.) EXAMPLES: sage: A = Matrix(ZZ, 3, 2, range(6)) sage: extend_to_primitive(A) [ 0 1 0] [ 2 3 0] [ 4 5 -1] sage: extend_to_primitive([vector([1,2,3])]) [(1, 2, 3), (0, 1, 0), (0, 0, 1)] """ ## Deal with a list of vectors if not is_Matrix(A_input): A = matrix(A_input) ## Make a matrix A with the given rows. vec_output_flag = True else: A = A_input vec_output_flag = False ## Arrange for A to have more columns than rows. if A.is_square(): return A if A.nrows() > A.ncols(): return extend_to_primitive(A.transpose()).transpose() ## Setup k = A.nrows() n = A.ncols() R = A.base_ring() # Smith normal form transformation, assuming more columns than rows V = A.smith_form()[2] ## Extend the matrix in new coordinates, then switch back. B = A * V B_new = matrix(R, n-k, n) for i in range(n-k): B_new[i, n-i-1] = 1 C = B.stack(B_new) D = C * V**(-1) ## DIAGNOSTIC #print "A = ", A, "\n" #print "B = ", B, "\n" #print "C = ", C, "\n" #print "D = ", D, "\n" # Normalize for a positive determinant if D.det() < 0: D.rescale_row(n-1, -1) ## Return the current information if vec_output_flag: return D.rows() else: return D
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 transfromation 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 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 SymbolicRing 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(SymbolicRing()) 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.coeff(a) for e in exprs] for a in args] try: arg2 = matrix(D.base_ring(), m, n, arg2) except TypeError, 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), e: msg = 'some image of the function is not in the codomain, because\n' + e.args[ 0] raise ArithmeticError(msg)
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) """ if is_Matrix(x): from constructor import Conic y = x.inverse() A = y.transpose()*self.matrix()*y im = Conic(A) if Y == None: Y = im else: q = Y.defining_polynomial()/im.defining_polynomial() if not (q.numerator().is_constant() and q.denominator().is_constant()): 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 ProjectiveCurve_generic.hom(self, x, Y)
def __call__(self, v): """ Evaluate this quadratic form Q on a vector or matrix of elements coercible to the base ring of the quadratic form. If a vector is given then the output will be the ring element Q(`v`), but if a matrix is given then the output will be the quadratic form Q' which in matrix notation is given by: .. math:: Q' = v^t * Q * v. EXAMPLES:: ## Evaluate a quadratic form at a vector: ## -------------------------------------- sage: Q = QuadraticForm(QQ, 3, range(6)) sage: Q Quadratic form in 3 variables over Rational Field with coefficients: [ 0 1 2 ] [ * 3 4 ] [ * * 5 ] sage: Q([1,2,3]) 89 sage: Q([1,0,0]) 0 sage: Q([1,1,1]) 15 :: ## Evaluate a quadratic form using a column matrix: ## ------------------------------------------------ sage: Q = QuadraticForm(QQ, 2, range(1,4)) sage: A = Matrix(ZZ,2,2,[-1,0,0,1]) sage: Q(A) Quadratic form in 2 variables over Rational Field with coefficients: [ 1 -2 ] [ * 3 ] sage: Q([1,0]) 1 sage: type(Q([1,0])) <type 'sage.rings.rational.Rational'> sage: Q = QuadraticForm(QQ, 2, range(1,4)) sage: Q(matrix(2, [1,0])) Quadratic form in 1 variables over Rational Field with coefficients: [ 1 ] :: ## Simple 2x2 change of variables: ## ------------------------------- sage: Q = QuadraticForm(ZZ, 2, [1,0,1]) sage: Q Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 1 ] sage: M = Matrix(ZZ, 2, 2, [1,1,0,1]) sage: M [1 1] [0 1] sage: Q(M) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 2 ] [ * 2 ] :: ## Some more tests: ## ---------------- sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q([1,2,3]) 14 sage: v = vector([1,2,3]) sage: Q(v) 14 sage: t = tuple([1,2,3]) sage: Q(v) 14 sage: M = Matrix(ZZ, 3, [1,2,3]) sage: Q(M) Quadratic form in 1 variables over Integer Ring with coefficients: [ 14 ] """ ## If we are passed a matrix A, return the quadratic form Q(A(x)) ## (In matrix notation: A^t * Q * A) n = self.dim() if is_Matrix(v): ## Check that v has the correct number of rows if v.nrows() != n: raise TypeError("Oops! The matrix must have " + str(n) + " rows. =(") ## Create the new quadratic form m = v.ncols() Q2 = QuadraticForm(self.base_ring(), m) return QFEvaluateMatrix(self, v, Q2) elif (is_Vector(v) or isinstance(v, (list, tuple))): ## Check the vector/tuple/list has the correct length if not (len(v) == n): raise TypeError("Oops! Your vector needs to have length " + str(n) + " .") ## TO DO: Check that the elements can be coerced into the base ring of Q -- on first elt. if len(v) > 0: try: x = self.base_ring()(v[0]) except Exception: raise TypeError("Oops! Your vector is not coercible to the base ring of the quadratic form... =(") ## Attempt to evaluate Q[v] return QFEvaluateVector(self, v) else: raise TypeError
def RSK(obj1=None, obj2=None, insertion='RSK', check_standard=False, **options): r""" Perform the Robinson-Schensted-Knuth (RSK) correspondence. The Robinson-Schensted-Knuth (RSK) correspondence (also known as the RSK algorithm) is most naturally stated as a bijection between generalized permutations (also known as two-line arrays, biwords, ...) and pairs of semi-standard Young tableaux `(P, Q)` of identical shape. The tableau `P` is known as the insertion tableau, and `Q` is known as the recording tableau. The basic operation is known as row insertion `P \leftarrow k` (where `P` is a given semi-standard Young tableau, and `k` is an integer). Row insertion is a recursive algorithm which starts by setting `k_0 = k`, and in its `i`-th step inserts the number `k_i` into the `i`-th row of `P` (we start counting the rows at `0`) by replacing the first integer greater than `k_i` in the row by `k_i` and defines `k_{i+1}` as the integer that has been replaced. If no integer greater than `k_i` exists in the `i`-th row, then `k_i` is simply appended to the row and the algorithm terminates at this point. Now the RSK algorithm, applied to a generalized permutation `p = ((j_0, k_0), (j_1, k_1), \ldots, (j_{\ell-1}, k_{\ell-1}))` (encoded as a lexicographically sorted list of pairs) starts by initializing two semi-standard tableaux `P_0` and `Q_0` as empty tableaux. For each nonnegative integer `t` starting at `0`, take the pair `(j_t, k_t)` from `p` and set `P_{t+1} = P_t \leftarrow k_t`, and define `Q_{t+1}` by adding a new box filled with `j_t` to the tableau `Q_t` at the same location the row insertion on `P_t` ended (that is to say, adding a new box with entry `j_t` such that `P_{t+1}` and `Q_{t+1}` have the same shape). The iterative process stops when `t` reaches the size of `p`, and the pair `(P_t, Q_t)` at this point is the image of `p` under the Robinson-Schensted-Knuth correspondence. This correspondence has been introduced in [Knu1970]_, where it has been referred to as "Construction A". For more information, see Chapter 7 in [Sta-EC2]_. We also note that integer matrices are in bijection with generalized permutations. Furthermore, we can convert any word `w` (and, in particular, any permutation) to a generalized permutation by considering the top line to be `(1, 2, \ldots, n)` where `n` is the length of `w`. The optional argument ``insertion`` allows to specify an alternative insertion procedure to be used instead of the standard Robinson-Schensted-Knuth insertion. If the input is a reduced word of a permutation (i.e., an element of a type-`A` Coxeter group), one can set ``insertion`` to ``'EG'``, which gives Edelman-Greene insertion, an algorithm defined in [EG1987]_ Definition 6.20 (where it is referred to as Coxeter-Knuth insertion). The Edelman-Greene insertion is similar to the standard row insertion except that if `k_i` and `k_i + 1` both exist in row `i`, we *only* set `k_{i+1} = k_i + 1` and continue. One can also perform a "Hecke RSK algorithm", defined using the Hecke insertion studied in [BKSTY06]_ (but using rows instead of columns). The algorithm proceeds similarly to the classical RSK algorithm. However, it is not clear in what generality it works; thus, following [BKSTY06]_, we shall assume that our biword `p` has top line `(1, 2, \ldots, n)` (or, at least, has its top line strictly increasing). The Hecke RSK algorithm returns a pair of an increasing tableau and a set-valued standard tableau. If `p = ((j_0, k_0), (j_1, k_1), \ldots, (j_{\ell-1}, k_{\ell-1}))`, then the algorithm recursively constructs pairs `(P_0, Q_0), (P_1, Q_1), \ldots, (P_\ell, Q_\ell)` of tableaux. The construction of `P_{t+1}` and `Q_{t+1}` from `P_t`, `Q_t`, `j_t` and `k_t` proceeds as follows: Set `i = j_t`, `x = k_t`, `P = P_t` and `Q = Q_t`. We are going to insert `x` into the increasing tableau `P` and update the set-valued "recording tableau" `Q` accordingly. As in the classical RSK algorithm, we first insert `x` into row `1` of `P`, then into row `2` of the resulting tableau, and so on, until the construction terminates. The details are different: Suppose we are inserting `x` into row `R` of `P`. If (Case 1) there exists an entry `y` in row `R` such that `x < y`, then let `y` be the minimal such entry. We replace this entry `y` with `x` if the result is still an increasing tableau; in either subcase, we then continue recursively, inserting `y` into the next row of `P`. If, on the other hand, (Case 2) no such `y` exists, then we append `x` to the end of `R` if the result is an increasing tableau (Subcase 2.1), and otherwise (Subcase 2.2) do nothing. Furthermore, in Subcase 2.1, we add the box that we have just filled with `x` in `P` to the shape of `Q`, and fill it with the one-element set `\{i\}`. In Subcase 2.2, we find the bottommost box of the column containing the rightmost box of row `R`, and add `i` to the entry of `Q` in this box (this entry is a set, since `Q` is a set-valued). In either subcase, we terminate the recursion, and set `P_{t+1} = P` and `Q_{t+1} = Q`. Notice that set-valued tableaux are encoded as tableaux whose entries are tuples of positive integers; each such tuple is strictly increasing and encodes a set (namely, the set of its entries). INPUT: - ``obj1, obj2`` -- Can be one of the following: - A word in an ordered alphabet - An integer matrix - Two lists of equal length representing a generalized permutation - Any object which has a method ``_rsk_iter()`` which returns an iterator over the object represented as generalized permutation or a pair of lists. - ``insertion`` -- (Default: ``'RSK'``) The following types of insertion are currently supported: - ``'RSK'`` -- Robinson-Schensted-Knuth - ``'EG'`` -- Edelman-Greene (only for reduced words of permutations/elements of a type-`A` Coxeter group) - ``'hecke'`` -- Hecke insertion (only guaranteed for generalized permutations whose top row is strictly increasing) - ``check_standard`` -- (Default: ``False``) Check if either of the resulting tableaux is a standard tableau, and if so, typecast it as such EXAMPLES: If we only give one line, we treat the top line as being `(1, 2, \ldots, n)`:: sage: RSK([3,3,2,4,1]) [[[1, 3, 4], [2], [3]], [[1, 2, 4], [3], [5]]] sage: RSK(Word([3,3,2,4,1])) [[[1, 3, 4], [2], [3]], [[1, 2, 4], [3], [5]]] sage: RSK(Word([2,3,3,2,1,3,2,3])) [[[1, 2, 2, 3, 3], [2, 3], [3]], [[1, 2, 3, 6, 8], [4, 7], [5]]] With a generalized permutation:: sage: RSK([1, 2, 2, 2], [2, 1, 1, 2]) [[[1, 1, 2], [2]], [[1, 2, 2], [2]]] sage: RSK(Word([1,1,3,4,4]), [1,4,2,1,3]) [[[1, 1, 3], [2], [4]], [[1, 1, 4], [3], [4]]] sage: RSK([1,3,3,4,4], Word([6,2,2,1,7])) [[[1, 2, 7], [2], [6]], [[1, 3, 4], [3], [4]]] If we give it a matrix:: sage: RSK(matrix([[0,1],[2,1]])) [[[1, 1, 2], [2]], [[1, 2, 2], [2]]] We can also give it something looking like a matrix:: sage: RSK([[0,1],[2,1]]) [[[1, 1, 2], [2]], [[1, 2, 2], [2]]] There are also variations of the insertion algorithm in RSK. Here we consider Edelman-Greene insertion:: sage: RSK([2,1,2,3,2], insertion='EG') [[[1, 2, 3], [2, 3]], [[1, 3, 4], [2, 5]]] We reproduce figure 6.4 in [EG1987]_:: sage: RSK([2,3,2,1,2,3], insertion='EG') [[[1, 2, 3], [2, 3], [3]], [[1, 2, 6], [3, 5], [4]]] Hecke insertion is also supported. We construct Example 2.1 in :arxiv:`0801.1319v2`:: sage: w = [5, 4, 1, 3, 4, 2, 5, 1, 2, 1, 4, 2, 4] sage: RSK(w, insertion='hecke') [[[1, 2, 4, 5], [2, 4, 5], [3, 5], [4], [5]], [[(1,), (4,), (5,), (7,)], [(2,), (9,), (11, 13)], [(3,), (12,)], [(6,)], [(8, 10)]]] There is also :func:`~sage.combinat.rsk.RSK_inverse` which performs the inverse of the bijection on a pair of semistandard tableaux. We note that the inverse function takes 2 separate tableaux as inputs, so to compose with :func:`~sage.combinat.rsk.RSK`, we need to use the python ``*`` on the output:: sage: RSK_inverse(*RSK([1, 2, 2, 2], [2, 1, 1, 2])) [[1, 2, 2, 2], [2, 1, 1, 2]] sage: P,Q = RSK([1, 2, 2, 2], [2, 1, 1, 2]) sage: RSK_inverse(P, Q) [[1, 2, 2, 2], [2, 1, 1, 2]] TESTS: Empty objects:: sage: RSK(Permutation([])) [[], []] sage: RSK(Word([])) [[], []] sage: RSK(matrix([[]])) [[], []] sage: RSK([], []) [[], []] sage: RSK([[]]) [[], []] sage: RSK(Word([]), insertion='EG') [[], []] sage: RSK(Word([]), insertion='hecke') [[], []] """ from sage.combinat.tableau import SemistandardTableau, StandardTableau if insertion == 'hecke': return hecke_insertion(obj1, obj2) if obj1 is None and obj2 is None: if 'matrix' in options: obj1 = matrix(options['matrix']) else: raise ValueError("invalid input") if is_Matrix(obj1): obj1 = obj1.rows() if len(obj1) == 0: return [StandardTableau([]), StandardTableau([])] if obj2 is None: try: itr = obj1._rsk_iter() except AttributeError: # If this is (something which looks like) a matrix # then build the generalized permutation try: t = [] b = [] for i, row in enumerate(obj1): for j, mult in enumerate(row): if mult > 0: t.extend([i+1]*mult) b.extend([j+1]*mult) itr = izip(t, b) except TypeError: itr = izip(range(1, len(obj1)+1), obj1) else: if len(obj1) != len(obj2): raise ValueError("the two arrays must be the same length") # Check it is a generalized permutation lt = 0 lb = 0 for t,b in izip(obj1, obj2): if t < lt or (lt == t and b < lb): raise ValueError("invalid generalized permutation") lt = t lb = b itr = izip(obj1, obj2) from bisect import bisect_right p = [] #the "insertion" tableau q = [] #the "recording" tableau use_EG = (insertion == 'EG') #For each x in self, insert x into the tableau p. lt = 0 lb = 0 for i, x in itr: for r, qr in izip(p,q): if r[-1] > x: #Figure out where to insert x into the row r. The #bisect command returns the position of the least #element of r greater than x. We will call it y. y_pos = bisect_right(r, x) if use_EG and r[y_pos] == x + 1 and y_pos > 0 and x == r[y_pos - 1]: #Special bump: Nothing to do except increment x by 1 x += 1 else: #Switch x and y x, r[y_pos] = r[y_pos], x else: break else: #We made through all of the rows of p without breaking #so we need to add a new row to p and q. r = []; p.append(r) qr = []; q.append(qr) r.append(x) qr.append(i) # Values are always inserted to the right if check_standard: try: P = StandardTableau(p) except ValueError: P = SemistandardTableau(p) try: Q = StandardTableau(q) except ValueError: Q = SemistandardTableau(q) return [P, Q] return [SemistandardTableau(p), SemistandardTableau(q)]
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 PolynomialSequence(arg1, arg2=None, immutable=False, cr=False, cr_str=None): """ Construct a new polynomial sequence object. INPUT: - ``arg1`` - a multivariate polynomial ring, an ideal or a matrix - ``arg2`` - an iterable object of parts or polynomials (default:``None``) - ``immutable`` - if ``True`` the sequence is immutable (default: ``False``) - ``cr`` - print a line break after each element (default: ``False``) - ``cr_str`` - print a line break after each element if 'str' is called (default: ``None``) EXAMPLES:: sage: P.<a,b,c,d> = PolynomialRing(GF(127),4) sage: I = sage.rings.ideal.Katsura(P) If a list of tuples is provided, those form the parts:: sage: F = Sequence([I.gens(),I.gens()], I.ring()); F # indirect doctest [a + 2*b + 2*c + 2*d - 1, a^2 + 2*b^2 + 2*c^2 + 2*d^2 - a, 2*a*b + 2*b*c + 2*c*d - b, b^2 + 2*a*c + 2*b*d - c, a + 2*b + 2*c + 2*d - 1, a^2 + 2*b^2 + 2*c^2 + 2*d^2 - a, 2*a*b + 2*b*c + 2*c*d - b, b^2 + 2*a*c + 2*b*d - c] sage: F.nparts() 2 If an ideal is provided, the generators are used:: sage: Sequence(I) [a + 2*b + 2*c + 2*d - 1, a^2 + 2*b^2 + 2*c^2 + 2*d^2 - a, 2*a*b + 2*b*c + 2*c*d - b, b^2 + 2*a*c + 2*b*d - c] If a list of polynomials is provided, the system has only one part:: sage: F = Sequence(I.gens(), I.ring()); F [a + 2*b + 2*c + 2*d - 1, a^2 + 2*b^2 + 2*c^2 + 2*d^2 - a, 2*a*b + 2*b*c + 2*c*d - b, b^2 + 2*a*c + 2*b*d - c] sage: F.nparts() 1 """ from sage.matrix.matrix import is_Matrix if is_MPolynomialRing(arg1) or is_QuotientRing(arg1): ring = arg1 gens = arg2 elif is_MPolynomialRing(arg2) or is_QuotientRing(arg2): ring = arg2 gens = arg1 elif is_Matrix(arg1) and arg2 is None: ring = arg1.base_ring() gens = arg1.list() elif isinstance(arg1, MPolynomialIdeal) and arg2 is None: ring = arg1.ring() gens = arg1.gens() elif isinstance(arg1, (list,tuple,GeneratorType)) and arg2 is None: gens = arg1 try: e = iter(gens).next() except StopIteration: raise ValueError("Cannot determine ring from provided information.") if is_MPolynomial(e) or isinstance(e, QuotientRingElement): ring = e.parent() else: ring = iter(e).next().parent() else: raise TypeError("Cannot understand input.") try: e = iter(gens).next() if is_MPolynomial(e) or isinstance(e, QuotientRingElement): gens = tuple(gens) parts = (gens,) if not all(f.parent() is ring for f in gens): parts = ((ring(f) for f in gens),) else: parts = [] _gens = [] for part in gens: _part = [] for gen in part: if not gen.parent() is ring: ring(gen) _part.append(gen) _gens.extend(_part) parts.append(tuple(_part)) gens = _gens except StopIteration: gens = tuple() parts = ((),) k = ring.base_ring() try: c = k.characteristic() except NotImplementedError: c = -1 if c != 2: return PolynomialSequence_generic(parts, ring, immutable=immutable, cr=cr, cr_str=cr_str) elif k.degree() == 1: return PolynomialSequence_gf2(parts, ring, immutable=immutable, cr=cr, cr_str=cr_str) elif k.degree() > 1: return PolynomialSequence_gf2e(parts, ring, immutable=immutable, cr=cr, cr_str=cr_str)
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 __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_of_automorphisms=None, determinant=None): """ EXAMPLES:: sage: s = QuadraticForm(ZZ, 4, range(10)) sage: s == loads(dumps(s)) True """ ## Deal with: QuadraticForm(ring, matrix) matrix_init_flag = False if isinstance(R, Ring): if is_Matrix(n): ## Test if n is symmetric and has even diagonal if not self._is_even_symmetric_matrix_(n, R): raise TypeError("Oops! The matrix is not a symmetric with even diagonal defined over R.") ## Rename the matrix and ring M = n M_ring = R matrix_init_flag = True ## Deal with: QuadraticForm(matrix) if is_Matrix(R) and (n == None): ## Test if R is symmetric and has even diagonal if not self._is_even_symmetric_matrix_(R): raise TypeError("Oops! The matrix is not a symmetric with even diagonal.") ## Rename the matrix and ring M = R M_ring = R.base_ring() matrix_init_flag = True ## Perform the quadratic form initialization if matrix_init_flag == True: self.__n = M.nrows() self.__base_ring = M_ring self.__coeffs = [] for i in range(M.nrows()): for j in range(i, M.nrows()): if (i == j): self.__coeffs += [ M_ring(M[i,j] / 2) ] else: self.__coeffs += [ M_ring(M[i,j]) ] return ## ----------------------------------------------------------- ## Verify the size of the matrix is an integer >= 0 try: n = int(n) except Exception: raise TypeError("Oops! The size " + str(n) + " must be an integer.") if (n < 0): raise TypeError("Oops! The size " + str(n) + " must be a non-negative integer.") ## TODO: Verify that R is a ring... ## Store the relevant variables N = int(n*(n+1))/2 self.__n = int(n) self.__base_ring = R self.__coeffs = [self.__base_ring(0) for i in range(N)] ## Check if entries is a list for the current size, and if so, write the upper-triangular matrix if isinstance(entries, list) and (len(entries) == N): for i in range(N): self.__coeffs[i] = self.__base_ring(entries[i]) elif (entries != None): raise TypeError("Oops! The entries " + str(entries) + "must be a list of size n(n+1)/2.") ## ----------------------------------------------------------- ## Process possible forced initialization of various fields self._external_initialization_list = [] if unsafe_initialization: ## Set the number of automorphisms if number_of_automorphisms != None: self.set_number_of_automorphisms(number_of_automorphisms) #self.__number_of_automorphisms = number_of_automorphisms #self.__external_initialization_list.append('number_of_automorphisms') ## Set the determinant if determinant != None: self.__det = determinant self._external_initialization_list.append('determinant')
def __call__(self, A, check=True): r""" INPUT: - ``A`` - one of several possible inputs representing a morphism from this vector space homspace. - a vector space morphism in this homspace - a matrix representation relative to the bases of the vector spaces, which acts on a vector placed to the left of the matrix - a list or tuple containing images of the domain's basis vectors - a function from the domain to the codomain - ``check`` (default: True) - ``True`` or ``False``, required for compatibility with calls from :meth:`sage.structure.parent_gens.ParentWithGens.hom`. EXAMPLES:: sage: V = (QQ^3).span_of_basis([[1,1,0],[1,0,2]]) sage: H = V.Hom(V) sage: H Set of Morphisms (Linear Transformations) from Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] to Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Coercing a matrix:: sage: A = matrix(QQ, [[0, 1], [1, 0]]) sage: rho = H(A) # indirect doctest sage: rho Vector space morphism represented by the matrix: [0 1] [1 0] Domain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Codomain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Coercing a list of images:: sage: phi = H([V.1, V.0]) sage: phi(V.1) == V.0 True sage: phi(V.0) == V.1 True sage: phi Vector space morphism represented by the matrix: [0 1] [1 0] Domain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Codomain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Coercing a lambda function:: sage: f = lambda x: vector(QQ, [x[0], (1/2)*x[2], 2*x[1]]) sage: zeta = H(f) sage: zeta Vector space morphism represented by the matrix: [0 1] [1 0] Domain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Codomain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 1 0] [1 0 2] Coercing a vector space morphism into the parent of a second vector space morphism will unify their parents:: sage: U = FreeModule(QQ,3, sparse=True ); V = QQ^4 sage: W = FreeModule(QQ,3, sparse=False); X = QQ^4 sage: H = Hom(U, V) sage: K = Hom(W, X) sage: H is K, H == K (False, True) sage: A = matrix(QQ, 3, 4, [0]*12) sage: f = H(A) sage: B = matrix(QQ, 3, 4, range(12)) sage: g = K(B) sage: f.parent() is H and g.parent() is K True sage: h = H(g) sage: f.parent() is h.parent() True See other examples in the module-level documentation. TESTS:: sage: V = GF(3)^0 sage: W = GF(3)^1 sage: H = V.Hom(W) sage: H.zero().is_zero() True Previously the above code resulted in a TypeError because the dimensions of the matrix were incorrect. """ from .vector_space_morphism import is_VectorSpaceMorphism, VectorSpaceMorphism D = self.domain() C = self.codomain() from sage.matrix.matrix import is_Matrix if is_Matrix(A): pass elif is_VectorSpaceMorphism(A): A = A.matrix() elif inspect.isfunction(A): try: images = [A(g) for g in D.basis()] except (ValueError, TypeError, IndexError) as e: msg = 'function cannot be applied properly to some basis element because\n' + e.args[0] raise ValueError(msg) try: A = matrix.matrix(D.dimension(), C.dimension(), [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) elif isinstance(A, (list, tuple)): if len(A) != len(D.basis()): msg = "number of images should equal the size of the domain's basis (={0}), not {1}" raise ValueError(msg.format(len(D.basis()), len(A))) try: v = [C(a) for a in A] A = matrix.matrix(D.dimension(), C.dimension(), [C.coordinates(a) for a in v]) except (ArithmeticError, TypeError) as e: msg = 'some proposed image is not in the codomain, because\n' + e.args[0] raise ArithmeticError(msg) else: msg = 'vector space homspace can only coerce matrices, vector space morphisms, functions or lists, not {0}' raise TypeError(msg.format(A)) return VectorSpaceMorphism(self, A)