Example #1
0
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
Example #2
0
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
Example #3
0
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