def 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:`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 :class:`Homset` (e.g. :class:`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 :mod:`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:`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:`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:`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..., ...) ... sage: Q == loads(dumps(Q)) True """ # This should use cache_function instead # However some special handling is currently needed for # domains/docomains that break the unique parent condition. Also, # at some point, it somehow broke the coercion (see e.g. sage -t # sage.rings.real_mpfr). To be investigated. global _cache key = (X, Y, category) try: H = _cache[key] except KeyError: H = None if H is not None: # Return H unless the domain or codomain breaks the unique parent condition if H.domain() is X and H.codomain() is Y: return H # Determines the category if category is None: category = X.category()._meet_(Y.category()) # Recurse to make sure that Hom(X, Y) and Hom(X, Y, category) are identical # No need to check the input again H = Hom(X, Y, category, check=False) else: if check: if not isinstance(category, Category): raise TypeError( "Argument category (= {}) must be a category.".format( category)) for O in [X, Y]: try: category_mismatch = O not in category except BaseException: # An error should not happen, this here is just to be on # the safe side. category_mismatch = True # A category mismatch does not necessarily mean that an error # should be raised. Instead, it could be the case that we are # unpickling an old pickle (that doesn't set the "check" # argument to False). In this case, it could be that the # (co)domain is not properly initialised, which we are # checking now. See trac #16275 and #14793. if category_mismatch and O._is_category_initialized(): # At this point, we can be rather sure that O is properly # initialised, and thus its string representation is # available for the following error message. It simply # belongs to the wrong category. raise ValueError("{} is not in {}".format(O, category)) # Construct H try: # _Hom_ hook from the parent H = X._Hom_(Y, category) except (AttributeError, TypeError): try: # Workaround in case the above fails, but the category # also provides a _Hom_ hook. # FIXME: # - If X._Hom_ actually comes from category and fails, it # will be called twice. # - This is bound to fail if X is an extension type and # does not actually inherit from category.parent_class H = category.parent_class._Hom_(X, Y, category=category) except (AttributeError, TypeError): # By default, construct a plain homset. H = Homset(X, Y, category=category, check=check) _cache[key] = H if isinstance(X, UniqueRepresentation) and isinstance( Y, UniqueRepresentation): if not isinstance(H, WithEqualityById): try: H.__class__ = dynamic_class(H.__class__.__name__ + "_with_equality_by_id", (WithEqualityById, H.__class__), doccls=H.__class__) except BaseException: pass return H
def 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:`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] (1, 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 Join of Category of monoids and Category of enumerated sets 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 :class:`Homset` (e.g. :class:`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 :mod:`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:`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:`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() Category of homsets of unital magmas and right modules over Rational Field and left modules over Rational Field 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 finite simplicial complexes sage: Hom(Set(), S, Sets()) Set of Morphisms from {} to S in Category of sets sage: Hom(S, Set(), Sets()) Set of Morphisms from S to {} 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:`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..., ...) ... sage: Q == loads(dumps(Q)) True """ # This should use cache_function instead # However some special handling is currently needed for # domains/docomains that break the unique parent condition. Also, # at some point, it somehow broke the coercion (see e.g. sage -t # sage.rings.real_mpfr). To be investigated. global _cache key = (X, Y, category) try: H = _cache[key] except KeyError: H = None if H is not None: # Return H unless the domain or codomain breaks the unique parent condition if H.domain() is X and H.codomain() is Y: return H # Determines the category if category is None: category = X.category()._meet_(Y.category()) # Recurse to make sure that Hom(X, Y) and Hom(X, Y, category) are identical # No need to check the input again H = Hom(X, Y, category, check=False) else: if check: if not isinstance(category, Category): raise TypeError("Argument category (= {}) must be a category.".format(category)) for O in [X, Y]: try: category_mismatch = O not in category except Exception: # An error should not happen, this here is just to be on # the safe side. category_mismatch = True # A category mismatch does not necessarily mean that an error # should be raised. Instead, it could be the case that we are # unpickling an old pickle (that doesn't set the "check" # argument to False). In this case, it could be that the # (co)domain is not properly initialised, which we are # checking now. See trac #16275 and #14793. if category_mismatch and O._is_category_initialized(): # At this point, we can be rather sure that O is properly # initialised, and thus its string representation is # available for the following error message. It simply # belongs to the wrong category. raise ValueError("{} is not in {}".format(O, category)) # Construct H try: # _Hom_ hook from the parent H = X._Hom_(Y, category) except (AttributeError, TypeError): try: # Workaround in case the above fails, but the category # also provides a _Hom_ hook. # FIXME: # - If X._Hom_ actually comes from category and fails, it # will be called twice. # - This is bound to fail if X is an extension type and # does not actually inherit from category.parent_class H = category.parent_class._Hom_(X, Y, category=category) except (AttributeError, TypeError): # By default, construct a plain homset. H = Homset(X, Y, category=category, check=check) _cache[key] = H if isinstance(X, UniqueRepresentation) and isinstance(Y, UniqueRepresentation): if not isinstance(H, WithEqualityById): try: H.__class__ = dynamic_class( H.__class__.__name__ + "_with_equality_by_id", (WithEqualityById, H.__class__), doccls=H.__class__ ) except Exception: pass return H
def Hom(X, Y, category=None): """ 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. 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:`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): ... TypeError: 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 :class:`Homset` (e.g. :class:`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 :mod:`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:`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:`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=Category.join([Fields(), ModulesWithBasis(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:: 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 """ # This should use cache_function instead # However some special handling is currently needed for # domains/docomains that break the unique parent condition. Also, # at some point, it somehow broke the coercion (see e.g. sage -t # sage.rings.real_mpfr). To be investigated. global _cache key = (X,Y,category) try: H = _cache[key] except KeyError: H = None if H is not None: # Return H unless the domain or codomain breaks the unique parent condition if H.domain() is X and H.codomain() is Y: return H # Determines the category if category is None: category = X.category()._meet_(Y.category()) # Recurse to make sure that Hom(X, Y) and Hom(X, Y, category) are identical H = Hom(X, Y, category) else: if not isinstance(category, Category): raise TypeError("Argument category (= {}) must be a category.".format(category)) # See trac #14793: It can happen, that Hom(X,X) is called during # unpickling of an instance X of a Python class at a time when # X.__dict__ is empty. In some of these cases, X.category() would # raise a error or would return a too large category (Sets(), for # example) and (worse!) would assign this larger category to the # X._category cdef attribute, so that it would subsequently seem that # X's category was properly initialised. # However, if the above scenario happens, then *before* calling # X.category(), X._is_category_initialised() will correctly say that # it is not initialised. Moreover, since X.__class__ is a Python # class, we will find that `isinstance(X, category.parent_class)`. If # this is the case, then we trust that we indeed are in the process of # unpickling X. Hence, we will trust that `category` has the correct # value, and we will thus skip the test whether `X in category`. try: unpickle_X = (not X._is_category_initialized()) and isinstance(X,category.parent_class) except AttributeError: # this happens for simplicial complexes unpickle_X = False try: unpickle_Y = (not Y._is_category_initialized()) and isinstance(Y,category.parent_class) except AttributeError: unpickle_Y = False if unpickle_X: cat_X = category else: try: cat_X = X.category() except BaseException: raise TypeError("%s is not in %s"%(X, category)) if unpickle_Y: cat_Y = category else: try: cat_Y = Y.category() except BaseException: raise TypeError("%s is not in %s"%(Y, category)) if not cat_X.is_subcategory(category): raise TypeError("%s is not in %s"%(X, category)) if not cat_Y.is_subcategory(category): raise TypeError("%s is not in %s"%(Y, category)) # Construct H try: # _Hom_ hook from the parent H = X._Hom_(Y, category) except (AttributeError, TypeError): try: # Workaround in case the above fails, but the category # also provides a _Hom_ hook. # FIXME: # - If X._Hom_ actually comes from category and fails, it # will be called twice. # - This is bound to fail if X is an extension type and # does not actually inherit from category.parent_class H = category.parent_class._Hom_(X, Y, category = category) except (AttributeError, TypeError): # By default, construct a plain homset. H = Homset(X, Y, category = category) _cache[key] = H if isinstance(X, UniqueRepresentation) and isinstance(Y, UniqueRepresentation): if not isinstance(H, WithEqualityById): try: H.__class__ = dynamic_class(H.__class__.__name__+"_with_equality_by_id", (WithEqualityById, H.__class__), doccls=H.__class__) except BaseException: pass return H