Path: blob/master/src/sage/modules/fg_pid/fgp_morphism.py
8815 views
r"""1Morphisms between finitely generated modules over a PID23AUTHOR:4- William Stein, 20095"""67####################################################################################8# Copyright (C) 2009 William Stein <[email protected]>9#10# Distributed under the terms of the GNU General Public License (GPL)11#12# This code is distributed in the hope that it will be useful,13# but WITHOUT ANY WARRANTY; without even the implied warranty of14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU15# General Public License for more details.16#17# The full text of the GPL is available at:18#19# http://www.gnu.org/licenses/20####################################################################################2122from sage.categories.all import Morphism, is_Morphism23import fgp_module242526class FGP_Morphism(Morphism):27"""28A morphism between finitely generated modules over a PID.2930EXAMPLES:3132An endomorphism::3334sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])35sage: Q = V/W; Q36Finitely generated module V/W over Integer Ring with invariants (4, 12)37sage: phi = Q.hom([Q.0+3*Q.1, -Q.1]); phi38Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(1, 3), (0, 11)]39sage: phi(Q.0) == Q.0 + 3*Q.140True41sage: phi(Q.1) == -Q.142True4344A morphism between different modules V1/W1 ---> V2/W2 in45different ambient spaces::4647sage: V1 = ZZ^2; W1 = V1.span([[1,2],[3,4]]); A1 = V1/W148sage: V2 = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W2 = V2.span([2*V2.0+4*V2.1, 9*V2.0+12*V2.1, 4*V2.2]); A2=V2/W249sage: A150Finitely generated module V/W over Integer Ring with invariants (2)51sage: A252Finitely generated module V/W over Integer Ring with invariants (4, 12)53sage: phi = A1.hom([2*A2.0])54sage: phi(A1.0)55(2, 0)56sage: 2*A2.057(2, 0)58sage: phi(2*A1.0)59(0, 0)6061TESTS::6263sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W64sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1])65sage: loads(dumps(phi)) == phi66True67"""68def __init__(self, parent, phi, check=True):69"""70A morphism between finitely generated modules over a PID.7172EXAMPLES::7374sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])75sage: Q = V/W; Q76Finitely generated module V/W over Integer Ring with invariants (4, 12)77sage: phi = Q.hom([Q.0+3*Q.1, -Q.1]); phi78Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(1, 3), (0, 11)]79sage: phi(Q.0) == Q.0 + 3*Q.180True81sage: phi(Q.1) == -Q.182True8384For full documentation, see :class:`FGP_Morphism`.85"""86Morphism.__init__(self, parent)87M = parent.domain()88N = parent.codomain()89if isinstance(phi, FGP_Morphism):90if check:91if phi.parent() != parent:92raise TypeError93phi = phi._phi94check = False # no need9596# input: phi is a morphism from MO = M.optimized().V() to N.V()97# that sends MO.W() to N.W()98if check:99if not is_Morphism(phi) and M == N:100A = M.optimized()[0].V()101B = N.V()102s = M.base_ring()(phi) * B.coordinate_module(A).basis_matrix()103phi = A.Hom(B)(s)104105MO, _ = M.optimized()106if phi.domain() != MO.V():107raise ValueError, "domain of phi must be the covering module for the optimized covering module of the domain"108if phi.codomain() != N.V():109raise ValueError, "codomain of phi must be the covering module the codomain."110# check that MO.W() gets sent into N.W()111# todo (optimize): this is slow:112for x in MO.W().basis():113if phi(x) not in N.W():114raise ValueError, "phi must send optimized submodule of M.W() into N.W()"115self._phi = phi116117def _repr_(self):118"""119EXAMPLES::120121sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])122sage: Q = V/W; Q123Finitely generated module V/W over Integer Ring with invariants (4, 12)124sage: phi = Q.hom([Q.0+3*Q.1, -Q.1])125sage: phi._repr_()126'Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(1, 3), (0, 11)]'127"""128return "Morphism from module over %s with invariants %s to module with invariants %s that sends the generators to %s"%(129self.domain().base_ring(), self.domain().invariants(), self.codomain().invariants(),130list(self.im_gens()))131132def im_gens(self):133"""134Return tuple of the images of the generators of the domain135under this morphism.136137EXAMPLES::138139sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W140sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1])141sage: phi.im_gens()142((1, 0), (1, 2))143sage: phi.im_gens() is phi.im_gens()144True145"""146try: return self.__im_gens147except AttributeError: pass148self.__im_gens = tuple([self(x) for x in self.domain().gens()])149return self.__im_gens150151def __cmp__(self, right):152"""153EXAMPLES::154155sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W156sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1])157sage: phi.im_gens()158((1, 0), (1, 2))159sage: phi.im_gens() is phi.im_gens()160True161sage: phi == phi162True163sage: psi = Q.hom([Q.0,Q.0 - 2*Q.1])164sage: phi == psi165False166sage: psi = Q.hom([Q.0,Q.0 - 2*Q.1])167sage: cmp(phi,psi)168-1169sage: cmp(psi,phi)1701171sage: psi = Q.hom([Q.0,Q.0 + 2*Q.1])172sage: phi == psi173True174"""175if not isinstance(right, FGP_Morphism):176raise TypeError177a = (self.domain(), self.codomain())178b = (right.domain(), right.codomain())179c = cmp(a,b)180if c: return c181return cmp(self.im_gens(), right.im_gens())182183def __add__(self, right):184"""185EXAMPLES::186187sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])188sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1]); phi189Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(2, 0), (0, 1)]190sage: phi + phi191Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(0, 0), (0, 2)]192"""193if not isinstance(right, FGP_Morphism): # todo: implement using coercion model194right = self.parent()(right)195return FGP_Morphism(self.parent(), self._phi + right._phi, check=fgp_module.DEBUG)196197def __sub__(self, right):198"""199EXAMPLES::200201sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])202sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1])203sage: phi - phi204Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(0, 0), (0, 0)]205"""206if not isinstance(right, FGP_Morphism): # todo: implement using coercion model207right = self.parent()(right)208return FGP_Morphism(self.parent(), self._phi - right._phi, check=fgp_module.DEBUG)209210def __neg__(self):211"""212EXAMPLES::213214sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])215sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1])216sage: -phi217Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(2, 0), (0, 11)]218"""219return FGP_Morphism(self.parent(), self._phi.__neg__(), check=fgp_module.DEBUG)220221def __call__(self, x):222"""223EXAMPLES::224225sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])226sage: Q = V/W227sage: phi = Q.hom([Q.0+3*Q.1, -Q.1]);228sage: phi(Q.0) == Q.0 + 3*Q.1229True230231We compute the image of some submodules of the domain::232233sage: phi(Q)234Finitely generated module V/W over Integer Ring with invariants (4, 12)235sage: phi(Q.submodule([Q.0]))236Finitely generated module V/W over Integer Ring with invariants (4)237sage: phi(Q.submodule([Q.1]))238Finitely generated module V/W over Integer Ring with invariants (12)239sage: phi(W/W)240Finitely generated module V/W over Integer Ring with invariants ()241242We try to evaluate on a module that is not a submodule of the domain, which raises a ValueError::243244sage: phi(V/W.scale(2))245Traceback (most recent call last):246...247ValueError: x must be a submodule or element of the domain248249We evaluate on an element of the domain that is not in the V250for the optimized representation of the domain::251252sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])253sage: Q = V/W; Q254Finitely generated module V/W over Integer Ring with invariants (4, 12)255sage: O, X = Q.optimized()256sage: O.V()257Free module of degree 3 and rank 2 over Integer Ring258User basis matrix:259[0 0 1]260[0 2 0]261sage: phi = Q.hom([Q.0, 4*Q.1])262sage: x = Q(V.0); x263(0, 4)264sage: x == 4*Q.1265True266sage: x in O.V()267False268sage: phi(x)269(0, 4)270sage: phi(4*Q.1)271(0, 4)272sage: phi(4*Q.1) == phi(x)273True274"""275from fgp_module import is_FGP_Module276if is_FGP_Module(x):277if not x.is_submodule(self.domain()):278raise ValueError, "x must be a submodule or element of the domain"279# perhaps can be optimized with a matrix multiply; but note280# the subtlety of optimized representations.281return self.codomain().submodule([self(y) for y in x.smith_form_gens()])282else:283C = self.codomain()284D = self.domain()285O, X = D.optimized()286x = D(x)287if O is D:288x = x.lift()289else:290# Now we have to transform x so that it is in the optimized representation.291x = D.V().coordinate_vector(x.lift()) * X292return C(self._phi(x))293294def kernel(self):295"""296Compute the kernel of this homomorphism.297298EXAMPLES::299300sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])301sage: Q = V/W; Q302Finitely generated module V/W over Integer Ring with invariants (4, 12)303sage: Q.hom([0, Q.1]).kernel()304Finitely generated module V/W over Integer Ring with invariants (4)305sage: A = Q.hom([Q.0, 0]).kernel(); A306Finitely generated module V/W over Integer Ring with invariants (12)307sage: Q.1 in A308True309sage: phi = Q.hom([Q.0-3*Q.1, Q.0+Q.1])310sage: A = phi.kernel(); A311Finitely generated module V/W over Integer Ring with invariants (4)312sage: phi(A)313Finitely generated module V/W over Integer Ring with invariants ()314"""315# The kernel is just got by taking the inverse image of the submodule W316# of the codomain quotient object.317V = self._phi.inverse_image(self.codomain().W())318D = self.domain()319V = D.W() + V320return D._module_constructor(V, D.W(), check=fgp_module.DEBUG)321322def inverse_image(self, A):323"""324Given a submodule A of the codomain of this morphism, return325the inverse image of A under this morphism.326327EXAMPLES::328329sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W; Q330Finitely generated module V/W over Integer Ring with invariants (4, 12)331sage: phi = Q.hom([0, Q.1])332sage: phi.inverse_image(Q.submodule([]))333Finitely generated module V/W over Integer Ring with invariants (4)334sage: phi.kernel()335Finitely generated module V/W over Integer Ring with invariants (4)336sage: phi.inverse_image(phi.codomain())337Finitely generated module V/W over Integer Ring with invariants (4, 12)338339sage: phi.inverse_image(Q.submodule([Q.0]))340Finitely generated module V/W over Integer Ring with invariants (4)341sage: phi.inverse_image(Q.submodule([Q.1]))342Finitely generated module V/W over Integer Ring with invariants (4, 12)343344sage: phi.inverse_image(ZZ^3)345Traceback (most recent call last):346...347TypeError: A must be a finitely generated quotient module348sage: phi.inverse_image(ZZ^3 / W.scale(2))349Traceback (most recent call last):350...351ValueError: A must be a submodule of the codomain352"""353from fgp_module import is_FGP_Module354if not is_FGP_Module(A):355raise TypeError, "A must be a finitely generated quotient module"356if not A.is_submodule(self.codomain()):357raise ValueError, "A must be a submodule of the codomain"358V = self._phi.inverse_image(A.V())359D = self.domain()360V = D.W() + V361return D._module_constructor(V, D.W(), check=fgp_module.DEBUG)362363def image(self):364"""365Compute the image of this homomorphism.366367EXAMPLES::368369sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])370sage: Q = V/W; Q371Finitely generated module V/W over Integer Ring with invariants (4, 12)372sage: Q.hom([Q.0+3*Q.1, -Q.1]).image()373Finitely generated module V/W over Integer Ring with invariants (4, 12)374sage: Q.hom([3*Q.1, Q.1]).image()375Finitely generated module V/W over Integer Ring with invariants (12)376"""377V = self._phi.image() + self.codomain().W()378W = V.intersection(self.codomain().W())379return self.codomain()._module_constructor(V, W, check=fgp_module.DEBUG)380381def lift(self, x):382"""383Given an element x in the codomain of self, if possible find an384element y in the domain such that self(y) == x. Raise a ValueError385if no such y exists.386387INPUT:388389- ``x`` -- element of the codomain of self.390391EXAMPLES::392393sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])394sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1])395sage: phi.lift(Q.1)396(0, 1)397sage: phi.lift(Q.0)398Traceback (most recent call last):399...400ValueError: no lift of element to domain401sage: phi.lift(2*Q.0)402(1, 0)403sage: phi.lift(2*Q.0+Q.1)404(1, 1)405sage: V = span([[5, -1/2]],ZZ); W = span([[20,-2]],ZZ); Q = V/W; phi=Q.hom([2*Q.0])406sage: x = phi.image().0; phi(phi.lift(x)) == x407True408409"""410x = self.codomain()(x)411412# We view self as a map V/W --> V'/W', where V/W is the413# optimized representation (which is fine to work with since414# there is a lift to the optimized representation if and only415# if there is a lift to the non-optimized representation).416CD = self.codomain()417A = self._phi.matrix()418try:419H, U = self.__lift_data420except AttributeError:421# Get the matrix of self: V --> V' wrt the basis for V and V'.422423# Stack it on top of the basis for W'.424Wp = CD.V().coordinate_module(CD.W()).basis_matrix()425B = A.stack(Wp)426427# Compute Hermite form of C with transformation428H, U = B.hermite_form(transformation=True)429self.__lift_data = H, U430431# write x in terms of the basis for V.432w = CD.V().coordinate_vector(x.lift())433434# Solve z*H = w.435try:436z = H.solve_left(w)437if z.denominator() != 1:438raise ValueError439except ValueError:440raise ValueError, "no lift of element to domain"441442# Write back in terms of rows of B, and delete rows not corresponding to A,443# since those corresponding to relations444v = (z*U)[:A.nrows()]445446# Take the linear combination that v defines.447y = v*self.domain().optimized()[0].V().basis_matrix()448449# Return the finitely generated module element defined by y.450y = self.domain()(y)451assert self(y) == x, "bug in phi.lift()"452return y453454from sage.categories.homset import Homset455456import sage.misc.weak_dict457_fgp_homset = sage.misc.weak_dict.WeakValueDictionary()458def FGP_Homset(X, Y):459"""460EXAMPLES::461462sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W463sage: Q.Hom(Q) # indirect doctest464Set of Morphisms from Finitely generated module V/W over Integer Ring with invariants (4, 12) to Finitely generated module V/W over Integer Ring with invariants (4, 12) in Category of modules over Integer Ring465sage: True # Q.Hom(Q) is Q.Hom(Q)466True467sage: type(Q.Hom(Q))468<class 'sage.modules.fg_pid.fgp_morphism.FGP_Homset_class_with_category'>469"""470key = (X,Y)471try: return _fgp_homset[key]472except KeyError: pass473H = FGP_Homset_class(X, Y)474# Caching breaks tests in fgp_module.475# _fgp_homset[key] = H476return H477478479class FGP_Homset_class(Homset):480def __init__(self, X, Y):481"""482EXAMPLES::483484sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W485sage: type(Q.Hom(Q))486<class 'sage.modules.fg_pid.fgp_morphism.FGP_Homset_class_with_category'>487"""488Homset.__init__(self, X, Y)489self._populate_coercion_lists_(element_constructor = FGP_Morphism,490coerce_list = [])491492def _coerce_map_from_(self, S):493"""494EXAMPLES::495496sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W497sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1]); psi = loads(dumps(phi))498sage: phi.parent()._coerce_map_from_(psi.parent())499True500sage: phi.parent()._coerce_map_from_(Q.Hom(ZZ^3))501False502"""503# We define this so that morphisms in equal parents canonically coerce,504# since otherwise, e.g., the dumps(loads(...)) doctest above would fail.505if isinstance(S, FGP_Homset_class) and S == self:506return True507if self.is_endomorphism_set():508R = self.domain().base_ring()509return R == S or bool(R._coerce_map_from_(S))510return False511512def __call__(self, x):513"""514Convert x into an fgp morphism.515516EXAMPLES::517518sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ); W = V.span([V.0+2*V.1, 9*V.0+2*V.1, 4*V.2])519sage: Q = V/W; H = Q.Hom(Q)520sage: H(3)521Morphism from module over Integer Ring with invariants (4, 16) to module with invariants (4, 16) that sends the generators to [(3, 0), (0, 3)]522"""523return FGP_Morphism(self, x)524525526527