diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 5fd49d46ec9..f7120ebb8dd 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -208,7 +208,7 @@ def Hom(X, Y, category=None, check=True): 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()])) + sage: Hom(QQ, ZZ) is Hom(QQ, ZZ, category=QQ.category() | ZZ.category()) True Some doc tests in :mod:`sage.rings` (need to) break the unique @@ -270,9 +270,8 @@ def Hom(X, Y, category=None, check=True): from to sage: Hom(PA, PJ).category() - Category of homsets of - unital magmas and right modules over Rational Field - and left modules over Rational Field + 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 @@ -412,7 +411,7 @@ def Hom(X, Y, category=None, check=True): # Determines the category if category is None: - category = X.category()._meet_(Y.category()) + category = X.category() | 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) @@ -654,11 +653,23 @@ def __init__(self, X, Y, category=None, base=None, check=True): sage: MyHomset(ZZ^3, ZZ^3, base=QQ).base_ring() # needs sage.modules Rational Field + + Check that :issue:`37730` is resolved:: + + sage: E = EllipticCurve(QQ, [3, 5]) + sage: E.point_homset().category() + Join of Category of commutative additive groups and Category of + homsets of schemes over Rational Field + sage: E.point_homset() in CommutativeAdditiveGroups() + True + sage: from sage.schemes.generic.homset import SchemeHomset_points + sage: SchemeHomset_points(Spec(QQ), E) in CommutativeAdditiveGroups() + True """ self._domain = X self._codomain = Y if category is None: - category = X.category() + category = X.category() | Y.category() self.__category = category if check: if not isinstance(category, Category): @@ -676,8 +687,26 @@ def __init__(self, X, Y, category=None, base=None, check=True): # See also #15801. base = X.base_ring() - Parent.__init__(self, base=base, - category=category.Endsets() if X is Y else category.Homsets()) + # Hom(X, AbGrp) has an AbGrp structure by pointwise operation. Not sure + # if we should also inherit the multiplication operation when it exists + category = category.Endsets() if X is Y else category.Homsets() + + # We handle abelian varieties as a special case, since they themselves + # don't have an abelian group, only their point homsets, see discussion + # under #37768. + from sage.categories.schemes import Sets, AbelianVarieties + from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups + + # TODO: Uncomment this once sum of morphisms is implemented (see #37768) + # group_cat = Y.category() | CommutativeAdditiveGroups() + group_cat = Sets() + try: + if Y in AbelianVarieties(Y._base_scheme): + group_cat = CommutativeAdditiveGroups() + except (AttributeError, ValueError): + pass + + Parent.__init__(self, base=base, category=category & group_cat) def __reduce__(self): """ diff --git a/src/sage/categories/schemes.py b/src/sage/categories/schemes.py index 68187fa2257..b0adfacf34f 100644 --- a/src/sage/categories/schemes.py +++ b/src/sage/categories/schemes.py @@ -23,12 +23,13 @@ from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.categories.homsets import HomsetsCategory +from sage.structure.unique_representation import UniqueRepresentation from sage.misc.abstract_method import abstract_method from sage.misc.lazy_import import lazy_import lazy_import('sage.categories.map', 'Map') lazy_import('sage.schemes.generic.morphism', 'SchemeMorphism') -lazy_import('sage.schemes.generic.scheme', 'Scheme') +lazy_import('sage.schemes.generic.scheme', ['Scheme', 'AffineScheme']) class Schemes(Category): @@ -197,7 +198,6 @@ def _repr_object_names(self): sage: Schemes(Spec(ZZ)) # indirect doctest Category of schemes over Integer Ring """ - from sage.schemes.generic.scheme import AffineScheme base = self.base() if isinstance(base, AffineScheme): base = base.coordinate_ring() @@ -206,7 +206,7 @@ def _repr_object_names(self): class AbelianVarieties(Schemes_over_base): r""" - The category of abelian varieties over a given field. + The category of abelian varieties over a given field scheme. EXAMPLES:: @@ -217,22 +217,43 @@ class AbelianVarieties(Schemes_over_base): ... ValueError: category of abelian varieties is only defined over fields """ + @staticmethod + def __classcall__(cls, base): + r""" + Normalise arguments for the constructor of the + :class:`AbelianVarieties` category. + + EXAMPLES:: + + sage: AbelianVarieties(QQ) is AbelianVarieties(Spec(QQ)) # indirect doctest + True + """ + if not isinstance(base, Scheme): + base = Schemes()(base) + if not (isinstance(base, AffineScheme) and base.coordinate_ring() in Fields()): + raise ValueError('category of abelian varieties is only defined over fields') + return UniqueRepresentation.__classcall__(cls, base) + def __init__(self, base): r""" - Constructor for the ``AbelianVarieties`` category. + Cosntructor for the :class:`AbelianVarieties` category. EXAMPLES:: - sage: AbelianVarieties(QQ) + sage: A = AbelianVarieties(QQ); A Category of abelian varieties over Rational Field - sage: AbelianVarieties(Spec(QQ)) + sage: B = AbelianVarieties(Spec(QQ)); B Category of abelian varieties over Rational Field + sage: A is B + True + + The category of abelian varieties is only defined over fields:: + + sage: AbelianVarieties(ZZ) + Traceback (most recent call last): + ... + ValueError: category of abelian varieties is only defined over fields """ - from sage.schemes.generic.scheme import AffineScheme - if isinstance(base, AffineScheme): - base = base.coordinate_ring() - if base not in Fields(): - raise ValueError('category of abelian varieties is only defined over fields') super().__init__(base) def base_scheme(self): @@ -252,10 +273,9 @@ def super_categories(self): EXAMPLES:: sage: AbelianVarieties(QQ).super_categories() - [Category of schemes over Rational Field, - Category of commutative additive groups] + [Category of schemes over Rational Field] """ - return [Schemes(self.base_scheme()), CommutativeAdditiveGroups()] + return [Schemes(self.base_scheme())] def _repr_object_names(self): """ @@ -264,7 +284,31 @@ def _repr_object_names(self): sage: AbelianVarieties(Spec(QQ)) # indirect doctest Category of abelian varieties over Rational Field """ - return "abelian varieties over %s" % self.base() + if isinstance(self.base_scheme(), AffineScheme): + return "abelian varieties over %s" % self.base_scheme().coordinate_ring() + else: + return "abelian varieties over %s" % self.base_scheme() + + class ParentMethods: + def zero(self): + """ + Return the additive identity of this abelian variety. + + EXAMPLES:: + + sage: E = EllipticCurve([1, 3]) + sage: E.zero() + (0 : 1 : 0) + sage: E.zero() == E(0) + True + """ + try: + return self(0) + except Exception: + try: + return self.point_homset()(0) + except Exception: + raise NotImplementedError class Homsets(HomsetsCategory): r""" @@ -331,7 +375,6 @@ def __init__(self, base): sage: Jacobians(Spec(QQ)) Category of Jacobians over Rational Field """ - from sage.schemes.generic.scheme import AffineScheme if isinstance(base, AffineScheme): base = base.coordinate_ring() if base not in Fields(): diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 471db14112b..147aff05570 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -4416,10 +4416,9 @@ def _Hom_(self, Y, category): sage: type(H) sage: H - Set of Morphisms from Vector space of dimension 2 over Rational Field - to Ambient free module of rank 3 over the principal ideal domain Integer Ring - in Category of finite dimensional vector spaces with basis over - (number fields and quotient fields and metric spaces) + Set of Morphisms from Vector space of dimension 2 over Rational + Field to Ambient free module of rank 3 over the principal ideal + domain Integer Ring in Category of commutative additive groups """ if Y.base_ring().is_field(): from sage.modules import vector_space_homspace diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index bfecebf1f86..f75934a0290 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -122,6 +122,12 @@ class EllipticCurve_generic(WithEqualityById, plane_curve.ProjectivePlaneCurve): sage: P = E([-1,1,1]) sage: -5*P (179051/80089 : -91814227/22665187 : 1) + + :: + + sage: for R in [Zmod(10), QQ, NumberField(x^3 + 5, "a"), Qp(3), GF(2), GF(3), GF((7, 3))]: + ....: params = [3, 5] if R.characteristic() not in [2, 3] else [1, 0, 1, 3, 5] + ....: TestSuite(EllipticCurve(R, params)).run(skip="_test_not_implemented_methods") """ def __init__(self, K, ainvs, category=None): r""" @@ -601,8 +607,8 @@ def __call__(self, *args, **kwds): [(0 : 1 : 0)] """ if len(args) == 1 and args[0] == 0: - R = self.base_ring() - return self.point([R(0),R(1),R(0)], check=False) + R = self.__base_ring + return self.point([R.zero(), R.one(), R.zero()], check=False) P = args[0] if isinstance(P, groups.AdditiveAbelianGroupElement) and isinstance(P.parent(),ell_torsion.EllipticCurveTorsionSubgroup): return self(P.element()) @@ -620,6 +626,20 @@ def __call__(self, *args, **kwds): return plane_curve.ProjectivePlaneCurve.__call__(self, *args, **kwds) + def zero(self): + """ + Return the additive identity of this abelian variety. + + EXAMPLES:: + + sage: E = EllipticCurve(Zmod(10), [1, 3]) + sage: E.zero() + (0 : 1 : 0) + sage: E.zero() == E(0) + True + """ + return self(0) + def _reduce_point(self, R, p): r""" Reduces a point R on an elliptic curve to the corresponding point on @@ -3668,7 +3688,7 @@ def pari_curve(self): sage: e.type() 't_VEC' sage: e.disc() - 37.0000000000000 + 37.00000000000000000 Over a finite field:: @@ -3695,16 +3715,13 @@ def pari_curve(self): sage: K. = QuadraticField(2) # needs sage.libs.pari sage.rings.number_field sage: E = EllipticCurve([1,a]) # needs sage.libs.pari sage.rings.number_field sage: E.pari_curve() # needs sage.libs.pari sage.rings.number_field - [0, 0, 0, Mod(1, y^2 - 2), - Mod(y, y^2 - 2), 0, Mod(2, y^2 - 2), Mod(4*y, y^2 - 2), - Mod(-1, y^2 - 2), Mod(-48, y^2 - 2), Mod(-864*y, y^2 - 2), - Mod(-928, y^2 - 2), Mod(3456/29, y^2 - 2), - Vecsmall([5]), - [[y^2 - 2, [2, 0], 8, 1, [[1, -1.41421356237310; 1, 1.41421356237310], - [1, -1.41421356237310; 1, 1.41421356237310], - [16, -23; 16, 23], [2, 0; 0, 4], [4, 0; 0, 2], [2, 0; 0, 1], - [2, [0, 2; 1, 0]], [2]], [-1.41421356237310, 1.41421356237310], - [1, y], [1, 0; 0, 1], [1, 0, 0, 2; 0, 1, 1, 0]]], [0, 0, 0, 0, 0]] + [0, 0, 0, Mod(1, y^2 - 2), Mod(y, y^2 - 2), 0, Mod(2, y^2 - 2), Mod(4*y, y^2 - 2), + Mod(-1, y^2 - 2), Mod(-48, y^2 - 2), Mod(-864*y, y^2 - 2), Mod(-928, y^2 - 2), + Mod(3456/29, y^2 - 2), Vecsmall([5]), [[y^2 - 2, [2, 0], 8, 1, [[1, + -1.414213562373095049; 1, 1.414213562373095049], [1, -1.414213562373095049; 1, + 1.414213562373095049], [16, -23; 16, 23], [2, 0; 0, 4], [4, 0; 0, 2], [2, 0; 0, 1], [2, + [0, 2; 1, 0]], [2]], [-1.414213562373095049, 1.414213562373095049], [1, y], [1, 0; 0, + 1], [1, 0, 0, 2; 0, 1, 1, 0]]], [0, 0, 0, 0, 0]] PARI no longer requires that the `j`-invariant has negative `p`-adic valuation:: diff --git a/src/sage/schemes/generic/homset.py b/src/sage/schemes/generic/homset.py index b6c59d7090a..5f16b90a185 100644 --- a/src/sage/schemes/generic/homset.py +++ b/src/sage/schemes/generic/homset.py @@ -436,6 +436,10 @@ def __init__(self, X, Y, category=None, check=True, base=ZZ): """ Python constructor. + This class should not be constructed directly. Instead, use the + :func:`sage.schemes.generic.homset.SchemeHomset` function or the + ``point_homset`` method, which initializes the correct category etc. + INPUT: See :class:`SchemeHomset_generic`. @@ -443,11 +447,19 @@ def __init__(self, X, Y, category=None, check=True, base=ZZ): EXAMPLES:: sage: from sage.schemes.generic.homset import SchemeHomset_points - sage: SchemeHomset_points(Spec(QQ), AffineSpace(ZZ,2)) + sage: SchemeHomset_points(Spec(QQ), AffineSpace(ZZ, 2)) Set of rational points of Affine Space of dimension 2 over Rational Field + + This constructor might not initialize the correct category:: + + sage: E = EllipticCurve(QQ, [3, 6]) + sage: E.point_homset(QQ) + Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 3*x + 6 over Rational Field + sage: SchemeHomset_points(Spec(QQ), E) + Set of rational points of Elliptic Curve defined by y^2 = x^3 + 3*x + 6 over Rational Field """ if check and not isinstance(X, AffineScheme): - raise ValueError('The domain must be an affine scheme.') + raise ValueError('The domain must be an affine scheme') SchemeHomset_generic.__init__(self, X, Y, category=category, check=check, base=base) def __reduce__(self): diff --git a/src/sage/schemes/generic/scheme.py b/src/sage/schemes/generic/scheme.py index 5cae30dae3a..af92c077b97 100644 --- a/src/sage/schemes/generic/scheme.py +++ b/src/sage/schemes/generic/scheme.py @@ -290,6 +290,17 @@ def point_homset(self, S=None): Set of rational points of Projective Space of dimension 3 over Finite Field of size 11 + The set of S-valued points of an abelian variety has an abelian group structure:: + + sage: E = EllipticCurve(QQ, [3, 6]) + sage: E in AbelianVarieties(QQ) + True + sage: E.point_homset() in CommutativeAdditiveGroups() + True + sage: K. = NumberField(x^3 + 2) + sage: E.point_homset(K) in CommutativeAdditiveGroups() + True + TESTS:: sage: P = ProjectiveSpace(QQ, 3) @@ -302,6 +313,7 @@ def point_homset(self, S=None): if S is None: S = self.base_ring() SpecS = AffineScheme(S, self.base_ring()) + from sage.schemes.generic.homset import SchemeHomset return SchemeHomset(SpecS, self, as_point_homset=True) diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves/jacobian_generic.py index c83c1a0997c..83ecb9f4df3 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_generic.py @@ -29,6 +29,8 @@ class HyperellipticJacobian_generic(Jacobian_generic): sage: f = x**5 + 1184*x**3 + 1846*x**2 + 956*x + 560 sage: C = HyperellipticCurve(f) sage: J = C.jacobian() + sage: J in AbelianVarieties(FF) + True sage: a = x**2 + 376*x + 245; b = 1015*x + 1368 sage: X = J(FF) sage: D = X([a,b]) @@ -163,7 +165,7 @@ def dimension(self): def point(self, mumford, check=True): try: - return self(self.base_ring())(mumford) + return self.point_homset()(mumford) except AttributeError: raise ValueError("Arguments must determine a valid Mumford divisor.") diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py b/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py index 0fdea5814f0..ef46145d0ee 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py @@ -128,10 +128,8 @@ def __call__(self, P): return JacobianMorphism_divisor_class_field(self, (R.one(), R.zero())) elif isinstance(P, (list, tuple)): - if len(P) == 1 and P[0] == 0: - R = self.curve().hyperelliptic_polynomials()[0].parent().change_ring(self.value_ring()) - return JacobianMorphism_divisor_class_field(self, - (R.one(), R.zero())) + if len(P) == 1: + return self(P[0]) elif len(P) == 2: P1 = P[0] P2 = P[1] @@ -162,6 +160,22 @@ def __call__(self, P): return self((x - x0, R(y0))) raise TypeError("argument P (= %s) does not determine a divisor class" % P) + def zero(self): + r""" + Return the additive identity of this elliptic curve. + + EXAMPLES:: + + sage: H = HyperellipticCurve(polygen(GF(103))^5 + 1) + sage: J = H.jacobian() + sage: S = J.point_homset() + sage: S.zero() + (1) + sage: S.zero() == S(0) + True + """ + return self(0) + def _morphism(self, *args, **kwds): return JacobianMorphism_divisor_class_field(*args, **kwds) diff --git a/src/sage/schemes/jacobians/abstract_jacobian.py b/src/sage/schemes/jacobians/abstract_jacobian.py index 6ec2880a627..f8b86e028d9 100644 --- a/src/sage/schemes/jacobians/abstract_jacobian.py +++ b/src/sage/schemes/jacobians/abstract_jacobian.py @@ -20,6 +20,8 @@ from sage.categories.schemes import Jacobians from sage.categories.fields import Fields +from sage.categories.schemes import AbelianVarieties +_Fields = Fields() from sage.schemes.generic.scheme import Scheme, is_Scheme from sage.structure.richcmp import richcmp_method, richcmp @@ -100,15 +102,13 @@ def __init__(self, C, category=None): Note: this is an abstract parent, so we skip element tests:: - sage: TestSuite(J).run(skip =["_test_an_element", \ - "_test_zero", \ - "_test_elements", \ - "_test_elements_eq_reflexive", \ - "_test_elements_eq_symmetric", \ - "_test_elements_eq_transitive", \ - "_test_additive_associativity", \ - "_test_elements_neq", \ - "_test_some_elements"]) + sage: TestSuite(J).run(skip=["_test_an_element",\ + "_test_elements",\ + "_test_elements_eq_reflexive",\ + "_test_elements_eq_symmetric",\ + "_test_elements_eq_transitive",\ + "_test_elements_neq",\ + "_test_some_elements"]) :: @@ -139,7 +139,10 @@ def __init__(self, C, category=None): if C.dimension() != 1: raise ValueError("C (=%s) must have dimension 1." % C) self.__curve = C - Scheme.__init__(self, C.base_scheme(), category=Jacobians(C.base_ring()).or_subcategory(category)) + # I am not sure how to deal with non-smooth curves... + # TODO: Isolate the smooth case into a Jacobian_AbelianVariety or something + category = Jacobians(C.base_scheme()).or_subcategory(category) if C.is_smooth() else None + Scheme.__init__(self, C.base_scheme(), category=category) def __richcmp__(self, J, op): """ diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index f8faa8a7737..8b6e946fd0c 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -714,7 +714,9 @@ cdef class Element(SageObject): SageObject._test_category(self, tester=tester) category = self.category() # Tests that self inherits methods from the categories - if can_assign_class(self): + # SchemeMorphisms are skipped, see #37768. + from sage.schemes.generic.morphism import SchemeMorphism + if can_assign_class(self) and not isinstance(self, SchemeMorphism): # For usual Python classes, that should be done with # standard inheritance tester.assertTrue(isinstance(self, self.parent().category().element_class))