def create_object(self, version, key, **extra_args): r""" Create the valuation specified by ``key``. EXAMPLES:: sage: K.<x> = FunctionField(QQ) sage: R.<x> = QQ[] sage: w = valuations.GaussValuation(R, QQ.valuation(2)) sage: v = K.valuation(w); v # indirect doctest 2-adic valuation """ domain, valuation = key from sage.rings.valuation.valuation_space import DiscretePseudoValuationSpace parent = DiscretePseudoValuationSpace(domain) if isinstance(valuation, tuple) and len(valuation) == 3: valuation, to_valuation_domain, from_valuation_domain = valuation if domain is domain.base() and valuation.domain() is valuation.domain().base() and to_valuation_domain == domain.hom([~valuation.domain().gen()]) and from_valuation_domain == valuation.domain().hom([~domain.gen()]): # valuation on the rational function field after x |--> 1/x if valuation == valuation.domain().valuation(valuation.domain().gen()): # the classical valuation at the place 1/x return parent.__make_element_class__(InfiniteRationalFunctionFieldValuation)(parent) from sage.structure.dynamic_class import dynamic_class clazz = RationalFunctionFieldMappedValuation if valuation.is_discrete_valuation(): clazz = dynamic_class("RationalFunctionFieldMappedValuation_discrete", (clazz, DiscreteValuation)) else: clazz = dynamic_class("RationalFunctionFieldMappedValuation_infinite", (clazz, InfiniteDiscretePseudoValuation)) return parent.__make_element_class__(clazz)(parent, valuation, to_valuation_domain, from_valuation_domain) return parent.__make_element_class__(FunctionFieldExtensionMappedValuation)(parent, valuation, to_valuation_domain, from_valuation_domain) if domain is valuation.domain(): # we can not just return valuation in this case # as this would break uniqueness and pickling raise ValueError("valuation must not be a valuation on domain yet but %r is a valuation on %r"%(valuation, domain)) if domain.base_field() is domain: # valuation is a base valuation on K[x] that induces a valuation on K(x) if valuation.restriction(domain.constant_base_field()).is_trivial() and valuation.is_discrete_valuation(): # valuation corresponds to a finite place return parent.__make_element_class__(FiniteRationalFunctionFieldValuation)(parent, valuation) else: from sage.structure.dynamic_class import dynamic_class clazz = NonClassicalRationalFunctionFieldValuation if valuation.is_discrete_valuation(): clazz = dynamic_class("NonClassicalRationalFunctionFieldValuation_discrete", (clazz, DiscreteFunctionFieldValuation_base)) else: clazz = dynamic_class("NonClassicalRationalFunctionFieldValuation_negative_infinite", (clazz, NegativeInfiniteDiscretePseudoValuation)) return parent.__make_element_class__(clazz)(parent, valuation) else: # valuation is a limit valuation that singles out an extension return parent.__make_element_class__(FunctionFieldFromLimitValuation)(parent, valuation, domain.polynomial(), extra_args['approximants']) raise NotImplementedError("valuation on %r from %r on %r"%(domain, valuation, valuation.domain()))
def _dynamic_MCF_class(base): r""" Return the MCF algorithm associated to some Cython base class including all the Python methods. This is where the fusion of cython methods with python methods is done with the use of ``dynamic_class``. INPUT: - ``base`` -- MCF algorithm base class OUTPUT: class EXAMPLES:: sage: from slabbe.mult_cont_frac_pyx import Brun sage: from slabbe.mult_cont_frac import _dynamic_MCF_class sage: cls = _dynamic_MCF_class(Brun) sage: algo = cls() sage: algo Brun 3-dimensional continued fraction algorithm sage: algo.matrix_cocycle() Cocycle with 6 gens over Regular language over [123, 132, 213, 231, 312, 321] defined by: Automaton with 6 states """ class_name = base().class_name() return dynamic_class(class_name, (base, _MCFAlgorithm_methods))
def _algo_with(base): r""" Return the MCF algorithm associated to some Cython base class including all the Python methods. This is where the fusion of cython methods with python methods is done with the use of ``dynamic_class``. INPUT: - ``base`` -- MCF algorithm base class EXAMPLES:: sage: from slabbe.mult_cont_frac_pyx import Brun sage: from slabbe.mult_cont_frac import _algo_with sage: algo = _algo_with(Brun) sage: algo Brun 3-dimensional continued fraction algorithm sage: algo.matrix_cocycle() Cocycle with 6 gens over Regular language over [123, 132, 213, 231, 312, 321] defined by: Automaton with 6 states """ class_name = base().class_name() cls = dynamic_class(class_name, (base, _MCFAlgorithm_methods)) return cls()
def _late_import(): """ Used to reset the class of PARI finite field elements in their initialization. EXAMPLES:: sage: from sage.rings.finite_rings.element_ext_pari import FiniteField_ext_pariElement sage: k.<a> = GF(3^17, impl='pari_mod') sage: a.__class__ is FiniteField_ext_pariElement # indirect doctest False """ global dynamic_FiniteField_ext_pariElement dynamic_FiniteField_ext_pariElement = dynamic_class("%s_with_category"%FiniteField_ext_pariElement.__name__, (FiniteField_ext_pariElement, FiniteFields().element_class), doccls=FiniteField_ext_pariElement)
def _abstract_element_class(self): """ An abstract class for the elements of this homset. This class is built from the element class of the homset category and the morphism class of the category. This makes it possible for a category to provide code for its morphisms and for morphisms of all its subcategories, full or not. .. NOTE:: The element class of ``C.Homsets()`` will be inherited by morphisms in *full* subcategories of ``C``, while the morphism class of ``C`` will be inherited by *all* subcategories of ``C``. Hence, if some feature of a morphism depends on the algebraic properties of the homsets, it should be implemented by ``C.Homsets.ElementMethods``, but if it depends only on the algebraic properties of domain and codomain, it should be implemented in ``C.MorphismMethods``. At this point, the homset element classes takes precedence over the morphism classes. But this may be subject to change. .. TODO:: - Make sure this class is shared whenever possible. - Flatten join category classes .. SEEALSO:: - :meth:`Parent._abstract_element_class` EXAMPLES: Let's take a homset of finite commutative groups as example; at this point this is the simplest one to create (gosh):: sage: cat = Groups().Finite().Commutative() sage: C3 = PermutationGroup([(1,2,3)]) sage: C3._refine_category_(cat) sage: C2 = PermutationGroup([(1,2)]) sage: C2._refine_category_(cat) sage: H = Hom(C3, C2, cat) sage: H.homset_category() Category of finite commutative groups sage: H.category() Category of homsets of unital magmas sage: cls = H._abstract_element_class; cls <class 'sage.categories.homsets.Homset_with_category._abstract_element_class'> sage: cls.__bases__ == (H.category().element_class, H.homset_category().morphism_class) True A morphism of finite commutative semigroups is also a morphism of semigroups, of magmas, ...; it thus inherits code from all those categories:: sage: issubclass(cls, Semigroups().Finite().morphism_class) True sage: issubclass(cls, Semigroups().morphism_class) True sage: issubclass(cls, Magmas().Commutative().morphism_class) True sage: issubclass(cls, Magmas().morphism_class) True sage: issubclass(cls, Sets().morphism_class) True Recall that FiniteMonoids() is a full subcategory of ``Monoids()``, but not of ``FiniteSemigroups()``. Thus:: sage: issubclass(cls, Monoids().Finite().Homsets().element_class) True sage: issubclass(cls, Semigroups().Finite().Homsets().element_class) False """ class_name = "%s._abstract_element_class" % self.__class__.__name__ return dynamic_class(class_name, (self.category().element_class, self.homset_category().morphism_class))
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 _abstract_element_class(self): """ An abstract class for the elements of this homset. This class is built from the element class of the homset category and the morphism class of the category. This makes it possible for a category to provide code for its morphisms and for morphisms of all its subcategories, full or not. .. NOTE:: The element class of ``C.Homsets()`` will be inherited by morphisms in *full* subcategories of ``C``, while the morphism class of ``C`` will be inherited by *all* subcategories of ``C``. Hence, if some feature of a morphism depends on the algebraic properties of the homsets, it should be implemented by ``C.Homsets.ElementMethods``, but if it depends only on the algebraic properties of domain and codomain, it should be implemented in ``C.MorphismMethods``. At this point, the homset element classes take precedence over the morphism classes. But this may be subject to change. .. TODO:: - Make sure this class is shared whenever possible. - Flatten join category classes .. SEEALSO:: - :meth:`Parent._abstract_element_class` EXAMPLES: Let's take a homset of finite commutative groups as example; at this point this is the simplest one to create (gosh):: sage: cat = Groups().Finite().Commutative() sage: C3 = PermutationGroup([(1,2,3)]) sage: C3._refine_category_(cat) sage: C2 = PermutationGroup([(1,2)]) sage: C2._refine_category_(cat) sage: H = Hom(C3, C2, cat) sage: H.homset_category() Category of finite commutative groups sage: H.category() Category of homsets of unital magmas sage: cls = H._abstract_element_class; cls <class 'sage.categories.homsets.GroupHomset_libgap_with_category._abstract_element_class'> sage: cls.__bases__ == (H.category().element_class, H.homset_category().morphism_class) True A morphism of finite commutative semigroups is also a morphism of semigroups, of magmas, ...; it thus inherits code from all those categories:: sage: issubclass(cls, Semigroups().Finite().morphism_class) True sage: issubclass(cls, Semigroups().morphism_class) True sage: issubclass(cls, Magmas().Commutative().morphism_class) True sage: issubclass(cls, Magmas().morphism_class) True sage: issubclass(cls, Sets().morphism_class) True Recall that FiniteMonoids() is a full subcategory of ``Monoids()``, but not of ``FiniteSemigroups()``. Thus:: sage: issubclass(cls, Monoids().Finite().Homsets().element_class) True sage: issubclass(cls, Semigroups().Finite().Homsets().element_class) False """ class_name = "%s._abstract_element_class" % self.__class__.__name__ return dynamic_class(class_name, (self.category().element_class, self.homset_category().morphism_class))
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 enumerated 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 Category of enumerated 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 (False, False) sage: V = ZZ^3 sage: H1 = Hom(U1, V); H2 = Hom(U2, V) sage: H1 == H2, H1 is H2 (False, False) sage: H1 = Hom(V, U1); H2 = Hom(V, U2) sage: H1 == H2, H1 is H2 (False, 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 <sage.structure.parent.Parent object at ...> to <sage.structure.parent.Parent object at ...> 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 <sage.structure.parent.Parent object at ...> to <sage.structure.parent.Parent object at ...> 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: from sage.sets.pythonclass import Set_PythonType sage: R = Set_PythonType(int) sage: S = Set_PythonType(float) sage: Hom(R, S) Set of Morphisms from Set of Python objects of class 'int' to Set of Python objects of class '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 Check that the ``_Hom_`` method of the ``category`` input is used:: sage: from sage.categories.category_types import Category_over_base_ring sage: class ModulesWithHom(Category_over_base_ring): ....: def super_categories(self): ....: return [Modules(self.base_ring())] ....: class ParentMethods: ....: def _Hom_(self, Y, category=None): ....: print("Modules") ....: raise TypeError sage: class AlgebrasWithHom(Category_over_base_ring): ....: def super_categories(self): ....: return [Algebras(self.base_ring()), ModulesWithHom(self.base_ring())] ....: class ParentMethods: ....: def _Hom_(self, Y, category=None): ....: R = self.base_ring() ....: if category is not None and category.is_subcategory(Algebras(R)): ....: print("Algebras") ....: raise TypeError sage: from sage.structure.element import Element sage: class Foo(Parent): ....: _no_generic_basering_coercion = True ....: class Element(Element): ....: pass sage: X = Foo(base=QQ, category=AlgebrasWithHom(QQ)) sage: H = Hom(X, X, ModulesWithHom(QQ)) Modules """ # 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): # 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 # For join categories, we check all of the direct super # categories as the parent_class of the join category is # not (necessarily) inherited and join categories do not # implement a _Hom_ (see trac #23418). if not isinstance(category, JoinCategory): cats = [category] else: cats = category.super_categories() H = None for C in cats: try: H = C.parent_class._Hom_(X, Y, category=category) break except (AttributeError, TypeError): pass if H is None: # 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 HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): r""" Returns the hyperelliptic curve `y^2 + h y = f`, for univariate polynomials `h` and `f`. If `h` is not given, then it defaults to 0. INPUT: - ``f`` - univariate polynomial - ``h`` - optional univariate polynomial - ``names`` (default: ``["x","y"]``) - names for the coordinate functions - ``check_squarefree`` (default: ``True``) - test if the input defines a hyperelliptic curve when f is homogenized to degree `2g+2` and h to degree `g+1` for some g. .. WARNING:: When setting ``check_squarefree=False`` or using a base ring that is not a field, the output curves are not to be trusted. For example, the output of ``is_singular`` is always ``False``, without this being properly tested in that case. .. NOTE:: The words "hyperelliptic curve" are normally only used for curves of genus at least two, but this class allows more general smooth double covers of the projective line (conics and elliptic curves), even though the class is not meant for those and some outputs may be incorrect. EXAMPLES: Basic examples:: sage: R.<x> = QQ[] sage: HyperellipticCurve(x^5 + x + 1) Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x + 1 sage: HyperellipticCurve(x^19 + x + 1, x-2) Hyperelliptic Curve over Rational Field defined by y^2 + (x - 2)*y = x^19 + x + 1 sage: k.<a> = GF(9); R.<x> = k[] sage: HyperellipticCurve(x^3 + x - 1, x+a) Hyperelliptic Curve over Finite Field in a of size 3^2 defined by y^2 + (x + a)*y = x^3 + x + 2 Characteristic two:: sage: P.<x> = GF(8,'a')[] sage: HyperellipticCurve(x^7+1, x) Hyperelliptic Curve over Finite Field in a of size 2^3 defined by y^2 + x*y = x^7 + 1 sage: HyperellipticCurve(x^8+x^7+1, x^4+1) Hyperelliptic Curve over Finite Field in a of size 2^3 defined by y^2 + (x^4 + 1)*y = x^8 + x^7 + 1 sage: HyperellipticCurve(x^8+1, x) Traceback (most recent call last): ... ValueError: Not a hyperelliptic curve: highly singular at infinity. sage: HyperellipticCurve(x^8+x^7+1, x^4) Traceback (most recent call last): ... ValueError: Not a hyperelliptic curve: singularity in the provided affine patch. sage: F.<t> = PowerSeriesRing(FiniteField(2)) sage: P.<x> = PolynomialRing(FractionField(F)) sage: HyperellipticCurve(x^5+t, x) Hyperelliptic Curve over Laurent Series Ring in t over Finite Field of size 2 defined by y^2 + x*y = x^5 + t We can change the names of the variables in the output:: sage: k.<a> = GF(9); R.<x> = k[] sage: HyperellipticCurve(x^3 + x - 1, x+a, names=['X','Y']) Hyperelliptic Curve over Finite Field in a of size 3^2 defined by Y^2 + (X + a)*Y = X^3 + X + 2 This class also allows curves of genus zero or one, which are strictly speaking not hyperelliptic:: sage: P.<x> = QQ[] sage: HyperellipticCurve(x^2+1) Hyperelliptic Curve over Rational Field defined by y^2 = x^2 + 1 sage: HyperellipticCurve(x^4-1) Hyperelliptic Curve over Rational Field defined by y^2 = x^4 - 1 sage: HyperellipticCurve(x^3+2*x+2) Hyperelliptic Curve over Rational Field defined by y^2 = x^3 + 2*x + 2 Double roots:: sage: P.<x> = GF(7)[] sage: HyperellipticCurve((x^3-x+2)^2*(x^6-1)) Traceback (most recent call last): ... ValueError: Not a hyperelliptic curve: singularity in the provided affine patch. sage: HyperellipticCurve((x^3-x+2)^2*(x^6-1), check_squarefree=False) Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^12 + 5*x^10 + 4*x^9 + x^8 + 3*x^7 + 3*x^6 + 2*x^4 + 3*x^3 + 6*x^2 + 4*x + 3 The input for a (smooth) hyperelliptic curve of genus `g` should not contain polynomials of degree greater than `2g+2`. In the following example, the hyperelliptic curve has genus 2 and there exists a model `y^2 = F` of degree 6, so the model `y^2 + yh = f` of degree 200 is not allowed.:: sage: P.<x> = QQ[] sage: h = x^100 sage: F = x^6+1 sage: f = F-h^2/4 sage: HyperellipticCurve(f, h) Traceback (most recent call last): ... ValueError: Not a hyperelliptic curve: highly singular at infinity. sage: HyperellipticCurve(F) Hyperelliptic Curve over Rational Field defined by y^2 = x^6 + 1 An example with a singularity over an inseparable extension of the base field:: sage: F.<t> = GF(5)[] sage: P.<x> = F[] sage: HyperellipticCurve(x^5+t) Traceback (most recent call last): ... ValueError: Not a hyperelliptic curve: singularity in the provided affine patch. Input with integer coefficients creates objects with the integers as base ring, but only checks smoothness over `\QQ`, not over Spec(`\ZZ`). In other words, it is checked that the discriminant is non-zero, but it is not checked whether the discriminant is a unit in `\ZZ^*`.:: sage: P.<x> = ZZ[] sage: HyperellipticCurve(3*x^7+6*x+6) Hyperelliptic Curve over Integer Ring defined by y^2 = 3*x^7 + 6*x + 6 TESTS: Check that `f` can be a constant (see :trac:`15516`):: sage: R.<u> = PolynomialRing(Rationals()) sage: HyperellipticCurve(-12, u^4 + 7) Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 7)*y = -12 Check that two curves with the same class name have the same class type:: sage: R.<t> = PolynomialRing(GF(next_prime(10^9))) sage: C = HyperellipticCurve(t^5 + t + 1) sage: C2 = HyperellipticCurve(t^5 + 3*t + 1) sage: type(C2) == type(C) True Check that the inheritance is correct:: sage: R.<t> = PolynomialRing(GF(next_prime(10^9))) sage: C = HyperellipticCurve(t^5 + t + 1) sage: type(C).mro() [<class 'sage.schemes.hyperelliptic_curves.constructor.HyperellipticCurve_g2_FiniteField_with_category'>, <class 'sage.schemes.hyperelliptic_curves.constructor.HyperellipticCurve_g2_FiniteField'>, <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_g2.HyperellipticCurve_g2'>, <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_finite_field.HyperellipticCurve_finite_field'>, <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_generic.HyperellipticCurve_generic'>, ...] """ # F is the discriminant; use this for the type check # rather than f and h, one of which might be constant. F = h**2 + 4 * f if not is_Polynomial(F): raise TypeError("Arguments f (= %s) and h (= %s) must be polynomials" % (f, h)) P = F.parent() f = P(f) h = P(h) df = f.degree() dh_2 = 2 * h.degree() if dh_2 < df: g = (df - 1) // 2 else: g = (dh_2 - 1) // 2 if check_squarefree: # Assuming we are working over a field, this checks that after # resolving the singularity at infinity, we get a smooth double cover # of P^1. if P(2) == 0: # characteristic 2 if h == 0: raise ValueError( "In characteristic 2, argument h (= %s) must be non-zero." % h) if h[g + 1] == 0 and f[2 * g + 1]**2 == f[2 * g + 2] * h[g]**2: raise ValueError("Not a hyperelliptic curve: " \ "highly singular at infinity.") should_be_coprime = [h, f * h.derivative()**2 + f.derivative()**2] else: # characteristic not 2 if not F.degree() in [2 * g + 1, 2 * g + 2]: raise ValueError("Not a hyperelliptic curve: " \ "highly singular at infinity.") should_be_coprime = [F, F.derivative()] try: smooth = should_be_coprime[0].gcd( should_be_coprime[1]).degree() == 0 except (AttributeError, NotImplementedError, TypeError): try: smooth = should_be_coprime[0].resultant( should_be_coprime[1]) != 0 except (AttributeError, NotImplementedError, TypeError): raise NotImplementedError("Cannot determine whether " \ "polynomials %s have a common root. Use " \ "check_squarefree=False to skip this check." % \ should_be_coprime) if not smooth: raise ValueError("Not a hyperelliptic curve: " \ "singularity in the provided affine patch.") R = P.base_ring() PP = ProjectiveSpace(2, R) if names is None: names = ["x", "y"] superclass = [] cls_name = ["HyperellipticCurve"] genus_classes = {2: HyperellipticCurve_g2} fields = [("FiniteField", is_FiniteField, HyperellipticCurve_finite_field), ("RationalField", is_RationalField, HyperellipticCurve_rational_field), ("pAdicField", is_pAdicField, HyperellipticCurve_padic_field)] if g in genus_classes: superclass.append(genus_classes[g]) cls_name.append("g%s" % g) for name, test, cls in fields: if test(R): superclass.append(cls) cls_name.append(name) break class_name = "_".join(cls_name) cls = dynamic_class(class_name, tuple(superclass), HyperellipticCurve_generic, doccls=HyperellipticCurve) return cls(PP, f, h, names=names, genus=g)
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