# Ticket #11556: trac_11556-linear-transformations-v6-consolidated.patch

File trac_11556-linear-transformations-v6-consolidated.patch, 83.8 KB (added by rbeezer, 10 years ago)

Consolidated, standalone

• ## doc/en/reference/modules.rst

# HG changeset patch
# User Rob Beezer <beezer@ups.edu>
# Date 1311723732 25200
# Node ID 5f534d820a1c9b8587ff6812af8d8f9444056828
# Parent  df5620969fbddef0cf191407d046c2ed522d6a49
11556: linear transformations from free module morphisms

diff --git a/doc/en/reference/modules.rst b/doc/en/reference/modules.rst
 a sage/modules/real_double_vector sage/modules/vector_callable_symbolic_dense sage/modules/vector_space_homspace sage/modules/vector_space_morphism sage/modules/free_module_homspace sage/modules/free_module_morphism sage/modules/matrix_morphism sage/modules/fg_pid/fgp_module
• ## sage/categories/homset.py

diff --git a/sage/categories/homset.py b/sage/categories/homset.py
 a sage: V = VectorSpace(QQ,3) sage: Hom(V, V) Set of Morphisms from Vector space of dimension 3 over Rational Field to Vector space of dimension 3 over Rational Field in Category of vector spaces over Rational Field Set of Morphisms (Linear Transformations) from Vector space of dimension 3 over Rational Field to Vector space of dimension 3 over Rational Field sage: G = AlternatingGroup(3) sage: Hom(G, G) Set of Morphisms from Alternating group of order 3!/2 as a permutation group to Alternating group of order 3!/2 as a permutation group in Category of finite permutation groups sage: V = VectorSpace(QQ, 3) sage: End(V) Set of Morphisms from Vector space of dimension 3 over Rational Field to Vector space of dimension 3 over Rational Field in Category of vector spaces over Rational Field Set of Morphisms (Linear Transformations) from Vector space of dimension 3 over Rational Field to Vector space of dimension 3 over Rational Field :: sage: G = AlternatingGroup(3) True """ def __init__(self, X, Y, category=None, base = None, check=True): """ r""" TESTS:: sage: X = ZZ['x']; X.rename("X") sage: Y = ZZ['y']; Y.rename("Y") sage: class MyHomset(Homset): # Really needed??? class HomsetWithBase(Homset): def __init__(self, X, Y, category=None, check=True, base=None): """ r""" TESTS:: sage: X = ZZ['x']; X.rename("X") sage: Y = ZZ['y']; Y.rename("Y") sage: class MyHomset(HomsetWithBase):
• ## sage/categories/map.pyx

diff --git a/sage/categories/map.pyx b/sage/categories/map.pyx
 a If the first map is surjective and the second map is not injective, then the composition is not injective:: sage: psi2 = V1.hom([1,1],QQ^1) sage: psi2 = V1.hom([[1],[1]],QQ^1) sage: c3 = FormalCompositeMap(Hom(V2,QQ^1,phi2.category_for()),psi2,psi1) sage: c3.is_injective() False If the second map is not surjective, the composition is not surjective:: sage: FormalCompositeMap(Hom(V3,V1,phi32.category_for()),phi32,V2.hom(Matrix([[0,0]]),V1)).is_surjective() sage: FormalCompositeMap(Hom(V3,V1,phi32.category_for()),phi32,V2.hom(Matrix([[0],[0]]),V1)).is_surjective() False If the second map is an isomorphism and the first map is not surjective, then the composition is not surjective:: sage: FormalCompositeMap(Hom(V2,V1,phi32.category_for()),V2.hom(Matrix([[0,0]]),V1),V1.hom(Matrix([[1]]),V1)).is_surjective() sage: FormalCompositeMap(Hom(V2,V1,phi32.category_for()),V2.hom(Matrix([[0],[0]]),V1),V1.hom(Matrix([[1]]),V1)).is_surjective() False Otherwise, surjectivity of the composition can not be determined::
• ## sage/modules/all.py

diff --git a/sage/modules/all.py b/sage/modules/all.py
 a import vector_callable_symbolic_dense from vector_space_homspace import is_VectorSpaceHomspace from vector_space_morphism import is_VectorSpaceMorphism, linear_transformation import vector_symbolic_dense
• ## sage/modules/free_module.py

diff --git a/sage/modules/free_module.py b/sage/modules/free_module.py
 a # FIXME: what's the level of generality of FreeModuleHomspace? # Should there be a category for free modules accepting it as hom space? # See similar method for FreeModule_generic_field class def _Hom_(self, Y, category): from free_module_homspace import FreeModuleHomspace return FreeModuleHomspace(self, Y, category) raise TypeError, "The base_field (=%s) must be a field"%base_field FreeModule_generic_pid.__init__(self, base_field, dimension, degree, sparse=sparse) def _Hom_(self, Y, category): r""" Returns a homspace whose morphisms have this vector space as domain. This is called by the general methods such as :meth:sage.structure.parent.Parent.Hom and :meth:sage.structure.parent_base.ParentWithBase.Hom. INPUT: - Y - a free module (or vector space) that will be the codomain of the morphisms in returned homspace - category - the category for the homspace OUTPUT: If Y is a free module over a field, in other words, a vector space, then this returns a space of homomorphisms between vector spaces, in other words a space of linear transformations. If Y is a free module that is not a vector space, then the returned space contains homomorphisms between free modules. EXAMPLES:: sage: V = QQ^2 sage: W = QQ^3 sage: H = V._Hom_(W, category=None) sage: type(H) sage: H Set of Morphisms (Linear Transformations) from Vector space of dimension 2 over Rational Field to Vector space of dimension 3 over Rational Field sage: V = QQ^2 sage: W = ZZ^3 sage: H = V._Hom_(W, category=None) sage: type(H) sage: H Set of Morphisms from Vector space of dimension 2 over Rational Field to Ambient free module of rank 3 over the principal ideal domain Integer Ring in Category of vector spaces over Rational Field """ if Y.base_ring().is_field(): import vector_space_homspace return vector_space_homspace.VectorSpaceHomspace(self, Y, category) import free_module_homspace return free_module_homspace.FreeModuleHomspace(self, Y, category) def scale(self, other): """ Return the product of self by the number other, which is the module
• ## sage/modules/free_module_homspace.py

diff --git a/sage/modules/free_module_homspace.py b/sage/modules/free_module_homspace.py
 a Domain: Ambient free module of rank 2 over the principal ideal domain ... Codomain: Ambient free module of rank 2 over the principal ideal domain ... We create \mathrm{Hom}(\QQ^3, \QQ^2) and We create \mathrm{Hom}(\ZZ^3, \ZZ^2) and compute a basis. :: sage: V3 = VectorSpace(RationalField(),3) sage: V2 = VectorSpace(RationalField(),2) sage: V3 = FreeModule(IntegerRing(),3) sage: V2 = FreeModule(IntegerRing(),2) sage: H = Hom(V3,V2) sage: H Set of Morphisms from Vector space of dimension 3 over Rational Field to Vector space of dimension 2 over Rational Field in Category of vector spaces over Rational Field Set of Morphisms from Ambient free module of rank 3 over the principal ideal domain Integer Ring to Ambient free module of rank 2 over the principal ideal domain Integer Ring in Category of modules with basis over Integer Ring sage: B = H.basis() sage: len(B) 6 See trac 5886:: sage: V = (QQ^2).span_of_basis([[1,2],[3,4]]) sage: V = (ZZ^2).span_of_basis([[1,2],[3,4]]) sage: V.hom([V.0, V.1]) Free module morphism defined by the matrix [1 0] def is_FreeModuleHomspace(x): """ Return True if x is a Free module homspace. r""" Return True if x is a free module homspace. EXAMPLES:: EXAMPLES: sage: H = Hom(QQ^3, QQ^2) Notice that every vector space is a field, but when we construct a set of morphisms between two vector spaces, it is a VectorSpaceHomspace, which qualifies as a FreeModuleHomspace, since the former is special case of the latter. sage: H = Hom(ZZ^3, ZZ^2) sage: type(H) sage: sage.modules.free_module_homspace.is_FreeModuleHomspace(H) True sage: sage.modules.free_module_homspace.is_FreeModuleHomspace(2) sage: K = Hom(QQ^3, ZZ^2) sage: type(K) sage: sage.modules.free_module_homspace.is_FreeModuleHomspace(K) True sage: L = Hom(ZZ^3, QQ^2) sage: type(L) sage: sage.modules.free_module_homspace.is_FreeModuleHomspace(L) True sage: P = Hom(QQ^3, QQ^2) sage: type(P) sage: sage.modules.free_module_homspace.is_FreeModuleHomspace(P) True sage: sage.modules.free_module_homspace.is_FreeModuleHomspace('junk') False """ return isinstance(x, FreeModuleHomspace) class FreeModuleHomspace(sage.categories.homset.HomsetWithBase): def __call__(self, A, check=True): """ r""" INPUT: - A -- either a matrix or a list/tuple of images of generators, EXAMPLES:: sage: V = (QQ^3).span_of_basis([[1,1,0],[1,0,2]]) sage: V = (ZZ^3).span_of_basis([[1,1,0],[1,0,2]]) sage: H = V.Hom(V); H Set of Morphisms from ... sage: H([V.0,V.1])                    # indirect doctest EXAMPLES:: sage: H = Hom(QQ^2, QQ^1) sage: H = Hom(ZZ^2, ZZ^1) sage: H.basis() (Free module morphism defined by the matrix [1] [0] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 1 over Rational Field, Free module morphism defined by the matrix Domain: Ambient free module of rank 2 over the principal ideal domain ... Codomain: Ambient free module of rank 1 over the principal ideal domain ..., Free module morphism defined by the matrix [0] [1] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 1 over Rational Field) Domain: Ambient free module of rank 2 over the principal ideal domain ... Codomain: Ambient free module of rank 1 over the principal ideal domain ...) """ try: return self.__basis return self.__basis def identity(self): r""" Return identity morphism in an endomorphism ring. r""" Return identity morphism in an endomorphism ring. EXAMPLE:: EXAMPLE:: sage: V=VectorSpace(QQ,5) sage: H=V.Hom(V) sage: H.identity() Free module morphism defined by the matrix [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 1 0] [0 0 0 0 1] Domain: Vector space of dimension 5 over Rational Field Codomain: Vector space of dimension 5 over Rational Field """ if self.is_endomorphism_set(): return self(matrix.identity_matrix(self.base_ring(),self.domain().rank())) else: raise TypeError, "Identity map only defined for endomorphisms. Try natural_map() instead." sage: V=FreeModule(ZZ,5) sage: H=V.Hom(V) sage: H.identity() Free module morphism defined by the matrix [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 1 0] [0 0 0 0 1] Domain: Ambient free module of rank 5 over the principal ideal domain ... Codomain: Ambient free module of rank 5 over the principal ideal domain ... """ if self.is_endomorphism_set(): return self(matrix.identity_matrix(self.base_ring(),self.domain().rank())) else: raise TypeError, "Identity map only defined for endomorphisms. Try natural_map() instead."
• ## sage/modules/free_module_morphism.py

diff --git a/sage/modules/free_module_morphism.py b/sage/modules/free_module_morphism.py
 a EXAMPLES:: sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ) sage: phi = V.hom(matrix(QQ,3,[1..9])) sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ) sage: phi = V.hom(matrix(ZZ,3,[1..9])) sage: type(phi) """ EXAMPLES:: sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ) sage: phi = V.hom(matrix(QQ,3,[1..9])) sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ) sage: phi = V.hom(matrix(ZZ,3,[1..9])) sage: phi._repr_() 'Free module morphism defined by the matrix\n[1 2 3]\n[4 5 6]\n[7 8 9]\nDomain: Vector space of dimension 3 over Rational Field\nCodomain: Vector space of dimension 3 over Rational Field' 'Free module morphism defined by the matrix\n[1 2 3]\n[4 5 6]\n[7 8 9]\nDomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring\nCodomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring' sage: V = ZZ^6 sage: W = ZZ^4 sage: m = matrix(QQ, 40, 40, 1600) sage: phi = V.hom(m, V) sage: phi Free module morphism defined by the matrix Vector space morphism represented by the matrix: 40 x 40 dense matrix over Rational Field Domain: Vector space of dimension 40 over Rational Field Codomain: Vector space of dimension 40 over Rational Field sage: h.change_ring(QQ).base_ring() Rational Field sage: f = h.change_ring(QQ); f Free module morphism defined by the matrix Vector space morphism represented by the matrix: [-3 -3] [-3 -3] Domain: Vector space of degree 3 and dimension 2 over Rational Field Basis ... Basis matrix: [0 1 0] [0 0 1] Codomain: Vector space of degree 2 and dimension 2 over Rational Field Basis ... Basis matrix: [1 0] [0 1] sage: f = h.change_ring(GF(7)); f Free module morphism defined by the matrix Vector space morphism represented by the matrix: [4 4] [4 4] Domain: Vector space of degree 3 and dimension 2 over Finite Field of ... Codomain: Vector space of degree 2 and dimension 2 over Finite Field of ... Domain: Vector space of degree 3 and dimension 2 over Finite Field of size 7 Basis matrix: [0 1 0] [0 0 1] Codomain: Vector space of degree 2 and dimension 2 over Finite Field of size 7 Basis matrix: [1 0] [0 1] """ D = self.domain().change_ring(R) C = self.codomain().change_ring(R) def eigenvectors(self,extend=True): """ Computes the subspace of eigenvectors of a given eigenvalue. INPUT: - extend -- boolean (default: True) decides if base field extensions should be considered or not. OUTPUT: A sequence of tuples. Each tuple contains an eigenvalue, a sequence with a basis of the corresponding subspace of eigenvectors, and the algebraic multiplicity of the eigenvalue. EXAMPLES: :: EXAMPLES:: sage: V=(QQ^4).subspace([[0,2,1,4],[1,2,5,0],[1,1,1,1]]) sage: H=(V.Hom(V))([[0,1,0],[-1,0,0],[0,0,3]]) sage: H=(V.Hom(V))(matrix(QQ, [[0,1,0],[-1,0,0],[0,0,3]])) sage: H.eigenvectors() [(3, [ (0, 0, 1, -6/7) [(3, [ (0, 0, 1, -6/7) ], 1)] sage: H1=(V.Hom(V))([[2,1,0],[0,2,0],[0,0,3]]) sage: H1=(V.Hom(V))(matrix(QQ, [[2,1,0],[0,2,0],[0,0,3]])) sage: H1.eigenvectors() [(3, [ (0, 0, 1, -6/7) ], 1), (2, [ (0, 1, 0, 17/7) ], 2)] """ if self.base_ring().is_field(): if self.is_endomorphism(): V=self.domain().base_extend(i[0].parent()) svectors=Sequence(map(lambda j: V(j * V.basis_matrix()),i[1]), cr=True) resu.append((i[0],svectors,i[2])) return resu return resu else: raise TypeError, "not an endomorphism" else: raise NotImplementedError, "module must be a vector space" def minpoly(self,var='x'): """ def minimal_polynomial(self,var='x'): r""" Computes the minimal polynomial. minpoly() and minimal_polynomial() are the same method. INPUT: - var - string (default: 'x') a variable name OUTPUT: polynomial in var - the minimal polynomial of the endomorphism. EXAMPLES: Compute the minimal polynomial, and check it :: sage: V=GF(7)^3 Compute the minimal polynomial, and check it. :: sage: V=GF(7)^3 sage: H=V.Hom(V)([[0,1,2],[-1,0,3],[2,4,1]]) sage: H Free module morphism defined by the matrix Vector space morphism represented by the matrix: [0 1 2] [6 0 3] [2 4 1] Domain: Vector space of dimension 3 over Finite Field of size 7 Codomain: Vector space of dimension 3 over Finite Field of size 7 sage: H.minpoly() x^3 + 6*x^2 + 6*x + 1 sage: H^3+6*H^2+6*H+1 Free module morphism defined by the matrix sage: H.minimal_polynomial() x^3 + 6*x^2 + 6*x + 1 sage: H^3 + (H^2)*6 + H*6 + 1 Vector space morphism represented by the matrix: [0 0 0] [0 0 0] [0 0 0] Domain: Vector space of dimension 3 over Finite Field of size 7 Codomain: Vector space of dimension 3 over Finite Field of size 7 """ if self.is_endomorphism(): return self.matrix().minpoly(var) else: raise TypeError, "not an endomorphism" minpoly = minimal_polynomial No newline at end of file
• ## sage/modules/matrix_morphism.py

diff --git a/sage/modules/matrix_morphism.py b/sage/modules/matrix_morphism.py
 a EXAMPLES:: sage: from sage.modules.matrix_morphism import MatrixMorphism sage: T = End(QQ^3) sage: M = MatrixSpace(QQ,3) sage: T = End(ZZ^3) sage: M = MatrixSpace(ZZ,3) sage: I = M.identity_matrix() sage: A = MatrixMorphism(T, I) sage: loads(A.dumps()) == A sage: V = QQ^3; W = QQ^2 sage: H = Hom(V, W); H Set of Morphisms from Vector space of dimension 3 over Rational Field to Vector space of dimension 2 over Rational Field in Category of vector spaces over Rational Field sage: phi = H(range(6)); phi Free module morphism defined by the matrix Set of Morphisms (Linear Transformations) from Vector space of dimension 3 over Rational Field to Vector space of dimension 2 over Rational Field sage: phi = H(matrix(QQ, 3, 2, range(6))); phi Vector space morphism represented by the matrix: [0 1] [2 3] [4 5] sage: V = QQ^2; phi = V.hom([3*V.0, 2*V.1]) sage: phi^(-1) Free module morphism defined by the matrix Vector space morphism represented by the matrix: [1/3   0] [  0 1/2] Domain: Vector space of dimension 2 over Rational Field sage: y = zeta(x); y (-3, 0, -1) sage: inv = zeta.inverse(); inv Free module morphism defined by the matrix Vector space morphism represented by the matrix: [-1  3] [ 1 -2] Domain: Vector space of degree 3 and dimension 2 over Rational Field Basis ... Basis matrix: [1 0 0] [0 0 1] Codomain: Vector space of degree 4 and dimension 2 over Rational Field Basis ... Basis matrix: [1 0 0 0] [0 0 0 1] sage: inv(y) == x True sage: phi = V.hom(r, V) sage: rho = phi.inverse() sage: zeta = ~phi sage: rho == zeta sage: rho.is_equal_function(zeta) True TESTS:: sage: V = QQ^3 sage: E = V.endomorphism_ring() sage: phi = E(Matrix(QQ,3,range(9))) ; phi Free module morphism defined by the matrix Vector space morphism represented by the matrix: [0 1 2] [3 4 5] [6 7 8] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 3 over Rational Field sage: phi*phi Free module morphism defined by the matrix Vector space morphism represented by the matrix: [ 15  18  21] [ 42  54  66] [ 69  90 111] [ 15  18  21] [ 42  54  66] [ 69  90 111] :: sage: W = QQ**4 sage: E_VW = V.Hom(W) sage: psi = E_VW(Matrix(QQ,3,4,range(12))) ; psi Free module morphism defined by the matrix Vector space morphism represented by the matrix: [ 0  1  2  3] [ 4  5  6  7] [ 8  9 10 11] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 4 over Rational Field sage: psi*phi Free module morphism defined by the matrix Vector space morphism represented by the matrix: [ 20  23  26  29] [ 56  68  80  92] [ 92 113 134 155] sage: V, VtoK, KtoV = K.vector_space() sage: f = V.hom([V.0 - V.1, V.0 + V.1])*KtoV; f Composite map: From: Number Field in a with defining polynomial x^2 + 23 To:   Vector space of dimension 2 over Rational Field Defn:   Isomorphism map: From: Number Field in a with defining polynomial x^2 + 23 To:   Vector space of dimension 2 over Rational Field From: Number Field in a with defining polynomial x^2 + 23 To:   Vector space of dimension 2 over Rational Field Defn:   Isomorphism map: From: Number Field in a with defining polynomial x^2 + 23 To:   Vector space of dimension 2 over Rational Field then Free module morphism defined by the matrix Vector space morphism represented by the matrix: [ 1 -1] [ 1  1] Domain: Vector space of dimension 2 over Rational Field """ return self.domain().base_ring() def charpoly(self, var='x'): """ def characteristic_polynomial(self, var='x'): r""" Return the characteristic polynomial of this endomorphism. characteristic_polynomial and char_poly are the same method. INPUT: - var -- variable EXAMPLES:: sage: V = ZZ^2; phi = V.hom([V.0+V.1, 2*V.1]) sage: phi.characteristic_polynomial() x^2 - 3*x + 2 sage: phi.charpoly() x^2 - 3*x + 2 sage: phi.matrix().charpoly() raise ArithmeticError, "charpoly only defined for endomorphisms " +\ "(i.e., domain = range)" return self.matrix().charpoly(var) charpoly = characteristic_polynomial def decomposition(self, *args, **kwds): """ Vector space of degree 3 and dimension 1 over Rational Field Basis matrix: [ 1 -2  1] sage: hom(CC^2, CC^2, 1).kernel() sage: hom(CC^2, CC^2, matrix(CC, [[1,0], [0,1]])).kernel() Vector space of degree 2 and dimension 0 over Complex Field with 53 bits of precision Basis matrix: [] EXAMPLES:: sage: V = VectorSpace(QQ,3) sage: phi = V.Hom(V)(range(9)) sage: phi = V.Hom(V)(matrix(QQ, 3, range(9))) sage: phi.image() Vector space of degree 3 and dimension 2 over Rational Field Basis matrix: [ 1  0 -1] [ 0  1  2] sage: hom(GF(7)^3, GF(7)^2, 0).image() sage: hom(GF(7)^3, GF(7)^2, zero_matrix(GF(7), 3, 2)).image() Vector space of degree 2 and dimension 0 over Finite Field of size 7 Basis matrix: [] sage: phi(V.1) in X True sage: psi = phi.restrict_codomain(X); psi Free module morphism defined by the matrix Vector space morphism represented by the matrix: [1] [2] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of degree 2 and dimension 1 over Rational Field Basis ... Basis matrix: [1 2] sage: psi(V.0) (1, 2) sage: psi(V.1) Basis matrix: [  1 4/3] sage: psi = phi.restrict(W); psi Free module morphism defined by the matrix Vector space morphism represented by the matrix: [2] Domain: Vector space of degree 2 and dimension 1 over Rational Field Basis ... Basis matrix: [  1 4/3] Codomain: Vector space of degree 2 and dimension 1 over Rational Field Basis ... Basis matrix: [  1 4/3] sage: psi.domain() == W True sage: psi(W.0) == 2*W.0 EXAMPLES:: sage: from sage.modules.matrix_morphism import MatrixMorphism sage: T = End(QQ^3) sage: M = MatrixSpace(QQ,3) sage: T = End(ZZ^3) sage: M = MatrixSpace(ZZ,3) sage: I = M.identity_matrix() sage: A = MatrixMorphism(T, I) sage: loads(A.dumps()) == A self._matrix = A MatrixMorphism_abstract.__init__(self, parent) def matrix(self): """ Return matrix that defines this morphism. def matrix(self, side='left'): r""" Return a matrix that defines this morphism. INPUT: - side - default:'left' - the side of the matrix where a vector is placed to effect the morphism (function). OUTPUT: A matrix which represents the morphism, relative to bases for the domain and codomain.  If the modules are provided with user bases, then the representation is relative to these bases. Internally, Sage represents a matrix morphism with the matrix multiplying a row vector placed to the left of the matrix.  If the option side='right' is used, then a matrix is returned that acts on a vector to the right of the matrix.  These two matrices are just transposes of each other and the difference is just a preference for the style of representation. EXAMPLES:: sage: V = ZZ^2; phi = V.hom([3*V.0, 2*V.1]) sage: V = ZZ^2; W = ZZ^3 sage: phi = V.hom([3*V.0 - 5*V.1, 4*V.0 + 2*V.1, V.0 + V.1], W) sage: phi.matrix() [3 0] [0 2] [ 3  4  1] [-5  2  1] sage: phi.matrix(side='right') [ 3 -5] [ 4  2] [ 1  1] TESTS:: sage: V = ZZ^2 sage: phi = V.hom([3*V.0, 2*V.1]) sage: phi.matrix(side='junk') Traceback (most recent call last): ... ValueError: side must be 'left' or 'right', not junk """ return self._matrix if not side in ['left', 'right']: raise ValueError("side must be 'left' or 'right', not {0}".format(side)) if side == 'left': return self._matrix else: return self._matrix.transpose() def is_injective(self): """
• ## sage/modules/quotient_module.py

diff --git a/sage/modules/quotient_module.py b/sage/modules/quotient_module.py
 a Basis matrix: [    1 1/3*i 1/3*i] sage: U.quotient_map() Free module morphism defined by the matrix Vector space morphism represented by the matrix: [  1] [3*i] Domain: Vector space of degree 3 and dimension 2 over Number Field in ... Codomain: Vector space quotient V/W of dimension 1 over Number Field in ... Domain: Vector space of degree 3 and dimension 2 over Number Field in i with defining polynomial x^2 + 1 Basis matrix: [ 1  0  i] [ 0  1 -2] Codomain: Vector space quotient V/W of dimension 1 over Number Field in i with defining polynomial x^2 + 1 where V: Vector space of degree 3 and dimension 2 over Number Field in i with defining polynomial x^2 + 1 Basis matrix: [ 1  0  i] [ 0  1 -2] W: Vector space of degree 3 and dimension 1 over Number Field in i with defining polynomial x^2 + 1 Basis matrix: [    1 1/3*i 1/3*i] sage: Z = V.quotient(W) sage: Z == U True EXAMPLES: sage: M = QQ^3 / [[1,2,3]] sage: M.quotient_map() Free module morphism defined by the matrix Vector space morphism represented by the matrix: [   1    0] [   0    1] [-1/3 -2/3] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space quotient V/W of dimension 2 over Rational Field ... Codomain: Vector space quotient V/W of dimension 2 over Rational Field where V: Vector space of dimension 3 over Rational Field W: Vector space of degree 3 and dimension 1 over Rational Field Basis matrix: [1 2 3] sage: M.quotient_map()( (QQ^3)([1,2,3]) ) (0, 0) EXAMPLES: sage: M = QQ^3 / [[1,2,3]] sage: M.lift_map() Free module morphism defined by the matrix Vector space morphism represented by the matrix: [1 0 0] [0 1 0] Domain: Vector space quotient V/W of dimension 2 over Rational Field ... Domain: Vector space quotient V/W of dimension 2 over Rational Field where V: Vector space of dimension 3 over Rational Field W: Vector space of degree 3 and dimension 1 over Rational Field Basis matrix: [1 2 3] Codomain: Vector space of dimension 3 over Rational Field """ return self.__lift_map
• ## new file sage/modules/vector_space_homspace.py

diff --git a/sage/modules/vector_space_homspace.py b/sage/modules/vector_space_homspace.py
new file mode 100644
 - r""" Space of Morphisms of Vector Spaces (Linear Transformations) AUTHOR: - Rob Beezer: (2011-06-29) A :class:VectorSpaceHomspace object represents the set of all possible homomorphisms from one vector space to another. These mappings are usually known as linear transformations. For more information on the use of linear transformations, consult the documentation for vector space morphisms at :mod:sage.modules.vector_space_morphism. Also, this is an extremely thin veneer on free module homspaces (:mod:sage.modules.free_module_homspace) and free module morphisms (:mod:sage.modules.free_module_morphism) - objects which might also be useful, and places where much of the documentation resides. EXAMPLES: Creation and basic examination is simple. :: sage: V = QQ^3 sage: W = QQ^2 sage: H = Hom(V, W) sage: H Set of Morphisms (Linear Transformations) from Vector space of dimension 3 over Rational Field to Vector space of dimension 2 over Rational Field sage: H.domain() Vector space of dimension 3 over Rational Field sage: H.codomain() Vector space of dimension 2 over Rational Field Homspaces have a few useful properties.  A basis is provided by a list of matrix representations, where these matrix representatives are relative to the bases of the domain and codomain.  :: sage: K = Hom(GF(3)^2, GF(3)^2) sage: B = K.basis() sage: for f in B: ...     print f, "\n" Vector space morphism represented by the matrix: [1 0] [0 0] Domain: Vector space of dimension 2 over Finite Field of size 3 Codomain: Vector space of dimension 2 over Finite Field of size 3 Vector space morphism represented by the matrix: [0 1] [0 0] Domain: Vector space of dimension 2 over Finite Field of size 3 Codomain: Vector space of dimension 2 over Finite Field of size 3 Vector space morphism represented by the matrix: [0 0] [1 0] Domain: Vector space of dimension 2 over Finite Field of size 3 Codomain: Vector space of dimension 2 over Finite Field of size 3 Vector space morphism represented by the matrix: [0 0] [0 1] Domain: Vector space of dimension 2 over Finite Field of size 3 Codomain: Vector space of dimension 2 over Finite Field of size 3 The zero and identity mappings are properties of the space. The identity mapping will only be available if the domain and codomain allow for endomorphisms (equal vector spaces with equal bases).  :: sage: H = Hom(QQ^3, QQ^3) sage: g = H.zero() sage: g([1, 1/2, -3]) (0, 0, 0) sage: f = H.identity() sage: f([1, 1/2, -3]) (1, 1/2, -3) The homspace may be used with various representations of a morphism in the space to create the morphism.  We demonstrate three ways to create the same linear transformation between two two-dimensional subspaces of QQ^3.  The V.n notation is a shortcut to the generators of each vector space, better known as the basis elements.  Note that the matrix representations are relative to the bases, which are purposely fixed when the subspaces are created ("user bases").  :: sage: U = QQ^3 sage: V = U.subspace_with_basis([U.0+U.1, U.1-U.2]) sage: W = U.subspace_with_basis([U.0, U.1+U.2]) sage: H = Hom(V, W) First, with a matrix.  Note that the matrix representation acts by matrix multiplication with the vector on the left. The input to the linear transformation, (3, 1, 2), is converted to the coordinate vector (3, -2), then matrix multiplication yields the vector (-3, -2), which represents the vector (-3, -2, -2) in the codomain.  :: sage: m = matrix(QQ, [[1, 2], [3, 4]]) sage: f1 = H(m) sage: f1 Vector space morphism represented by the matrix: [1 2] [3 4] Domain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [ 1  1  0] [ 0  1 -1] Codomain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 0 0] [0 1 1] sage: f1([3,1,2]) (-3, -2, -2) Second, with a list of images of the domain's basis elements.  :: sage: img = [1*(U.0) + 2*(U.1+U.2), 3*U.0 + 4*(U.1+U.2)] sage: f2 = H(img) sage: f2 Vector space morphism represented by the matrix: [1 2] [3 4] Domain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [ 1  1  0] [ 0  1 -1] Codomain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 0 0] [0 1 1] sage: f2([3,1,2]) (-3, -2, -2) Third, with a linear function taking the domain to the codomain.  :: sage: g = lambda x: vector(QQ, [-2*x[0]+3*x[1], -2*x[0]+4*x[1], -2*x[0]+4*x[1]]) sage: f3 = H(g) sage: f3 Vector space morphism represented by the matrix: [1 2] [3 4] Domain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [ 1  1  0] [ 0  1 -1] Codomain: Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 0 0] [0 1 1] sage: f3([3,1,2]) (-3, -2, -2) The three linear transformations look the same, and are the same.  :: sage: f1 == f2 True sage: f2 == f3 True TESTS:: sage: V = QQ^2 sage: W = QQ^3 sage: H = Hom(QQ^2, QQ^3) sage: loads(dumps(H)) Set of Morphisms (Linear Transformations) from Vector space of dimension 2 over Rational Field to Vector space of dimension 3 over Rational Field sage: loads(dumps(H)) == H True """ #################################################################################### #       Copyright (C) 2011 Rob Beezer # #  Distributed under the terms of the GNU General Public License (GPL) # #    This code is distributed in the hope that it will be useful, #    but WITHOUT ANY WARRANTY; without even the implied warranty of #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU #    General Public License for more details. # #  The full text of the GPL is available at: # #                  http://www.gnu.org/licenses/ #################################################################################### import inspect import sage.matrix.all as matrix import sage.modules.free_module_homspace import vector_space_morphism # This module initially overrides just the minimum functionality necessary # from  sage.modules.free_module_homspace.FreeModuleHomSpace. # If additional methods here override the free module homspace methods, # consider adjusting the free module doctests, since many are written with # examples that are actually vector spaces and not so many use "pure" modules # for the examples. def is_VectorSpaceHomspace(x): r""" Return True if x is a vector space homspace. INPUT: x - anything EXAMPLES: To be a vector space morphism, the domain and codomain must both be vector spaces, in other words, modules over fields.  If either set is just a module, then the Hom() constructor will build a space of free module morphisms.  :: sage: H = Hom(QQ^3, QQ^2) sage: type(H) sage: sage.modules.vector_space_homspace.is_VectorSpaceHomspace(H) True sage: K = Hom(QQ^3, ZZ^2) sage: type(K) sage: sage.modules.vector_space_homspace.is_VectorSpaceHomspace(K) False sage: L = Hom(ZZ^3, QQ^2) sage: type(L) sage: sage.modules.vector_space_homspace.is_VectorSpaceHomspace(L) False sage: sage.modules.vector_space_homspace.is_VectorSpaceHomspace('junk') False """ return isinstance(x, VectorSpaceHomspace) class VectorSpaceHomspace(sage.modules.free_module_homspace.FreeModuleHomspace): 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 = QQ^3 sage: V = QQ^4 sage: W = QQ^3 sage: X = QQ^4 sage: H = Hom(U, V) sage: K = Hom(W, X) 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 g.parent() False sage: h = H(g) sage: f.parent() is h.parent() True See other examples in the module-level documentation. """ import vector_space_morphism D = self.domain() C = self.codomain() if matrix.is_Matrix(A): pass elif vector_space_morphism.is_VectorSpaceMorphism(A): A = A.matrix() elif inspect.isfunction(A): try: images = [A(g) for g in D.basis()] except (ValueError, TypeError, IndexError), e: msg = 'function cannot be applied properly to some basis element because\n' + e.args[0] raise ValueError(msg) try: A = matrix.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) 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([C.coordinates(a) for a in v]) except (ArithmeticError, TypeError), 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 vector_space_morphism.VectorSpaceMorphism(self, A) def _repr_(self): r""" Text representation of a space of vector space morphisms. EXAMPLE:: sage: H = Hom(QQ^2, QQ^3) sage: H._repr_().split(' ') ['Set', 'of', 'Morphisms', '(Linear', 'Transformations)', 'from', 'Vector', 'space', 'of', 'dimension', '2', 'over', 'Rational', 'Field', 'to', 'Vector', 'space', 'of', 'dimension', '3', 'over', 'Rational', 'Field'] """ msg = 'Set of Morphisms (Linear Transformations) from {0} to {1}' return msg.format(self.domain(), self.codomain()) No newline at end of file
• ## new file sage/modules/vector_space_morphism.py

diff --git a/sage/modules/vector_space_morphism.py b/sage/modules/vector_space_morphism.py
new file mode 100644
 - r""" Vector Space Morphisms (aka Linear Transformations) AUTHOR: - Rob Beezer: (2011-06-29) A vector space morphism is a homomorphism between vector spaces, better known as a linear transformation.  These are a specialization of Sage's free module homomorphisms.  (A free module is like a vector space, but with scalars from a ring that may not be a field.)  So references to free modules in the documentation or error messages should be understood as simply reflectng a more general situation. Creation -------- The constructor :func:linear_transformation is designed to accept a variety of inputs that can define a linear transformation.  See the documentation of the function for all the possibilities.  Here we give two. First a matrix representation.  By default input matrices are understood to act on vectors placed to left of the matrix.  Optionally, an input matrix can be described as acting on vectors placed to the right.  :: sage: A = matrix(QQ, [[-1, 2, 3], [4, 2, 0]]) sage: phi = linear_transformation(A) sage: phi Vector space morphism represented by the matrix: [-1  2  3] [ 4  2  0] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 3 over Rational Field sage: phi([2, -3]) (-14, -2, 6) A symbolic function can be used to specify the "rule" for a linear transformation, along with explicit descriptions of the domain and codomain.  :: sage: F = Integers(13) sage: D = F^3 sage: C = F^2 sage: x, y, z = var('x y z') sage: f(x, y, z) = [2*x + 3*y + 5*z, x + z] sage: rho = linear_transformation(D, C, f) sage: f(1, 2, 3) (23, 4) sage: rho([1, 2, 3]) (10, 4) A "vector space homspace" is the set of all linear transformations between two vector spaces.  Various input can be coerced into a homspace to create a linear transformation.  See :mod:sage.modules.vector_space_homspace for more. :: sage: D = QQ^4 sage: C = QQ^2 sage: hom_space = Hom(D, C) sage: images = [[1, 3], [2, -1], [4, 0], [3, 7]] sage: zeta = hom_space(images) sage: zeta Vector space morphism represented by the matrix: [ 1  3] [ 2 -1] [ 4  0] [ 3  7] Domain: Vector space of dimension 4 over Rational Field Codomain: Vector space of dimension 2 over Rational Field A homomorphism may also be created via a method on the domain.  :: sage: F = QQ[sqrt(3)] sage: a = F.gen(0) sage: D = F^2 sage: C = F^2 sage: A = matrix(F, [[a, 1], [2*a, 2]]) sage: psi = D.hom(A, C) sage: psi Vector space morphism represented by the matrix: [  sqrt3       1] [2*sqrt3       2] Domain: Vector space of dimension 2 over Number Field in sqrt3 with defining polynomial x^2 - 3 Codomain: Vector space of dimension 2 over Number Field in sqrt3 with defining polynomial x^2 - 3 sage: psi([1, 4]) (9*sqrt3, 9) Properties ---------- Many natural properties of a linear transformation can be computed. Some of these are more general methods of objects in the classes :class:sage.modules.free_module_morphism.FreeModuleMorphism and :class:sage.modules.matrix_morphism.MatrixMorphism. Values are computed in a natural way, an inverse image of an element can be computed with the lift() method, when the inverse image actually exists.  :: sage: A = matrix(QQ, [[1,2], [2,4], [3,6]]) sage: phi = linear_transformation(A) sage: phi([1,2,0]) (5, 10) sage: phi.lift([10, 20]) (10, 0, 0) sage: phi.lift([100, 100]) Traceback (most recent call last): ... ValueError: element is not in the image Images and pre-images can be computed as vector spaces.  :: sage: A = matrix(QQ, [[1,2], [2,4], [3,6]]) sage: phi = linear_transformation(A) sage: phi.image() Vector space of degree 2 and dimension 1 over Rational Field Basis matrix: [1 2] sage: phi.inverse_image( (QQ^2).span([[1,2]]) ) Vector space of degree 3 and dimension 3 over Rational Field Basis matrix: [1 0 0] [0 1 0] [0 0 1] sage: phi.inverse_image( (QQ^2).span([[1,1]]) ) Vector space of degree 3 and dimension 2 over Rational Field Basis matrix: [   1    0 -1/3] [   0    1 -2/3] Injectivity and surjectivity can be checked.  :: sage: A = matrix(QQ, [[1,2], [2,4], [3,6]]) sage: phi = linear_transformation(A) sage: phi.is_injective() False sage: phi.is_surjective() False Restrictions and Representations -------------------------------- It is possible to restrict the domain and codomain of a linear transformation to make a new linear transformation.  We will use those commands to replace the domain and codomain by equal vector spaces, but with alternate bases.  The point here is that the matrix representation used to represent linear transformations are relative to the bases of both the domain and codomain. :: sage: A = graphs.PetersenGraph().adjacency_matrix() sage: V = QQ^10 sage: phi = linear_transformation(V, V, A) sage: phi Vector space morphism represented by the matrix: [0 1 0 0 1 1 0 0 0 0] [1 0 1 0 0 0 1 0 0 0] [0 1 0 1 0 0 0 1 0 0] [0 0 1 0 1 0 0 0 1 0] [1 0 0 1 0 0 0 0 0 1] [1 0 0 0 0 0 0 1 1 0] [0 1 0 0 0 0 0 0 1 1] [0 0 1 0 0 1 0 0 0 1] [0 0 0 1 0 1 1 0 0 0] [0 0 0 0 1 0 1 1 0 0] Domain: Vector space of dimension 10 over Rational Field Codomain: Vector space of dimension 10 over Rational Field sage: B1 = [V.gen(i) + V.gen(i+1) for i in range(9)] + [V.gen(9)] sage: B2 = [V.gen(0)] + [-V.gen(i-1) + V.gen(i) for i in range(1,10)] sage: D = V.subspace_with_basis(B1) sage: C = V.subspace_with_basis(B2) sage: rho = phi.restrict_codomain(C) sage: zeta = rho.restrict_domain(D) sage: zeta Vector space morphism represented by the matrix: [6 5 4 3 3 2 1 0 0 0] [6 5 4 3 2 2 2 1 0 0] [6 6 5 4 3 2 2 2 1 0] [6 5 5 4 3 2 2 2 2 1] [6 4 4 4 3 3 3 3 2 1] [6 5 4 4 4 4 4 4 3 1] [6 6 5 4 4 4 3 3 3 2] [6 6 6 5 4 4 2 1 1 1] [6 6 6 6 5 4 3 1 0 0] [3 3 3 3 3 2 2 1 0 0] Domain: Vector space of degree 10 and dimension 10 over Rational Field User basis matrix: [1 1 0 0 0 0 0 0 0 0] [0 1 1 0 0 0 0 0 0 0] [0 0 1 1 0 0 0 0 0 0] [0 0 0 1 1 0 0 0 0 0] [0 0 0 0 1 1 0 0 0 0] [0 0 0 0 0 1 1 0 0 0] [0 0 0 0 0 0 1 1 0 0] [0 0 0 0 0 0 0 1 1 0] [0 0 0 0 0 0 0 0 1 1] [0 0 0 0 0 0 0 0 0 1] Codomain: Vector space of degree 10 and dimension 10 over Rational Field User basis matrix: [ 1  0  0  0  0  0  0  0  0  0] [-1  1  0  0  0  0  0  0  0  0] [ 0 -1  1  0  0  0  0  0  0  0] [ 0  0 -1  1  0  0  0  0  0  0] [ 0  0  0 -1  1  0  0  0  0  0] [ 0  0  0  0 -1  1  0  0  0  0] [ 0  0  0  0  0 -1  1  0  0  0] [ 0  0  0  0  0  0 -1  1  0  0] [ 0  0  0  0  0  0  0 -1  1  0] [ 0  0  0  0  0  0  0  0 -1  1] An endomorphism is a linear transformation with an equal domain and codomain, and here each needs to have the same basis.  We are using a matrix that has well-behaved eigenvalues, as part of showing that these do not change as the representation changes.  :: sage: A = graphs.PetersenGraph().adjacency_matrix() sage: V = QQ^10 sage: phi = linear_transformation(V, V, A) sage: phi.eigenvalues() [3, -2, -2, -2, -2, 1, 1, 1, 1, 1] sage: B1 = [V.gen(i) + V.gen(i+1) for i in range(9)] + [V.gen(9)] sage: C = V.subspace_with_basis(B1) sage: zeta = phi.restrict(C) sage: zeta Vector space morphism represented by the matrix: [ 1  0  1 -1  2 -1  2 -2  2 -2] [ 1  0  1  0  0  0  1  0  0  0] [ 0  1  0  1  0  0  0  1  0  0] [ 1 -1  2 -1  2 -2  2 -2  3 -2] [ 2 -2  2 -1  1 -1  1  0  1  0] [ 1  0  0  0  0  0  0  1  1  0] [ 0  1  0  0  0  1 -1  1  0  2] [ 0  0  1  0  0  2 -1  1 -1  2] [ 0  0  0  1  0  1  1  0  0  0] [ 0  0  0  0  1 -1  2 -1  1 -1] Domain: Vector space of degree 10 and dimension 10 over Rational Field User basis matrix: [1 1 0 0 0 0 0 0 0 0] [0 1 1 0 0 0 0 0 0 0] [0 0 1 1 0 0 0 0 0 0] [0 0 0 1 1 0 0 0 0 0] [0 0 0 0 1 1 0 0 0 0] [0 0 0 0 0 1 1 0 0 0] [0 0 0 0 0 0 1 1 0 0] [0 0 0 0 0 0 0 1 1 0] [0 0 0 0 0 0 0 0 1 1] [0 0 0 0 0 0 0 0 0 1] Codomain: Vector space of degree 10 and dimension 10 over Rational Field User basis matrix: [1 1 0 0 0 0 0 0 0 0] [0 1 1 0 0 0 0 0 0 0] [0 0 1 1 0 0 0 0 0 0] [0 0 0 1 1 0 0 0 0 0] [0 0 0 0 1 1 0 0 0 0] [0 0 0 0 0 1 1 0 0 0] [0 0 0 0 0 0 1 1 0 0] [0 0 0 0 0 0 0 1 1 0] [0 0 0 0 0 0 0 0 1 1] [0 0 0 0 0 0 0 0 0 1] sage: zeta.eigenvalues() [3, -2, -2, -2, -2, 1, 1, 1, 1, 1] Equality -------- Equality of linear transformations is a bit nuanced.  The equality operator == tests if two linear transformations have equal matrix representations, while we determine if two linear transformations are the same function with the .is_equal_function() method.  Notice in this example that the function never changes, just the representations.  :: sage: f = lambda x: vector(QQ, [x[1], x[0]+x[1], x[0]]) sage: H = Hom(QQ^2, QQ^3) sage: phi = H(f) sage: rho = linear_transformation(QQ^2, QQ^3, matrix(QQ,2, 3, [[0,1,1], [1,1,0]])) sage: phi == rho True sage: U = (QQ^2).subspace_with_basis([[1, 2], [-3, 1]]) sage: V = (QQ^3).subspace_with_basis([[0, 1, 0], [2, 3, 1], [-1, 1, 6]]) sage: K = Hom(U, V) sage: zeta = K(f) sage: zeta == phi False sage: zeta.is_equal_function(phi) True sage: zeta.is_equal_function(rho) True TESTS:: sage: V = QQ^2 sage: H = Hom(V, V) sage: f = H([V.1,-2*V.0]) sage: loads(dumps(f)) Vector space morphism represented by the matrix: [ 0  1] [-2  0] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 2 over Rational Field sage: loads(dumps(f)) == f True """ #################################################################################### #       Copyright (C) 2011 Rob Beezer # #  Distributed under the terms of the GNU General Public License (GPL) # #    This code is distributed in the hope that it will be useful, #    but WITHOUT ANY WARRANTY; without even the implied warranty of #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU #    General Public License for more details. # #  The full text of the GPL is available at: # #                  http://www.gnu.org/licenses/ #################################################################################### import sage.modules.matrix_morphism as matrix_morphism import sage.modules.free_module_morphism as free_module_morphism import vector_space_homspace from sage.matrix.matrix import is_Matrix 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. = 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) else: msg = 'third argument must be a matrix, function, or list of images, not {0}' raise TypeError(msg.format(arg2)) # arg2 now compatible with homspace H call method # __init__ will check matrix sizes versus domain/codomain dimensions return H(arg2) def is_VectorSpaceMorphism(x): r""" Returns True if x is a vector space morphism (a linear transformation). INPUT: x - anything OUTPUT: True only if x is an instance of a vector space morphism, which are also known as linear transformations. EXAMPLES:: sage: V = QQ^2; f = V.hom([V.1,-2*V.0]) sage: sage.modules.vector_space_morphism.is_VectorSpaceMorphism(f) True sage: sage.modules.vector_space_morphism.is_VectorSpaceMorphism('junk') False """ return isinstance(x, VectorSpaceMorphism) class VectorSpaceMorphism(free_module_morphism.FreeModuleMorphism): 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) 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) """ 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 is_invertible(self): r""" Determines if the vector space morphism has an inverse. OUTPUT: True if the vector space morphism is invertible, otherwise False. EXAMPLES: If the dimension of the domain does not match the dimension of the codomain, then the morphism cannot be invertible.  :: sage: V = QQ^3 sage: U = V.subspace_with_basis([V.0 + V.1, 2*V.1 + 3*V.2]) sage: phi = V.hom([U.0, U.0 + U.1, U.0 - U.1], U) sage: phi.is_invertible() False An invertible linear transformation. :: sage: A = matrix(QQ, 3, [[-3, 5, -5], [4, -7, 7], [6, -8, 10]]) sage: A.determinant() 2 sage: H = Hom(QQ^3, QQ^3) sage: rho = H(A) sage: rho.is_invertible() True A non-invertible linear transformation, an endomorphism of a vector space over a finite field.  :: sage: F. = GF(11^2) sage: A = matrix(F, [[6*a + 3,   8*a +  2, 10*a + 3], ...                  [2*a + 7,   4*a +  3,  2*a + 3], ...                  [9*a + 2,  10*a + 10,  3*a + 3]]) sage: A.nullity() 1 sage: E = End(F^3) sage: zeta = E(A) sage: zeta.is_invertible() False """ # endomorphism or not, this is equivalent to invertibility of #   the matrix representation, so any test of this will suffice m = self.matrix() if not m.is_square(): return False return m.rank() == m.ncols() def _latex_(self): r""" A LaTeX representation of this vector space morphism. EXAMPLE:: sage: H = Hom(QQ^3, QQ^2) sage: f = H(matrix(3, 2, range(6))) sage: f._latex_().split(' ') ['\\texttt{vector', 'space', 'morphism', 'from', '}\n\\Bold{Q}^{3}\\texttt{', 'to', '}\n\\Bold{Q}^{2}\\texttt{', 'represented', 'by', 'the', 'matrix', '}\n\\left(\\begin{array}{rr}\n0', '&', '1', '\\\\\n2', '&', '3', '\\\\\n4', '&', '5\n\\end{array}\\right)'] """ from sage.misc.latex import latex s = ('\\texttt{vector space morphism from }\n', self.domain()._latex_(), '\\texttt{ to }\n', self.codomain()._latex_(), '\\texttt{ represented by the matrix }\n', self.matrix()._latex_()) return ''.join(s) def _repr_(self): r""" A text representation of this vector space morphism. EXAMPLE:: sage: H = Hom(QQ^3, QQ^2) sage: f = H(matrix(3, 2, range(6))) sage: f._repr_().split(' ') ['Vector', 'space', 'morphism', 'represented', 'by', 'the', 'matrix:\n[0', '1]\n[2', '3]\n[4', '5]\nDomain:', 'Vector', 'space', 'of', 'dimension', '3', 'over', 'Rational', 'Field\nCodomain:', 'Vector', 'space', 'of', 'dimension', '2', 'over', 'Rational', 'Field'] """ m = self.matrix() msg = ("Vector space morphism represented by the matrix:\n", "{0}\n", "Domain: {1}\n", "Codomain: {2}") return ''.join(msg).format(m, self.domain(), self.codomain())