# Homsets¶

The class Hom is the base class used to represent sets of morphisms between objects of a given category. Hom objects are usually “weakly” cached upon creation so that they don’t have to be generated over and over but can be garbage collected together with the corresponding objects when these are are not stongly ref’ed anymore.

EXAMPLES:

In the following, the Hom object is indeed cached:

sage: K = GF(17)
sage: H = Hom(ZZ, K)
sage: H
Set of Homomorphisms from Integer Ring to Finite Field of size 17
sage: H is Hom(ZZ, K)
True


Nonetheless, garbage collection occurs when the original references are overwritten:

sage: for p in prime_range(200):
...     K = GF(p)
...     H = Hom(ZZ, K)
...
sage: import gc
sage: _ = gc.collect()
sage: from sage.rings.finite_rings.finite_field_prime_modn import FiniteField_prime_modn as FF
sage: L = [x for x in gc.get_objects() if isinstance(x, FF)]
sage: len(L)
2
sage: L
[Finite Field of size 2, Finite Field of size 199]


AUTHORS:

• David Kohel and William Stein
• David Joyner (2005-12-17): added examples
• William Stein (2006-01-14): Changed from Homspace to Homset.
• Nicolas M. Thiery (2008-12-): Updated for the new category framework
• Simon King (2011-12): Use a weak cache for homsets
• Simon King (2013-02): added examples
sage.categories.homset.End(X, category=None)

Create the set of endomorphisms of X in the category category.

INPUT:

• X – anything
• category – (optional) category in which to coerce X

OUTPUT:

A set of endomorphisms in category

EXAMPLES:

sage: V = VectorSpace(QQ, 3)
sage: End(V)
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: S = End(G); S
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: from sage.categories.homset import is_Endset
sage: is_Endset(S)
True
sage: S.domain()
Alternating group of order 3!/2 as a permutation group


To avoid creating superfluous categories, homsets are in the homset category of the lowest category which currently says something specific about its homsets. For example, S is not in the category of hom sets of the category of groups:

sage: S.category()
Category of hom sets in Category of sets
sage: End(QQ).category()
Category of hom sets in Category of rings

sage.categories.homset.Hom(X, Y, category=None, check=True)

Create the space of homomorphisms from X to Y in the category category.

INPUT:

• X – an object of a category
• Y – an object of a category
• category – a category in which the morphisms must be. (default: the meet of the categories of X and Y) Both X and Y must belong to that category.
• check – a boolean (default: True): whether to check the input, and in particular that X and Y belong to category.

OUTPUT: a homset in category

EXAMPLES:

sage: V = VectorSpace(QQ,3)
sage: Hom(V, V)
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: Hom(ZZ, QQ, Sets())
Set of Morphisms from Integer Ring to Rational Field in Category of sets

sage: Hom(FreeModule(ZZ,1), FreeModule(QQ,1))
Set of Morphisms from Ambient free module of rank 1 over the principal ideal domain Integer Ring to Vector space of dimension 1 over Rational Field in Category of commutative additive groups
sage: Hom(FreeModule(QQ,1), FreeModule(ZZ,1))
Set of Morphisms from Vector space of dimension 1 over Rational Field to Ambient free module of rank 1 over the principal ideal domain Integer Ring in Category of commutative additive groups


Here, we test against a memory leak that has been fixed at trac ticket #11521 by using a weak cache:

sage: for p in prime_range(10^3):
...    K = GF(p)
...    a = K(0)
sage: import gc
sage: gc.collect()       # random
624
sage: from sage.rings.finite_rings.finite_field_prime_modn import FiniteField_prime_modn as FF
sage: L = [x for x in gc.get_objects() if isinstance(x, FF)]
sage: len(L), L[0], L[len(L)-1]
(2, Finite Field of size 2, Finite Field of size 997)


To illustrate the choice of the category, we consider the following parents as running examples:

sage: X = ZZ; X
Integer Ring
sage: Y = SymmetricGroup(3); Y
Symmetric group of order 3! as a permutation group


By default, the smallest category containing both X and Y, is used:

sage: Hom(X, Y)
Set of Morphisms from Integer Ring to Symmetric group of order 3! as a permutation group in Category of monoids


Otherwise, if category is specified, then category is used, after checking that X and Y are indeed in category:

sage: Hom(X, Y, Magmas())
Set of Morphisms from Integer Ring to Symmetric group of order 3! as a permutation group in Category of magmas

sage: Hom(X, Y, Groups())
Traceback (most recent call last):
...
ValueError: Integer Ring is not in Category of groups


A parent (or a parent class of a category) may specify how to construct certain homsets by implementing a method _Hom_(self, codomain, category). This method should either construct the requested homset or raise a TypeError. This hook is currently mostly used to create homsets in some specific subclass of Homset (e.g. sage.rings.homset.RingHomset):

sage: Hom(QQ,QQ).__class__
<class 'sage.rings.homset.RingHomset_generic_with_category'>


Do not call this hook directly to create homsets, as it does not handle unique representation:

sage: Hom(QQ,QQ) == QQ._Hom_(QQ, category=QQ.category())
True
sage: Hom(QQ,QQ) is QQ._Hom_(QQ, category=QQ.category())
False


TESTS:

Homset are unique parents:

sage: k = GF(5)
sage: H1 = Hom(k,k)
sage: H2 = Hom(k,k)
sage: H1 is H2
True


Moreover, if no category is provided, then the result is identical with the result for the meet of the categories of the domain and the codomain:

sage: Hom(QQ, ZZ) is Hom(QQ,ZZ, Category.meet([QQ.category(), ZZ.category()]))
True


Some doc tests in sage.rings (need to) break the unique parent assumption. But if domain or codomain are not unique parents, then the homset will not fit. That is to say, the hom set found in the cache will have a (co)domain that is equal to, but not identical with, the given (co)domain.

By trac ticket #9138, we abandon the uniqueness of homsets, if the domain or codomain break uniqueness:

sage: from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict_domain
sage: P.<x,y,z>=MPolynomialRing_polydict_domain(QQ, 3, order='degrevlex')
sage: Q.<x,y,z>=MPolynomialRing_polydict_domain(QQ, 3, order='degrevlex')
sage: P == Q
True
sage: P is Q
False


Hence, P and Q are not unique parents. By consequence, the following homsets aren’t either:

sage: H1 = Hom(QQ,P)
sage: H2 = Hom(QQ,Q)
sage: H1 == H2
True
sage: H1 is H2
False


It is always the most recently constructed homset that remains in the cache:

sage: H2 is Hom(QQ,Q)
True


Variation on the theme:

sage: U1 = FreeModule(ZZ,2)
sage: U2 = FreeModule(ZZ,2,inner_product_matrix=matrix([[1,0],[0,-1]]))
sage: U1 == U2, U1 is U2
(True, False)
sage: V = ZZ^3
sage: H1 = Hom(U1, V); H2 = Hom(U2, V)
sage: H1 == H2, H1 is H2
(True, False)
sage: H1 = Hom(V, U1); H2 = Hom(V, U2)
sage: H1 == H2, H1 is H2
(True, False)


Since trac ticket #11900, the meet of the categories of the given arguments is used to determine the default category of the homset. This can also be a join category, as in the following example:

sage: PA = Parent(category=Algebras(QQ))
sage: PJ = Parent(category=Rings() & Modules(QQ))
sage: Hom(PA,PJ)
Set of Homomorphisms from <type 'sage.structure.parent.Parent'> to <type 'sage.structure.parent.Parent'>
sage: Hom(PA,PJ).category()
Join of Category of hom sets in Category of modules over Rational Field and Category of hom sets in Category of rings
sage: Hom(PA,PJ, Rngs())
Set of Morphisms from <type 'sage.structure.parent.Parent'> to <type 'sage.structure.parent.Parent'> in Category of rngs


Todo

• Design decision: how much of the homset comes from the category of X and Y, and how much from the specific X and Y. In particular, do we need several parent classes depending on X and Y, or does the difference only lie in the elements (i.e. the morphism), and of course how the parent calls their constructors.
• Specify the protocol for the _Hom_ hook in case of ambiguity (e.g. if both a parent and some category thereof provide one).

TESTS:

Facade parents over plain Python types are supported:

sage: R = sage.structure.parent.Set_PythonType(int)
sage: S = sage.structure.parent.Set_PythonType(float)
sage: Hom(R, S)
Set of Morphisms from Set of Python objects of type 'int' to Set of Python objects of type 'float' in Category of sets


Checks that the domain and codomain are in the specified category. Case of a non parent:

sage: S = SimplicialComplex([[1,2], [1,4]]); S.rename("S")
sage: Hom(S, S, SimplicialComplexes())
Set of Morphisms from S to S in Category of simplicial complexes

sage: H = Hom(Set(), S, Sets())
Traceback (most recent call last):
...
ValueError: S is not in Category of sets

sage: H = Hom(S, Set(), Sets())
Traceback (most recent call last):
...
ValueError: S is not in Category of sets

sage: H = Hom(S, S, ChainComplexes(QQ))
Traceback (most recent call last):
...
ValueError: S is not in Category of chain complexes over Rational Field


Those checks are done with the natural idiom X in category, and not X.category().is_subcategory(category) as it used to be before :trac:16275: (see trac ticket #15801 for a real use case):

sage: class PermissiveCategory(Category):
....:     def super_categories(self): return [Objects()]
....:     def __contains__(self, X): return True
sage: C = PermissiveCategory(); C.rename("Permissive category")
sage: S.category().is_subcategory(C)
False
sage: S in C
True
sage: Hom(S, S, C)
Set of Morphisms from S to S in Permissive category


With check=False, unitialized parents, as can appear upon unpickling, are supported. Case of a parent:

sage: cls = type(Set())
sage: S = unpickle_newobj(cls, ())  # A non parent
sage: H = Hom(S, S, SimplicialComplexes(), check=False);
sage: H = Hom(S, S, Sets(),                check=False)
sage: H = Hom(S, S, ChainComplexes(QQ),    check=False)


Case of a non parent:

sage: cls = type(SimplicialComplex([[1,2], [1,4]]))
sage: S = unpickle_newobj(cls, ())
sage: H = Hom(S, S, Sets(),                check=False)
sage: H = Hom(S, S, Groups(),              check=False)
sage: H = Hom(S, S, SimplicialComplexes(), check=False)


Typical example where unpickling involves calling Hom on an unitialized parent:

sage: P.<x,y> = QQ['x,y']
sage: Q = P.quotient([x^2-1,y^2-1])
sage: q = Q.an_element()
sage: explain_pickle(dumps(Q))
pg_...
... = pg_dynamic_class('QuotientRing_generic_with_category', (pg_QuotientRing_generic, pg_getattr(..., 'parent_class')), None, None, pg_QuotientRing_generic)
si... = unpickle_newobj(..., ())
...
si... = pg_unpickle_MPolynomialRing_libsingular(..., ('x', 'y'), ...)
si... = ... pg_Hom(si..., si..., ...) ...
True

class sage.categories.homset.Homset(X, Y, category=None, base=None, check=True)

The class for collections of morphisms in a category.

EXAMPLES:

sage: H = Hom(QQ^2, QQ^3)
True


Homsets of non-unique parents are non-unique as well:

sage: H = End(AffineSpace(2, names='x,y'))
sage: loads(dumps(AffineSpace(2, names='x,y'))) is AffineSpace(2, names='x,y')
False
sage: loads(dumps(AffineSpace(2, names='x,y'))) == AffineSpace(2, names='x,y')
True
False
True

codomain()

Return the codomain of this homset.

EXAMPLES:

sage: P.<t> = ZZ[]
sage: f = P.hom([1/2*t])
sage: f.parent().codomain()
Univariate Polynomial Ring in t over Rational Field
sage: f.codomain() is f.parent().codomain()
True

coerce_map_from_c(R)

Warning

For compatibility with old coercion model. DO NOT USE!

TESTS:

sage: H = Hom(ZZ^2, ZZ^3)
sage: H.coerce_map_from_c(ZZ)

domain()

Return the domain of this homset.

EXAMPLES:

sage: P.<t> = ZZ[]
sage: f = P.hom([1/2*t])
sage: f.parent().domain()
Univariate Polynomial Ring in t over Integer Ring
sage: f.domain() is f.parent().domain()
True

element_class_set_morphism()

A base class for elements of this homset which are also SetMorphism, i.e. implemented by mean of a Python function.

This is currently plain SetMorphism, without inheritance from categories.

Todo

Refactor during the upcoming homset cleanup.

EXAMPLES:

sage: H = Hom(ZZ, ZZ)
sage: H.element_class_set_morphism
<type 'sage.categories.morphism.SetMorphism'>

get_action_c(R, op, self_on_left)

Warning

For compatibility with old coercion model. DO NOT USE!

TESTS:

sage: H = Hom(ZZ^2, ZZ^3)

homset_category()

Return the category that this is a Hom in, i.e., this is typically the category of the domain or codomain object.

EXAMPLES:

sage: H = Hom(AlternatingGroup(4), AlternatingGroup(7))
sage: H.homset_category()
Category of finite permutation groups

identity()

The identity map of this homset.

Note

Of course, this only exists for sets of endomorphisms.

EXAMPLES:

sage: H = Hom(QQ,QQ)
sage: H.identity()
Identity endomorphism of Rational Field
sage: H = Hom(ZZ,QQ)
sage: H.identity()
Traceback (most recent call last):
...
TypeError: Identity map only defined for endomorphisms. Try natural_map() instead.
sage: H.natural_map()
Ring Coercion morphism:
From: Integer Ring
To:   Rational Field

is_endomorphism_set()

Return True if the domain and codomain of self are the same object.

EXAMPLES:

sage: P.<t> = ZZ[]
sage: f = P.hom([1/2*t])
sage: f.parent().is_endomorphism_set()
False
sage: g = P.hom([2*t])
sage: g.parent().is_endomorphism_set()
True

natural_map()

Return the “natural map” of this homset.

Note

By default, a formal coercion morphism is returned.

EXAMPLES:

sage: H = Hom(ZZ['t'],QQ['t'], CommutativeAdditiveGroups())
sage: H.natural_map()
Coercion morphism:
From: Univariate Polynomial Ring in t over Integer Ring
To:   Univariate Polynomial Ring in t over Rational Field
sage: H = Hom(QQ['t'],GF(3)['t'])
sage: H.natural_map()
Traceback (most recent call last):
...
TypeError: Natural coercion morphism from Univariate Polynomial Ring in t over Rational Field to Univariate Polynomial Ring in t over Finite Field of size 3 not defined.

reversed()

Return the corresponding homset, but with the domain and codomain reversed.

EXAMPLES:

sage: H = Hom(ZZ^2, ZZ^3); H
Set of Morphisms from Ambient free module of rank 2 over the principal ideal domain Integer Ring to Ambient free module of rank 3 over the principal ideal domain Integer Ring in Category of modules with basis over Integer Ring
sage: type(H)
<class 'sage.modules.free_module_homspace.FreeModuleHomspace_with_category'>
sage: H.reversed()
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: type(H.reversed())
<class 'sage.modules.free_module_homspace.FreeModuleHomspace_with_category'>

class sage.categories.homset.HomsetWithBase(X, Y, category=None, check=True, base=None)

TESTS:

sage: X = ZZ['x']; X.rename("X")
sage: Y = ZZ['y']; Y.rename("Y")
sage: class MyHomset(HomsetWithBase):
...       def my_function(self, x):
...           return Y(x[0])
...       def _an_element_(self):
...           return sage.categories.morphism.SetMorphism(self, self.my_function)
...
sage: import __main__; __main__.MyHomset = MyHomset # fakes MyHomset being defined in a Python module
sage: H = MyHomset(X, Y, category=Monoids())
sage: H
Set of Morphisms from X to Y in Category of monoids
sage: H.base()
Integer Ring
sage: TestSuite(H).run()

sage.categories.homset.end(X, f)

Return End(X)(f), where f is data that defines an element of End(X).

EXAMPLES:

sage: R, x = PolynomialRing(QQ,'x').objgen()
sage: phi = end(R, [x + 1])
sage: phi
Ring endomorphism of Univariate Polynomial Ring in x over Rational Field
Defn: x |--> x + 1
sage: phi(x^2 + 5)
x^2 + 2*x + 6

sage.categories.homset.hom(X, Y, f)

Return Hom(X,Y)(f), where f is data that defines an element of Hom(X,Y).

EXAMPLES:

sage: R, x = PolynomialRing(QQ,'x').objgen()
sage: phi = hom(R, QQ, [2])
sage: phi(x^2 + 3)
7

sage.categories.homset.is_Endset(x)

Return True if x is a set of endomorphisms in a category.

EXAMPLES:

sage: from sage.categories.homset import is_Endset
sage: P.<t> = ZZ[]
sage: f = P.hom([1/2*t])
sage: is_Endset(f.parent())
False
sage: g = P.hom([2*t])
sage: is_Endset(g.parent())
True

sage.categories.homset.is_Homset(x)

Return True if x is a set of homomorphisms in a category.

EXAMPLES:

sage: from sage.categories.homset import is_Homset
sage: P.<t> = ZZ[]
sage: f = P.hom([1/2*t])
sage: is_Homset(f)
False
sage: is_Homset(f.category())
False
sage: is_Homset(f.parent())
True
`

#### Previous topic

Base class for maps

Morphisms