From e1d2ba46574273ec4d358e654ed195a82b81a654 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 21 Jun 2016 11:47:39 +0200 Subject: [PATCH 01/10] Move arithmetic using coercion model to Element --- src/sage/categories/additive_magmas.py | 32 +- src/sage/categories/associative_algebras.py | 8 - src/sage/categories/coercion_methods.pyx | 271 +---- src/sage/categories/magmas.py | 7 - src/sage/categories/modules.py | 5 +- src/sage/categories/unital_algebras.py | 4 - src/sage/combinat/posets/posets.py | 2 +- src/sage/combinat/sf/new_kschur.py | 3 - .../hyperplane_arrangement/hyperplane.py | 5 +- src/sage/groups/matrix_gps/group_element.pyx | 10 +- src/sage/interfaces/interface.py | 7 +- src/sage/interfaces/lisp.py | 4 +- src/sage/rings/infinity.py | 2 +- .../rings/semirings/tropical_semiring.pyx | 6 +- src/sage/structure/coerce.pyx | 4 +- src/sage/structure/element.pxd | 40 +- src/sage/structure/element.pyx | 1016 +++++++++-------- 17 files changed, 614 insertions(+), 812 deletions(-) diff --git a/src/sage/categories/additive_magmas.py b/src/sage/categories/additive_magmas.py index 0d0dee48dfa..81570898ee1 100644 --- a/src/sage/categories/additive_magmas.py +++ b/src/sage/categories/additive_magmas.py @@ -17,9 +17,7 @@ from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.homsets import HomsetsCategory from sage.categories.with_realizations import WithRealizationsCategory -import sage.categories.coercion_methods from sage.categories.sets_cat import Sets -from sage.structure.element import have_same_parent class AdditiveMagmas(Category_singleton): """ @@ -390,9 +388,6 @@ def addition_table(self, names='letters', elements=None): class ElementMethods: - __add__ = sage.categories.coercion_methods.__add__ - __radd__ = sage.categories.coercion_methods.__radd__ - @abstract_method(optional = True) def _add_(self, right): """ @@ -805,27 +800,6 @@ def _test_nonzero_equal(self, **options): tester.assertEqual(bool(self), self != self.parent().zero()) tester.assertEqual(not self, self == self.parent().zero()) - def __sub__(left, right): - """ - Return the difference between ``left`` and ``right``, if it exists. - - This top-level implementation delegates the work to - the ``_sub_`` method or to coercion. See the extensive - documentation at the top of :ref:`sage.structure.element`. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b']) - sage: a,b = F.basis() - sage: a - b - B['a'] - B['b'] - """ - if have_same_parent(left, right): - return left._sub_(right) - from sage.structure.element import get_coercion_model - import operator - return get_coercion_model().bin_op(left, right, operator.sub) - def _sub_(left, right): r""" Default implementation of difference. @@ -865,10 +839,6 @@ def __neg__(self): TESTS:: - sage: b.__neg__.__module__ - 'sage.categories.additive_magmas' - sage: b._neg_.__module__ - 'sage.combinat.free_module' sage: F = CombinatorialFreeModule(ZZ, ['a','b']) sage: a,b = F.gens() sage: FF = cartesian_product((F,F)) @@ -940,7 +910,7 @@ def extra_super_categories(self): return [AdditiveMagmas().AdditiveUnital().AdditiveInverse()] class ElementMethods: - def __neg__(self): + def _neg_(self): """ Return the negation of ``self``. diff --git a/src/sage/categories/associative_algebras.py b/src/sage/categories/associative_algebras.py index c94c09b5dce..aee68f2e6e7 100644 --- a/src/sage/categories/associative_algebras.py +++ b/src/sage/categories/associative_algebras.py @@ -47,12 +47,6 @@ class ElementMethods: """ An abstract class for elements of an associative algebra. - .. NOTE:: - - ``Magmas.Element.__mul__`` is preferable to - ``Modules.Element.__mul__`` since the later does not - handle products of two elements of ``self``. - TESTS:: sage: A = AlgebrasWithBasis(QQ).example(); A @@ -67,7 +61,5 @@ class ElementMethods: + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] """ - __mul__ = Magmas.ElementMethods.__mul__.__func__ - Unital = LazyImport('sage.categories.algebras', 'Algebras', at_startup=True) diff --git a/src/sage/categories/coercion_methods.pyx b/src/sage/categories/coercion_methods.pyx index 8552f21a8cb..ca63524dff2 100644 --- a/src/sage/categories/coercion_methods.pyx +++ b/src/sage/categories/coercion_methods.pyx @@ -1,210 +1,18 @@ """ Coercion methods for categories -Recall that the Sage categories provide implementations for Python's -special methods for the basic arithmetic operations (e.g. ``__mul__``, -``__add__``), that dispatch to the coercion model or call Sage's special -methods (e.g. ``_mul_``, ``_add_``) for the internal operations. - -To reduce the induced overhead, we want those methods to be -Cythonized, while for most of the non speed-critical methods -plain Python is more practical to reduce compilation time and -simplify interactive debugging and introspection. - -The purpose of this Cython module is to hold all the coercion methods, +The purpose of this Cython module is to hold special coercion methods, which are inserted by their respective categories. """ -from sage.structure.element cimport Element, have_same_parent_c, coercion_model, parent_c -import operator -cimport cython - -# This could eventually be moved to SageObject -@cython.binding -def __add__(Element self, right): - r""" - Return the sum of ``self`` and ``right``. - - This calls the ``_add_`` method of ``self``, if it is - available and the two elements have the same parent. - - Otherwise, the job is delegated to the coercion model. - - Do not override; instead implement an ``_add_`` method in the - element class or a ``summation`` method in the parent class. - - .. SEEALSO:: :meth:`AdditiveMagmas.ElementMethods._add_` - - EXAMPLES:: - - sage: F = CommutativeAdditiveSemigroups().example() - sage: (a,b,c,d) = F.additive_semigroup_generators() - sage: a + b - a + b - sage: a.__add__(b) - a + b - - This is :meth:`AdditiveMagmas.ElementMethods.__add__`, implemented as a - Cython method in :mod:`sage.categories.coercion_methods`:: - - sage: a.__add__.im_func is AdditiveMagmas.ElementMethods.__add__.im_func - True - sage: a.__add__.im_func is sage.categories.coercion_methods.__add__ - True - """ - if have_same_parent_c(self, right) and hasattr(self, "_add_"): - return self._add_(right) - return coercion_model.bin_op(self, right, operator.add) - -@cython.binding -def __radd__(Element self, left): - r""" - Handles the sum of two elements, when the left hand side - needs to be coerced first. - - EXAMPLES:: - - sage: F = CommutativeAdditiveSemigroups().example() - sage: (a,b,c,d) = F.additive_semigroup_generators() - sage: a.__radd__(b) - a + b - - This is :meth:`AdditiveMagmas.ElementMethods.__radd__`, implemented - as a Cython method in :mod:`sage.categories.coercion_methods`:: - - sage: a.__radd__.im_func is AdditiveMagmas.ElementMethods.__radd__.im_func - True - sage: a.__radd__.im_func is sage.categories.coercion_methods.__radd__ - True - """ - if have_same_parent_c(left, self) and hasattr(left, "_add_"): - return left._add_(self) - return coercion_model.bin_op(left, self, operator.add) - - -@cython.binding -def Modules__mul__(Element left, right): - """ - Return the product of ``left`` and ``right``. - - INPUT: - - - ``left`` -- an element of a :class:`module ` - - ``right`` -- any object - - EXAMPLES: - - This is used when multiplying an element of a module on the right - by something, typically a coefficient:: - - sage: F = CombinatorialFreeModule(QQ, ["a", "b"]) - sage: x = F.monomial("a") - sage: x * int(2) - 2*B['a'] - - .. SEEALSO:: :meth:`Modules.ElementMethods.__rmul__` - - This is :meth:`Modules.ElementMethods.__rmul__`, implemented as a - Cython method in :mod:`sage.categories.magmas_cython`:: - - sage: x.__mul__.im_func is Modules.ElementMethods.__mul__.im_func - True - sage: x.__mul__.im_func is sage.categories.coercion_methods.Modules__mul__ - True - - .. TODO:: - - Make a better unit test once ``Modules().example()`` is implemented. - """ - return coercion_model.bin_op(left, right, operator.mul) - -@cython.binding -def Modules__rmul__(Element right, left): - """ - Return the product of ``left`` and ``right``. - - INPUT: - - - ``right`` -- an element of a :class:`module ` - - ``left`` -- any object - - EXAMPLES: - - This is used when multiplying an element of a module on the left - by something, typically a coefficient:: - - sage: F = CombinatorialFreeModule(QQ, ["a", "b"]) - sage: x = F.monomial("a") - sage: int(2) * x - 2*B['a'] - sage: x.__rmul__(int(2)) - 2*B['a'] +from __future__ import absolute_import, division, print_function - .. SEEALSO:: :meth:`Modules.ElementMethods.__mul__` - - This is :meth:`Modules.ElementMethods.__rmul__`, implemented as a Cython - method in :mod:`sage.categories.coercion_methods`:: - - sage: x.__rmul__.im_func is Modules.ElementMethods.__rmul__.im_func - True - sage: x.__rmul__.im_func is sage.categories.coercion_methods.Modules__rmul__ - True - - .. TODO:: - - Make a better unit test once ``Modules().example()`` is implemented. - """ - return coercion_model.bin_op(left, right, operator.mul) - -@cython.binding -def __mul__(Element self, right): - r""" - Return the product of ``self`` and ``right``. - - INPUT: - - - ``self`` -- an element of a :class:`magma ` - - ``right`` -- an object - - This calls the ``_mul_`` method of ``self``, if it is - available and the two elements have the same parent - (see :meth:`Magmas.ElementMethods._mul_`). - - Otherwise, the job is delegated to the coercion model. - - Do not override; instead implement a ``_mul_`` method in the - element class or a ``product`` method in the parent class. - - EXAMPLES:: - - sage: S = Semigroups().example("free") - sage: x = S('a'); y = S('b') - sage: x * y - 'ab' - - .. SEEALSO:: - - - :meth:`Magmas.ElementMethods._mul_` - - :meth:`Magmas.ElementMethods._mul_parent` - - :meth:`Magmas.ParentMethods.product` +from sage.structure.element cimport Element +cimport cython - This is :meth:`Magmas.ElementMethods.__mul__`, implemented as a - Cython method in :mod:`sage.categories.coercion_methods`:: - - sage: x.__mul__.im_func is Magmas.ElementMethods.__mul__.im_func - True - sage: x.__mul__.im_func is sage.categories.coercion_methods.__mul__ - True - """ - if have_same_parent_c(self, right): - try: - return self._mul_(right) - except AttributeError: - pass - return coercion_model.bin_op(self, right, operator.mul) @cython.binding -def _mul_parent(Element self, other): +def _mul_parent(self, other): r""" Return the product of the two elements, calculated using the ``product`` method of the parent. @@ -241,71 +49,4 @@ def _mul_parent(Element self, other): sage: x._mul_parent.im_func is sage.categories.coercion_methods._mul_parent True """ - return parent_c(self).product(self, other) - - -@cython.binding -def __truediv__(left, right): - """ - Return the result of the division of ``left`` by ``right``, if possible. - - This top-level implementation delegates the work to - the ``_div_`` method if ``left`` and ``right`` have - the same parent and to coercion otherwise. See the - extensive documentation at the top of - :ref:`sage.structure.element`. - - INPUT: - - - ``self`` -- an element of a :class:`unital magma ` - - ``right`` -- an object - - .. SEEALSO:: :meth:`Magmas.Unital.ElementMethods._div_` - - EXAMPLES:: - - sage: G = FreeGroup(2) - sage: x0, x1 = G.group_generators() - sage: c1 = cartesian_product([x0, x1]) - sage: c2 = cartesian_product([x1, x0]) - sage: c1.__div__(c2) - (x0*x1^-1, x1*x0^-1) - sage: c1 / c2 - (x0*x1^-1, x1*x0^-1) - - Division supports coercion:: - - sage: C = cartesian_product([G, G]) - sage: H = Hom(G, C) - sage: phi = H(lambda g: cartesian_product([g, g])) - sage: phi.register_as_coercion() - sage: x1 / c1 - (x1*x0^-1, 1) - sage: c1 / x1 - (x0*x1^-1, 1) - - Depending on how the division itself is implemented in - ``_div_``, division may fail even when ``right`` - actually divides ``left``:: - - sage: x = cartesian_product([2, 1]) - sage: y = cartesian_product([1, 1]) - sage: x / y - (2, 1) - sage: x / x - Traceback (most recent call last): - ... - TypeError: no conversion of this rational to integer - - This is :meth:`Magmas.Unital.ElementMethods.__truediv__`, implemented - as a Cython method in :mod:`sage.categories.coercion_methods`:: - - sage: x.__truediv__.im_func is Magmas.Unital.ElementMethods.__truediv__.im_func - True - sage: x.__truediv__.im_func is sage.categories.coercion_methods.__truediv__ - True - """ - if have_same_parent_c(left, right): - return left._div_(right) - return coercion_model.bin_op(left, right, operator.div) - + return (self)._parent.product(self, other) diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 5064569273e..00c808c49ac 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -527,10 +527,6 @@ def is_empty(self): return False class ElementMethods: - - __truediv__ = sage.categories.coercion_methods.__truediv__ - __div__ = __truediv__ # For Python2/3 compatibility; see e.g. #18578 - def _div_(left, right): r""" Default implementation of division, multiplying (on the right) by the inverse. @@ -977,9 +973,6 @@ def multiplication_table(self, names='letters', elements=None): return OperationTable(self, operation=operator.mul, names=names, elements=elements) class ElementMethods: - - __mul__ = sage.categories.coercion_methods.__mul__ - @abstract_method(optional = True) def _mul_(self, right): """ diff --git a/src/sage/categories/modules.py b/src/sage/categories/modules.py index 08b04a328b2..7478b0b6407 100644 --- a/src/sage/categories/modules.py +++ b/src/sage/categories/modules.py @@ -18,7 +18,6 @@ from sage.categories.homsets import HomsetsCategory from .category import Category, JoinCategory from .category_types import Category_module, Category_over_base_ring -import sage.categories.coercion_methods from sage.categories.tensor import TensorProductsCategory, tensor from .dual import DualObjectsCategory from sage.categories.cartesian_product import CartesianProductsCategory @@ -529,9 +528,7 @@ def tensor_square(self): return tensor([self, self]) class ElementMethods: - - __mul__ = sage.categories.coercion_methods.Modules__mul__ - __rmul__ = sage.categories.coercion_methods.Modules__rmul__ + pass class Homsets(HomsetsCategory): r""" diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index 6b836b3ae42..a77f34b08a8 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -152,9 +152,6 @@ def __init_extra__(self): class ElementMethods: """ - ``Magmas.Element.__mul__`` is preferable to ``Modules.Element.__mul__`` - since the later does not handle products of two elements of ``self``. - TESTS:: sage: A = AlgebrasWithBasis(QQ).example(); A @@ -169,7 +166,6 @@ class ElementMethods: + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] """ - __mul__ = Magmas.ElementMethods.__mul__.__func__ class WithBasis(CategoryWithAxiom_over_base_ring): diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index d0172ddac20..098c56606ce 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -492,7 +492,7 @@ def Poset(data=None, element_labels=None, cover_relations=False, linear_extensio sage: a + b Traceback (most recent call last): ... - TypeError: unsupported operand type(s) for +: 'FinitePoset_with_category.element_class' and 'FinitePoset_with_category.element_class' + TypeError: unsupported operand parent(s) for '+': 'Finite poset containing 4 elements' and 'Finite poset containing 4 elements' sage: a.element + b.element 'ab' diff --git a/src/sage/combinat/sf/new_kschur.py b/src/sage/combinat/sf/new_kschur.py index b83771ce27f..e4b586aaf09 100644 --- a/src/sage/combinat/sf/new_kschur.py +++ b/src/sage/combinat/sf/new_kschur.py @@ -583,9 +583,6 @@ def counit(self, element): return element.coefficient([]) class ElementMethods: - - __mul__ = Magmas.ElementMethods.__mul__.__func__ - def _mul_(self, other): r""" Return the product of two elements ``self`` and ``other``. diff --git a/src/sage/geometry/hyperplane_arrangement/hyperplane.py b/src/sage/geometry/hyperplane_arrangement/hyperplane.py index d44efb76f21..22e5d184c02 100644 --- a/src/sage/geometry/hyperplane_arrangement/hyperplane.py +++ b/src/sage/geometry/hyperplane_arrangement/hyperplane.py @@ -90,9 +90,8 @@ Arrangement sage: arrangement + x Traceback (most recent call last): - TypeError: unsupported operand type(s) for +: - 'HyperplaneArrangements_with_category.element_class' and - 'HyperplaneArrangements_with_category.element_class' + ... + TypeError: unsupported operand parent(s) for '+': 'Hyperplane arrangements in 3-dimensional linear space over Rational Field with coordinates x, y, z' and 'Hyperplane arrangements in 3-dimensional linear space over Rational Field with coordinates x, y, z' """ #***************************************************************************** diff --git a/src/sage/groups/matrix_gps/group_element.pyx b/src/sage/groups/matrix_gps/group_element.pyx index d48d07de336..21537b13530 100644 --- a/src/sage/groups/matrix_gps/group_element.pyx +++ b/src/sage/groups/matrix_gps/group_element.pyx @@ -23,9 +23,13 @@ there:: sage: g + h Traceback (most recent call last): ... - TypeError: unsupported operand type(s) for +: - 'sage.groups.matrix_gps.group_element.MatrixGroupElement_gap' and - 'sage.groups.matrix_gps.group_element.MatrixGroupElement_gap' + TypeError: unsupported operand parent(s) for '+': 'Matrix group over Finite Field of size 3 with 2 generators ( + [1 0] [1 1] + [0 1], [0 1] + )' and 'Matrix group over Finite Field of size 3 with 2 generators ( + [1 0] [1 1] + [0 1], [0 1] + )' sage: g.matrix() + h.matrix() [2 0] diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 7c860137d31..40cbf5087ac 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -44,7 +44,7 @@ from sage.structure.sage_object import SageObject from sage.structure.parent_base import ParentWithBase -from sage.structure.element import RingElement, parent +from sage.structure.element import Element, parent import sage.misc.sage_eval @@ -650,12 +650,13 @@ def _sage_doc_(self): def is_InterfaceElement(x): return isinstance(x, InterfaceElement) -class InterfaceElement(RingElement): + +class InterfaceElement(Element): """ Interface element. """ def __init__(self, parent, value, is_name=False, name=None): - RingElement.__init__(self, parent) + Element.__init__(self, parent) self._create = value if parent is None: return # means "invalid element" # idea: Joe Wetherell -- try to find out if the output diff --git a/src/sage/interfaces/lisp.py b/src/sage/interfaces/lisp.py index c40efacccd8..e6ef1e5738b 100644 --- a/src/sage/interfaces/lisp.py +++ b/src/sage/interfaces/lisp.py @@ -381,7 +381,9 @@ def function_call(self, function, args=None, kwds=None): self._check_valid_function_name(function) return self.new("(%s %s)"%(function, ",".join([s.name() for s in args]))) -class LispElement(ExpectElement): + +# Inherit from RingElement to make __pow__ work +class LispElement(RingElement, ExpectElement): def __cmp__(self, other): """ EXAMPLES:: diff --git a/src/sage/rings/infinity.py b/src/sage/rings/infinity.py index 3b043a575d6..f2b9b0f38b9 100644 --- a/src/sage/rings/infinity.py +++ b/src/sage/rings/infinity.py @@ -93,7 +93,7 @@ sage: unsigned_oo/0 Traceback (most recent call last): ... - ValueError: unsigned oo times smaller number not defined + ValueError: quotient of number < oo by number < oo not defined What happened above is that 0 is canonically coerced to "a number less than infinity" in the unsigned infinity ring, and the quotient diff --git a/src/sage/rings/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index c983b3e6df1..f03e2786749 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -24,7 +24,7 @@ AUTHORS: from sage.misc.cachefunc import cached_method from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.element cimport RingElement, Element, ModuleElement +from sage.structure.element cimport Element, ModuleElement from sage.categories.semirings import Semirings from sage.categories.map cimport Map from sage.sets.family import Family @@ -32,7 +32,7 @@ from sage.rings.all import ZZ import operator -cdef class TropicalSemiringElement(RingElement): +cdef class TropicalSemiringElement(Element): r""" An element in the tropical semiring over an ordered additive semigroup `R`. Either in `R` or `\infty`. The operators `+, \cdot` are defined as @@ -60,7 +60,7 @@ cdef class TropicalSemiringElement(RingElement): sage: elt = T(2) sage: TestSuite(elt).run() """ - RingElement.__init__(self, parent) + Element.__init__(self, parent) self._val = val def __reduce__(self): diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx index df883e2a5f1..5e692ab265e 100644 --- a/src/sage/structure/coerce.pyx +++ b/src/sage/structure/coerce.pyx @@ -87,7 +87,7 @@ from operator import add, sub, mul, div, truediv, iadd, isub, imul, idiv from .sage_object cimport SageObject, rich_to_bool from .parent cimport Set_PythonType, Parent_richcmp_element_without_coercion -from .element cimport arith_error_message, parent_c, Element +from .element cimport bin_op_error_message, parent_c, Element from .coerce_actions import LeftModuleAction, RightModuleAction, IntegerMulAction from .coerce_exceptions import CoercionException from sage.categories.map cimport Map @@ -1087,7 +1087,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): # We should really include the underlying error. # This causes so much headache. - raise TypeError(arith_error_message(x,y,op)) + raise TypeError(bin_op_error_message(op, x, y)) cpdef canonical_coercion(self, x, y): r""" diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index dcb37c2049f..0a9eec00ce9 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -162,14 +162,9 @@ cdef inline parent_c(x): return parent(x) -cdef inline bint have_same_parent_c(left, right): - """ - Deprecated alias for :func:`have_same_parent`. - """ - return have_same_parent(left, right) - +cdef un_op_error_message(op, x) +cdef bin_op_error_message(op, x, y) -cdef str arith_error_message(x, y, op) cdef class Element(SageObject): cdef Parent _parent @@ -182,7 +177,16 @@ cdef class Element(SageObject): cpdef _act_on_(self, x, bint self_on_left) cpdef _acted_upon_(self, x, bint self_on_left) - cpdef _mod_(self, right) + cdef _add_(self, other) + cdef _sub_(self, other) + cdef _neg_(self) + cdef _add_long(self, long n) + + cdef _mul_(self, other) + cdef _mul_long(self, long n) + cdef _div_(self, other) + cdef _floordiv_(self, other) + cdef _mod_(self, other) cdef class ElementWithCachedMethod(Element): @@ -193,18 +197,16 @@ cdef class ModuleElement(Element) # forward declaration cdef class RingElement(ModuleElement) # forward declaration cdef class ModuleElement(Element): - cpdef _add_(self, right) cpdef _sub_(self, right) cpdef _neg_(self) + # self._rmul_(x) is x * self cpdef _lmul_(self, RingElement right) - # self._lmul_(x) is self * x, to abide with Python conventions. + # self._lmul_(x) is self * x cpdef _rmul_(self, RingElement left) - cdef _mul_long(self, long n) - cdef class MonoidElement(Element): - cpdef _mul_(self, right) + pass cdef class MultiplicativeGroupElement(MonoidElement): cpdef _div_(self, right) @@ -213,11 +215,7 @@ cdef class AdditiveGroupElement(ModuleElement): pass cdef class RingElement(ModuleElement): - cpdef _mul_(self, right) cpdef _div_(self, right) - cpdef _floordiv_(self, right) - - cdef _add_long(self, long n) cdef class CommutativeRingElement(RingElement): pass @@ -232,10 +230,11 @@ cdef class PrincipalIdealDomainElement(DedekindDomainElement): pass cdef class EuclideanDomainElement(PrincipalIdealDomainElement): - pass + cpdef _floordiv_(self, right) + cpdef _mod_(self, right) cdef class FieldElement(CommutativeRingElement): - pass + cpdef _floordiv_(self, right) cdef class AlgebraElement(RingElement): pass @@ -243,9 +242,6 @@ cdef class AlgebraElement(RingElement): cdef class CommutativeAlgebraElement(CommutativeRingElement): pass -cdef class CommutativeAlgebra(AlgebraElement): - pass - cdef class InfinityElement(RingElement): pass diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 00d565ff0d0..0113153f8df 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -19,6 +19,9 @@ AUTHORS: - Maarten Derickx (2010-07): added architecture for is_square and sqrt +- Jeroen Demeyer (2016-08): moved all coercion to the base class + :class:`Element`, see :trac:`20767` + The Abstract Element Class Hierarchy ------------------------------------ @@ -71,59 +74,71 @@ followed by both arithmetic implementers and callers. A quick summary for the impatient: -- To implement addition for any Element class, override ``def _add_()``. -- If you want to add ``x`` and ``y``, whose parents you know are **identical**, - you may call ``_add_(x, y)``. This will be the fastest way to guarantee - that the correct implementation gets called. Of course you can still - always use ``x + y``. +- To implement addition for any Element class, override + ``def _add_(self, other)`` in Python or ``cpdef _add_(self, other)`` + in Cython. + +- If you want to add ``x`` and ``y``, whose parents you know are + **identical**, you may call ``x._add_(y)`` in Cython. This will be the + fastest way to guarantee that the correct implementation gets called. + Of course you can still always use ``x + y``. Now in more detail. The aims of this system are to provide (1) an efficient calling protocol from both Python and Cython, (2) uniform coercion semantics across Sage, (3) ease of use, (4) readability of code. -We will take addition of RingElements as an example; all other -operators and classes are similar. There are three relevant functions, -with subtly differing names (``add`` vs. ``iadd``, single vs. double -underscores). +We will take addition as an example; all other operators are similar. +There are two relevant functions, with differing names +(single vs. double underscores). -- **def RingElement.__add__** +- **def Element.__add__** - This function is called by Python or Cython when the binary "+" operator - is encountered. It **assumes** that at least one of its arguments is a - RingElement; only a really twisted programmer would violate this - condition. It has a fast pathway to deal with the most common case - where the arguments have the same parent. Otherwise, it uses the coercion - module to work out how to make them have the same parent. After any - necessary coercions have been performed, it calls ``_add_`` to dispatch to - the correct underlying addition implementation. + This function is called by Python or Cython when the binary "+" + operator is encountered. It **assumes** that at least one of its + arguments is an :class:`Element`. - Note that although this function is declared as ``def``, it doesn't have the - usual overheads associated with Python functions (either for the caller or - for ``__add__`` itself). This is because Python has optimised calling - protocols for such special functions. + It has a fast pathway to deal with the most common case where both + arguments have the same parent. Otherwise, it uses the coercion + framework to work out how to make them have the same parent. After + any necessary coercions have been performed, it calls ``_add_`` to + dispatch to the correct underlying addition implementation. -- **def RingElement._add_** + Note that although this function is declared as ``def``, it doesn't + have the usual overheads associated with Python functions (either + for the caller or for ``__add__`` itself). This is because Python + has optimised calling protocols for such special functions. + +- **def Element._add_** This is the function you should override to implement addition in a - subclass of ``RingElement``. + subclass of ``Element``. The two arguments to this function are guaranteed to have the **same parent**. Its return value **must** have the **same parent** as its arguments. - If you want to add two objects and you know that their parents are - the same object, you are encouraged to call this function directly, - instead of using ``x + y``. + In Cython code, if you want to add two objects and you know that + their parents are the same object, you are encouraged to call this + function directly, instead of using ``x + y``. This only works if + Cython knows that the left argument is an ``Element``. You can + always cast explicitly: ``(x)._add_(y)`` to force this. - When implementing ``_add_`` in a Cython extension class, use + When implementing ``_add_`` in a Cython extension type, use ``cpdef _add_`` instead of ``def _add_``. """ -################################################################## -# Generic element, so all this functionality must be defined -# by any element. Derived class must call __init__ -################################################################## -from __future__ import print_function +#***************************************************************************** +# Copyright (C) 2006-2016 ... +# Copyright (C) 2016 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from __future__ import absolute_import, division, print_function from libc.limits cimport LONG_MAX, LONG_MIN @@ -131,7 +146,6 @@ from cpython cimport * from sage.ext.stdsage cimport * from cpython.ref cimport PyObject -from cpython.number cimport PyNumber_TrueDivide import types cdef add, sub, mul, div, truediv, floordiv, mod @@ -166,13 +180,25 @@ def make_element(_class, _dict, parent): return make_element_old(_class, _dict, parent) -cdef str arith_error_message(x, y, op): - name = op.__name__ +cdef un_op_error_message(op, x): + try: + op = op.__name__ + op = _coerce_op_symbols[op] + except (AttributeError, KeyError): + pass + px = parent(x) + return f"unsupported operand parent for {op}: '{px}'" + + +cdef bin_op_error_message(op, x, y): try: - name = _coerce_op_symbols[name] - except KeyError: + op = op.__name__ + op = _coerce_op_symbols[op] + except (AttributeError, KeyError): pass - return "unsupported operand parent(s) for '%s': '%s' and '%s'"%(name, parent_c(x), parent_c(y)) + px = parent(x) + py = parent(y) + return f"unsupported operand parent(s) for {op!r}: '{px}' and '{py}'" def is_Element(x): @@ -944,54 +970,402 @@ cdef class Element(SageObject): msg = LazyFormat("comparison not implemented for %r")%type(left) raise NotImplementedError(msg) - def __mod__(self, other): + ################################################## + # Arithmetic using the coercion model + ################################################## + + def __add__(left, right): """ - Top-level modulo operator for :class:`Element`. + Top-level addition operator for :class:`Element` invoking + the coercion model. + See extensive documentation at the top of element.pyx. EXAMPLES:: - sage: 7 % 3 - 1 - sage: 7 % int(3) - 1 - sage: int(7) % 3 - 1 + sage: from sage.structure.element import RingElement + sage: e = RingElement(Parent()) + sage: e + e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': '' and '' + sage: 1 + e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': 'Integer Ring' and '' + sage: e + 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': '' and 'Integer Ring' + sage: int(1) + e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'int' and 'sage.structure.element.RingElement' + sage: e + int(1) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'sage.structure.element.RingElement' and 'int' + sage: None + e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'NoneType' and 'sage.structure.element.RingElement' + sage: e + None + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'sage.structure.element.RingElement' and 'NoneType' + """ + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._add_(right) + # Left and right are Sage elements => use coercion model + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, add) + + try: + # Special case addition with Python int + if isinstance(right, int): + return (left)._add_long(PyInt_AS_LONG(right)) + if isinstance(left, int): + return (right)._add_long(PyInt_AS_LONG(left)) + return coercion_model.bin_op(left, right, add) + except TypeError: + # Either coercion failed or arithmetic is not defined. + # + # According to the Python convention, we should return + # NotImplemented now. This will cause Python to try the + # reversed addition (__radd__). + return NotImplemented + + cdef _add_(self, other): + """ + Virtual addition method for elements with identical parents. + + This is meant to implement the fact that there is no ``_add_`` + method: it looks up a Python ``_add_`` method and calls that. + This indirectly also looks up ``_add_`` in the category. + If there is no Python ``_add_`` method, an exception is raised + saying that addition is not implemented. + + EXAMPLES:: + + sage: 23._add_(5) + 28 :: sage: from sage.structure.element import Element sage: e = Element(Parent()) - sage: e % e + sage: e._add_(e) Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for '%': '' and '' + AttributeError: 'sage.structure.element.Element' object has no attribute '_add_' + """ + try: + python_op = (self)._add_ + except AttributeError: + raise TypeError(bin_op_error_message('+', self, other)) + else: + return python_op(other) + + cdef _add_long(self, long n): """ - if have_same_parent_c(self, other): - return (self)._mod_(other) - return coercion_model.bin_op(self, other, mod) + Generic path for adding a C long, assumed to commute. + """ + return coercion_model.bin_op(self, n, add) - cpdef _mod_(self, other): + def __sub__(left, right): """ - Cython classes should override this function to implement - remaindering. + Top-level subtraction operator for :class:`Element` invoking + the coercion model. + See extensive documentation at the top of element.pyx. EXAMPLES:: - sage: 23._mod_(5) - 3 + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e - e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '-': '' and '' + """ + # See __add__ for comments + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._sub_(right) + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, sub) + + try: + return coercion_model.bin_op(left, right, sub) + except TypeError: + return NotImplemented + + cdef _sub_(self, other): + # See _add_ for documentation + try: + python_op = (self)._sub_ + except AttributeError: + raise TypeError(bin_op_error_message('-', self, other)) + else: + return python_op(other) + + def __neg__(self): + """ + Top-level negation operator for :class:`Element`. + + EXAMPLES:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: -e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent for unary -: '' + """ + return self._neg_() + + cdef _neg_(self): + # See _add_ for documentation + try: + python_op = (self)._neg_ + except AttributeError: + raise TypeError(un_op_error_message('unary -', self)) + else: + return python_op() + + def __mul__(left, right): + """ + Top-level multiplication operator for :class:`Element` invoking + the coercion model. + + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e * e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': '' and '' + """ + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._mul_(right) + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, mul) + + try: + if isinstance(right, int): + return (left)._mul_long(PyInt_AS_LONG(right)) + if isinstance(left, int): + return (right)._mul_long(PyInt_AS_LONG(left)) + return coercion_model.bin_op(left, right, mul) + except TypeError: + return NotImplemented + + cdef _mul_(self, other): + """ + Multiplication method for elements with identical parents. + + See extensive documentation at the top of element.pyx. + """ + # See _add_ for documentation + try: + python_op = (self)._mul_ + except AttributeError: + raise TypeError(bin_op_error_message('*', self, other)) + else: + return python_op(other) + + cdef _mul_long(self, long n): + """ + Generic path for multiplying by a C long, assumed to commute. + """ + return coercion_model.bin_op(self, n, mul) + + def __div__(left, right): + """ + Top-level division operator for :class:`Element` invoking + the coercion model. + + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: 2 / 3 + 2/3 + sage: pi / 3 + 1/3*pi + sage: K. = NumberField(x^2+1) + sage: 2 / K.ideal(i+1) + Fractional ideal (-i + 1) + + :: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e / e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '/': '' and '' + """ + # See __add__ for comments + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._div_(right) + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, div) + + try: + return coercion_model.bin_op(left, right, div) + except TypeError: + return NotImplemented + + def __truediv__(left, right): + """ + Top-level true division operator for :class:`Element` invoking + the coercion model. + + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: operator.truediv(2, 3) + 2/3 + sage: operator.truediv(pi, 3) + 1/3*pi + sage: K. = NumberField(x^2+1) + sage: operator.truediv(2, K.ideal(i+1)) + Fractional ideal (-i + 1) + + :: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: operator.truediv(e, e) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '/': '' and '' + """ + # See __add__ for comments + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._div_(right) + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, truediv) + + try: + return coercion_model.bin_op(left, right, truediv) + except TypeError: + return NotImplemented + + cdef _div_(self, other): + # See _add_ for documentation + try: + python_op = (self)._div_ + except AttributeError: + raise TypeError(bin_op_error_message('/', self, other)) + else: + return python_op(other) + + def __floordiv__(left, right): + """ + Top-level floor division operator for :class:`Element` invoking + the coercion model. + + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: 7 // 3 + 2 + sage: 7 // int(3) + 2 + sage: int(7) // 3 + 2 + + :: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e // e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '//': '' and '' + """ + # See __add__ for comments + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._floordiv_(right) + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, floordiv) + + try: + return coercion_model.bin_op(left, right, floordiv) + except TypeError: + return NotImplemented + + cdef _floordiv_(self, other): + # See _add_ for documentation + try: + python_op = (self)._floordiv_ + except AttributeError: + raise TypeError(bin_op_error_message('//', self, other)) + else: + return python_op(other) + + def __mod__(left, right): + """ + Top-level modulo operator for :class:`Element` invoking + the coercion model. + + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: 7 % 3 + 1 + sage: 7 % int(3) + 1 + sage: int(7) % 3 + 1 :: sage: from sage.structure.element import Element sage: e = Element(Parent()) - sage: e._mod_(e) + sage: e % e Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '%': '' and '' """ - raise TypeError(arith_error_message(self, other, mod)) + # See __add__ for comments + cdef int cl = classify_elements(left, right) + if HAVE_SAME_PARENT(cl): + return (left)._mod_(right) + if BOTH_ARE_ELEMENT(cl): + return coercion_model.bin_op(left, right, mod) + + try: + return coercion_model.bin_op(left, right, mod) + except TypeError: + return NotImplemented + + cdef _mod_(self, other): + """ + Modulo method for elements with identical parents. + + See extensive documentation at the top of element.pyx. + """ + # See _add_ for documentation + try: + python_op = (self)._mod_ + except AttributeError: + raise TypeError(bin_op_error_message('%', self, other)) + else: + return python_op(other) def is_ModuleElement(x): @@ -1220,77 +1594,35 @@ cdef class ModuleElement(Element): """ Generic element of a module. """ - - ################################################## - # Addition - ################################################## - def __add__(left, right): + cdef _add_long(self, long n): """ - Top-level addition operator for ModuleElements. - - See extensive documentation at the top of element.pyx. + Generic path for adding a C long, assumed to commute. """ - # Try fast pathway if they are both ModuleElements and the parents - # match. - - # (We know at least one of the arguments is a ModuleElement. So if - # their types are *equal* (fast to check) then they are both - # ModuleElements. Otherwise use the slower test via isinstance.) - if have_same_parent_c(left, right): - return (left)._add_(right) - return coercion_model.bin_op(left, right, add) - - cpdef _add_(left, right): - raise TypeError(arith_error_message(left, right, add)) - - ################################################## - # Subtraction - ################################################## + if n == 0: + return self + return coercion_model.bin_op(self, n, add) - def __sub__(left, right): + cpdef _sub_(self, other): """ - Top-level subtraction operator for ModuleElements. - See extensive documentation at the top of element.pyx. + Default implementation of subtraction using addition and + negation. """ - if have_same_parent_c(left, right): - return (left)._sub_(right) - return coercion_model.bin_op(left, right, sub) - - cpdef _sub_(left, right): - # default implementation is to use the negation and addition - # dispatchers: - return left._add_(-right) - - ################################################## - # Negation - ################################################## + return self + (-other) - def __neg__(self): + cpdef _neg_(self): """ - Top-level negation operator for ModuleElements, which - may choose to implement _neg_ rather than __neg__ for - consistency. + Default implementation of negation using multiplication + with -1. """ - return self._neg_() - - cpdef _neg_(self): - # default implementation is to try multiplying by -1. - if self._parent._base is None: - return coercion_model.bin_op(-1, self, mul) - else: - return coercion_model.bin_op(self._parent._base(-1), self, mul) + return self._mul_long(-1) - ################################################## - # Module element multiplication (scalars, etc.) - ################################################## - def __mul__(left, right): - if type(right) is int: - return (left)._mul_long(PyInt_AS_LONG(right)) - if type(left) is int: - return (right)._mul_long(PyInt_AS_LONG(left)) - if have_same_parent_c(left, right): - raise TypeError(arith_error_message(left, right, mul)) - return coercion_model.bin_op(left, right, mul) + cdef _mul_long(self, long n): + """ + Generic path for multiplying by a C long, assumed to commute. + """ + if n == 1: + return self + return coercion_model.bin_op(self, n, mul) # rmul -- left * self cpdef _rmul_(self, RingElement left): @@ -1312,12 +1644,6 @@ cdef class ModuleElement(Element): """ return None - cdef _mul_long(self, long n): - """ - Generic path for multiplying by a C long, assumed to commute. - """ - return coercion_model.bin_op(self, n, mul) - ################################################## # Other properties ################################################## @@ -1348,33 +1674,6 @@ cdef class MonoidElement(Element): Generic element of a monoid. """ - ############################################################# - # Multiplication - ############################################################# - def __mul__(left, right): - """ - Top-level multiplication operator for monoid elements. - See extensive documentation at the top of element.pyx. - """ - if have_same_parent_c(left, right): - return (left)._mul_(right) - try: - return coercion_model.bin_op(left, right, mul) - except TypeError as msg: - if isinstance(left, (int, long)) and left==1: - return right - elif isinstance(right, (int, long)) and right==1: - return left - raise - - - cpdef _mul_(left, right): - """ - Cython classes should override this function to implement multiplication. - See extensive documentation at the top of element.pyx. - """ - raise TypeError - ############################################################# # Other generic functions that should be available to # any monoid element. @@ -1460,42 +1759,10 @@ cdef class MultiplicativeGroupElement(MonoidElement): """ return self.multiplicative_order() - def _add_(self, x): - raise ArithmeticError("addition not defined in a multiplicative group") - - def __truediv__(left, right): - """ - Top-level true division operator for multiplicative group - elements. See extensive documentation at the top of - element.pyx. - - If two elements have the same parent, we just call ``_div_`` - because all divisions of Sage elements are really true - divisions. - - EXAMPLES:: - - sage: K. = NumberField(x^2+1) - sage: operator.truediv(2, K.ideal(i+1)) - Fractional ideal (-i + 1) - """ - if have_same_parent_c(left, right): - return (left)._div_(right) - return coercion_model.bin_op(left, right, truediv) - - def __div__(left, right): - """ - Top-level division operator for multiplicative group elements. - See extensive documentation at the top of element.pyx. - """ - if have_same_parent_c(left, right): - return (left)._div_(right) - return coercion_model.bin_op(left, right, div) - cpdef _div_(self, right): """ - Cython classes should override this function to implement division. - See extensive documentation at the top of element.pyx. + Default implementation of division using multiplication by + the inverse. """ return self * ~right @@ -1503,9 +1770,7 @@ cdef class MultiplicativeGroupElement(MonoidElement): r""" Return the inverse of ``self``. """ - if self.is_one(): - return self - return self.parent().one()/self + return self._parent.one() / self def is_RingElement(x): @@ -1515,181 +1780,9 @@ def is_RingElement(x): return isinstance(x, RingElement) cdef class RingElement(ModuleElement): - ################################################## def is_one(self): return self == self._parent.one() - ################################## - # Fast long add/sub path. - ################################## - - def __add__(left, right): - """ - Top-level addition operator for RingElements. - - See extensive documentation at the top of element.pyx. - """ - if have_same_parent_c(left, right): - return (left)._add_(right) - if type(right) is int: - return (left)._add_long(PyInt_AS_LONG(right)) - elif type(left) is int: - return (right)._add_long(PyInt_AS_LONG(left)) - return coercion_model.bin_op(left, right, add) - - cdef _add_long(self, long n): - """ - Generic path for adding a C long, assumed to commute. - """ - return coercion_model.bin_op(self, n, add) - - def __sub__(left, right): - """ - Top-level subtraction operator for RingElements. - - See extensive documentation at the top of element.pyx. - """ - cdef long n - if have_same_parent_c(left, right): - return (left)._sub_(right) - if type(right) is int: - n = PyInt_AS_LONG(right) - # See UNARY_NEG_WOULD_OVERFLOW in Python's intobject.c - if (n == 0) | (n != 0 - n): - return (left)._add_long(-n) - return coercion_model.bin_op(left, right, sub) - - ################################## - # Multiplication - ################################## - - def __mul__(left, right): - """ - Top-level multiplication operator for ring elements. - See extensive documentation at the top of element.pyx. - - AUTHOR: - - - Gonzalo Tornaria (2007-06-25) - write base-extending test cases and fix them - - TESTS: - - Here we test (scalar * vector) multiplication:: - - sage: parent(ZZ(1)*vector(ZZ,[1,2])) - Ambient free module of rank 2 over the principal ideal domain Integer Ring - sage: parent(QQ(1)*vector(ZZ,[1,2])) - Vector space of dimension 2 over Rational Field - sage: parent(ZZ(1)*vector(QQ,[1,2])) - Vector space of dimension 2 over Rational Field - sage: parent(QQ(1)*vector(QQ,[1,2])) - Vector space of dimension 2 over Rational Field - - sage: parent(QQ(1)*vector(ZZ['x'],[1,2])) - Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x'](1)*vector(QQ,[1,2])) - Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field - - sage: parent(QQ(1)*vector(ZZ['x']['y'],[1,2])) - Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x']['y'](1)*vector(QQ,[1,2])) - Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - - sage: parent(QQ['x'](1)*vector(ZZ['x']['y'],[1,2])) - Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x']['y'](1)*vector(QQ['x'],[1,2])) - Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - - sage: parent(QQ['y'](1)*vector(ZZ['x']['y'],[1,2])) - Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x']['y'](1)*vector(QQ['y'],[1,2])) - Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - - sage: parent(ZZ['x'](1)*vector(ZZ['y'],[1,2])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Integer Ring' - sage: parent(ZZ['x'](1)*vector(QQ['y'],[1,2])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in y over Rational Field' - sage: parent(QQ['x'](1)*vector(ZZ['y'],[1,2])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Integer Ring' - sage: parent(QQ['x'](1)*vector(QQ['y'],[1,2])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in y over Rational Field' - - Here we test (scalar * matrix) multiplication:: - - sage: parent(ZZ(1)*matrix(ZZ,2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Integer Ring - sage: parent(QQ(1)*matrix(ZZ,2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: parent(ZZ(1)*matrix(QQ,2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: parent(QQ(1)*matrix(QQ,2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Rational Field - - sage: parent(QQ(1)*matrix(ZZ['x'],2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x'](1)*matrix(QQ,2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field - - sage: parent(QQ(1)*matrix(ZZ['x']['y'],2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x']['y'](1)*matrix(QQ,2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - - sage: parent(QQ['x'](1)*matrix(ZZ['x']['y'],2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x']['y'](1)*matrix(QQ['x'],2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - - sage: parent(QQ['y'](1)*matrix(ZZ['x']['y'],2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - sage: parent(ZZ['x']['y'](1)*matrix(QQ['y'],2,2,[1,2,3,4])) - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field - - sage: parent(ZZ['x'](1)*matrix(ZZ['y'],2,2,[1,2,3,4])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Integer Ring' - sage: parent(ZZ['x'](1)*matrix(QQ['y'],2,2,[1,2,3,4])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Rational Field' - sage: parent(QQ['x'](1)*matrix(ZZ['y'],2,2,[1,2,3,4])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Integer Ring' - sage: parent(QQ['x'](1)*matrix(QQ['y'],2,2,[1,2,3,4])) - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Rational Field' - - """ - # Try fast pathway if they are both RingElements and the parents match. - # (We know at least one of the arguments is a RingElement. So if their - # types are *equal* (fast to check) then they are both RingElements. - # Otherwise use the slower test via isinstance.) - if have_same_parent_c(left, right): - return (left)._mul_(right) - if type(right) is int: - return (left)._mul_long(PyInt_AS_LONG(right)) - elif type(left) is int: - return (right)._mul_long(PyInt_AS_LONG(left)) - return coercion_model.bin_op(left, right, mul) - - cpdef _mul_(self, right): - """ - Cython classes should override this function to implement multiplication. - See extensive documentation at the top of element.pyx. - """ - raise TypeError(arith_error_message(self, right, mul)) - def __pow__(self, n, dummy): """ Return the (integral) power of self. @@ -1779,92 +1872,18 @@ cdef class RingElement(ModuleElement): l.append(x) return l - ################################## - # Division - ################################## - - def __truediv__(self, right): - """ - Top-level true division operator for ring elements. - See extensive documentation at the top of element.pyx. - - If two elements have the same parent, we just call ``_div_`` - because all divisions of Sage elements are really true - divisions. - - EXAMPLES:: - - sage: operator.truediv(2, 3) - 2/3 - sage: operator.truediv(pi, 3) - 1/3*pi - """ - if have_same_parent_c(self, right): - return (self)._div_(right) - return coercion_model.bin_op(self, right, truediv) - - def __div__(self, right): - """ - Top-level division operator for ring elements. - See extensive documentation at the top of element.pyx. + cpdef _div_(self, other): """ - if have_same_parent_c(self, right): - return (self)._div_(right) - return coercion_model.bin_op(self, right, div) - - cpdef _div_(self, right): - """ - Cython classes should override this function to implement division. - See extensive documentation at the top of element.pyx. + Default implementation of division using the fraction field. """ try: - return self._parent.fraction_field()(self, right) + frac = self._parent.fraction_field() except AttributeError: - if not right: - raise ZeroDivisionError("Cannot divide by zero") - else: - raise TypeError(arith_error_message(self, right, div)) - - def __floordiv__(self, right): - """ - Top-level floor division operator for ring elements. - See extensive documentation at the top of element.pyx. - - EXAMPLES:: - - sage: 7 // 3 - 2 - sage: 7 // int(3) - 2 - sage: int(7) // 3 - 2 - sage: p = Parent() - sage: e = RingElement(p) - sage: e // e - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for '//': '' and '' - """ - if have_same_parent_c(self, right): - return (self)._floordiv_(right) - return coercion_model.bin_op(self, right, floordiv) - - cpdef _floordiv_(self, right): - """ - Cython classes should override this function to implement floor - division. See extensive documentation at the top of element.pyx. - - EXAMPLES:: - - sage: 23._floordiv_(5) - 4 - """ - raise TypeError(arith_error_message(self, right, floordiv)) + raise TypeError(bin_op_error_message('/', self, other)) + return frac(self, other) def __invert__(self): - if self.is_one(): - return self - return 1/self + return self._parent.one() / self def additive_order(self): """ @@ -2520,8 +2539,55 @@ cdef class Vector(ModuleElement): ... TypeError: unsupported operand parent(s) for '*': 'Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field' + Here we test (scalar * vector) multiplication:: + + sage: parent(ZZ(1)*vector(ZZ,[1,2])) + Ambient free module of rank 2 over the principal ideal domain Integer Ring + sage: parent(QQ(1)*vector(ZZ,[1,2])) + Vector space of dimension 2 over Rational Field + sage: parent(ZZ(1)*vector(QQ,[1,2])) + Vector space of dimension 2 over Rational Field + sage: parent(QQ(1)*vector(QQ,[1,2])) + Vector space of dimension 2 over Rational Field + + sage: parent(QQ(1)*vector(ZZ['x'],[1,2])) + Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x'](1)*vector(QQ,[1,2])) + Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field + + sage: parent(QQ(1)*vector(ZZ['x']['y'],[1,2])) + Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x']['y'](1)*vector(QQ,[1,2])) + Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + + sage: parent(QQ['x'](1)*vector(ZZ['x']['y'],[1,2])) + Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x']['y'](1)*vector(QQ['x'],[1,2])) + Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + + sage: parent(QQ['y'](1)*vector(ZZ['x']['y'],[1,2])) + Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x']['y'](1)*vector(QQ['y'],[1,2])) + Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + + sage: parent(ZZ['x'](1)*vector(ZZ['y'],[1,2])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Integer Ring' + sage: parent(ZZ['x'](1)*vector(QQ['y'],[1,2])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in y over Rational Field' + sage: parent(QQ['x'](1)*vector(ZZ['y'],[1,2])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in y over Integer Ring' + sage: parent(QQ['x'](1)*vector(QQ['y'],[1,2])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in y over Rational Field' """ - if have_same_parent_c(left, right): + if have_same_parent(left, right): return (left)._dot_product_(right) return coercion_model.bin_op(left, right, mul) @@ -2529,10 +2595,13 @@ cdef class Vector(ModuleElement): return left._dot_product_coerce_(right) cpdef _dot_product_coerce_(Vector left, Vector right): - raise TypeError(arith_error_message(left, right, mul)) + raise TypeError(bin_op_error_message('*', left, right)) cpdef _pairwise_product_(Vector left, Vector right): - raise TypeError("unsupported operation for '%s' and '%s'"%(parent_c(left), parent_c(right))) + raise TypeError("unsupported operation for '%s' and '%s'"%(parent(left), parent(right))) + + def __div__(self, other): + return self / other def __truediv__(self, right): right = py_scalar_to_element(right) @@ -2548,10 +2617,7 @@ cdef class Vector(ModuleElement): raise ZeroDivisionError("division by zero vector") else: raise ArithmeticError("vector is not in free module") - raise TypeError(arith_error_message(self, right, div)) - - def __div__(self, right): - return PyNumber_TrueDivide(self, right) + raise TypeError(bin_op_error_message('/', self, right)) def _magma_init_(self, magma): """ @@ -2761,6 +2827,54 @@ cdef class Matrix(ModuleElement): ... TypeError: unsupported operand parent(s) for '*': 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field' + Here we test (scalar * matrix) multiplication:: + + sage: parent(ZZ(1)*matrix(ZZ,2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Integer Ring + sage: parent(QQ(1)*matrix(ZZ,2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Rational Field + sage: parent(ZZ(1)*matrix(QQ,2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Rational Field + sage: parent(QQ(1)*matrix(QQ,2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Rational Field + + sage: parent(QQ(1)*matrix(ZZ['x'],2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x'](1)*matrix(QQ,2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field + + sage: parent(QQ(1)*matrix(ZZ['x']['y'],2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x']['y'](1)*matrix(QQ,2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + + sage: parent(QQ['x'](1)*matrix(ZZ['x']['y'],2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x']['y'](1)*matrix(QQ['x'],2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + + sage: parent(QQ['y'](1)*matrix(ZZ['x']['y'],2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + sage: parent(ZZ['x']['y'](1)*matrix(QQ['y'],2,2,[1,2,3,4])) + Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field + + sage: parent(ZZ['x'](1)*matrix(ZZ['y'],2,2,[1,2,3,4])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Integer Ring' + sage: parent(ZZ['x'](1)*matrix(QQ['y'],2,2,[1,2,3,4])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Integer Ring' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Rational Field' + sage: parent(QQ['x'](1)*matrix(ZZ['y'],2,2,[1,2,3,4])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Integer Ring' + sage: parent(QQ['x'](1)*matrix(QQ['y'],2,2,[1,2,3,4])) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Univariate Polynomial Ring in x over Rational Field' and 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in y over Rational Field' + Examples with matrices having matrix coefficients:: sage: m = matrix @@ -3209,7 +3323,7 @@ cdef class CoercionModel: cpdef bin_op(self, x, y, op): if parent(x) is parent(y): return op(x,y) - raise TypeError(arith_error_message(x,y,op)) + raise TypeError(bin_op_error_message(op, x, y)) cpdef richcmp(self, x, y, int op): x, y = self.canonical_coercion(x, y) From 343131a9951dc842c462809b9366b13416382dfc Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 30 Aug 2016 12:31:07 +0200 Subject: [PATCH 02/10] Fix doctest for Trac #20767 --- src/sage/rings/asymptotic/growth_group.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py index e752f492c3c..16a37743264 100644 --- a/src/sage/rings/asymptotic/growth_group.py +++ b/src/sage/rings/asymptotic/growth_group.py @@ -633,10 +633,8 @@ def _substitute_(self, rules): sage: Variable('1/x')._substitute_({'x': 'z'}) Traceback (most recent call last): ... - TypeError: Cannot substitute in 1/x in - . - > *previous* TypeError: unsupported operand parent(s) for '/': - 'Integer Ring' and '' + TypeError: Cannot substitute in 1/x in . + > *previous* TypeError: unsupported operand type(s) for /: 'sage.rings.integer.Integer' and 'str' sage: Variable('1/x')._substitute_({'x': 0}) Traceback (most recent call last): ... From 1f964c707019c2fe2ece49be49688d1eff3e7525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Tue, 6 Sep 2016 12:11:16 +0200 Subject: [PATCH 03/10] 20767: minor documentation reformating / improvements --- src/sage/categories/coercion_methods.pyx | 5 +++-- src/sage/geometry/hyperplane_arrangement/hyperplane.py | 6 +++++- src/sage/groups/matrix_gps/group_element.pyx | 6 ++++-- src/sage/rings/asymptotic/growth_group.py | 6 ++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/sage/categories/coercion_methods.pyx b/src/sage/categories/coercion_methods.pyx index ca63524dff2..1d9831f26cd 100644 --- a/src/sage/categories/coercion_methods.pyx +++ b/src/sage/categories/coercion_methods.pyx @@ -17,7 +17,8 @@ def _mul_parent(self, other): Return the product of the two elements, calculated using the ``product`` method of the parent. - This is the default implementation of ``_mul_`` if + This is inserted by :meth:`Magmas.ParentMethods.__init_extra__` as + default implementation of ``Magmas.ElementMethods._mul_`` if ``product`` is implemented in the parent. INPUT: @@ -37,8 +38,8 @@ def _mul_parent(self, other): .. SEEALSO:: - - :meth:`Magmas.ElementMethods._mul_` - :meth:`Magmas.ElementMethods._mul_parent` + - :meth:`Magmas.ElementMethods.__init_extra__` - :meth:`Magmas.ParentMethods.product` This is :meth:`Magmas.ElementMethods._mul_parent`, implemented as diff --git a/src/sage/geometry/hyperplane_arrangement/hyperplane.py b/src/sage/geometry/hyperplane_arrangement/hyperplane.py index 22e5d184c02..a16585bcbe0 100644 --- a/src/sage/geometry/hyperplane_arrangement/hyperplane.py +++ b/src/sage/geometry/hyperplane_arrangement/hyperplane.py @@ -91,7 +91,11 @@ sage: arrangement + x Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for '+': 'Hyperplane arrangements in 3-dimensional linear space over Rational Field with coordinates x, y, z' and 'Hyperplane arrangements in 3-dimensional linear space over Rational Field with coordinates x, y, z' + TypeError: unsupported operand parent(s) for '+': + 'Hyperplane arrangements in 3-dimensional linear space + over Rational Field with coordinates x, y, z' and + 'Hyperplane arrangements in 3-dimensional linear space + over Rational Field with coordinates x, y, z' """ #***************************************************************************** diff --git a/src/sage/groups/matrix_gps/group_element.pyx b/src/sage/groups/matrix_gps/group_element.pyx index 21537b13530..fb1612ab1bb 100644 --- a/src/sage/groups/matrix_gps/group_element.pyx +++ b/src/sage/groups/matrix_gps/group_element.pyx @@ -23,10 +23,12 @@ there:: sage: g + h Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for '+': 'Matrix group over Finite Field of size 3 with 2 generators ( + TypeError: unsupported operand parent(s) for '+': + 'Matrix group over Finite Field of size 3 with 2 generators ( [1 0] [1 1] [0 1], [0 1] - )' and 'Matrix group over Finite Field of size 3 with 2 generators ( + )' and + 'Matrix group over Finite Field of size 3 with 2 generators ( [1 0] [1 1] [0 1], [0 1] )' diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py index 16a37743264..01a507385cd 100644 --- a/src/sage/rings/asymptotic/growth_group.py +++ b/src/sage/rings/asymptotic/growth_group.py @@ -633,8 +633,10 @@ def _substitute_(self, rules): sage: Variable('1/x')._substitute_({'x': 'z'}) Traceback (most recent call last): ... - TypeError: Cannot substitute in 1/x in . - > *previous* TypeError: unsupported operand type(s) for /: 'sage.rings.integer.Integer' and 'str' + TypeError: Cannot substitute in 1/x in + . + > *previous* TypeError: unsupported operand type(s) for /: + 'sage.rings.integer.Integer' and 'str' sage: Variable('1/x')._substitute_({'x': 0}) Traceback (most recent call last): ... From 6b1f65f9915194a24a87a00aea6edc848fc2f7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Tue, 6 Sep 2016 12:12:03 +0200 Subject: [PATCH 04/10] 20767: additional test, use Element rather than RingElement in some tests, minor doc improvement --- src/sage/structure/element.pyx | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index f58c4d5cda7..09aadf9e4c4 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -1020,8 +1020,8 @@ cdef class Element(SageObject): EXAMPLES:: - sage: from sage.structure.element import RingElement - sage: e = RingElement(Parent()) + sage: from sage.structure.element import Element + sage: e = Element(Parent()) sage: e + e Traceback (most recent call last): ... @@ -1037,19 +1037,19 @@ cdef class Element(SageObject): sage: int(1) + e Traceback (most recent call last): ... - TypeError: unsupported operand type(s) for +: 'int' and 'sage.structure.element.RingElement' + TypeError: unsupported operand type(s) for +: 'int' and 'sage.structure.element.Element' sage: e + int(1) Traceback (most recent call last): ... - TypeError: unsupported operand type(s) for +: 'sage.structure.element.RingElement' and 'int' + TypeError: unsupported operand type(s) for +: 'sage.structure.element.Element' and 'int' sage: None + e Traceback (most recent call last): ... - TypeError: unsupported operand type(s) for +: 'NoneType' and 'sage.structure.element.RingElement' + TypeError: unsupported operand type(s) for +: 'NoneType' and 'sage.structure.element.Element' sage: e + None Traceback (most recent call last): ... - TypeError: unsupported operand type(s) for +: 'sage.structure.element.RingElement' and 'NoneType' + TypeError: unsupported operand type(s) for +: 'sage.structure.element.Element' and 'NoneType' """ cdef int cl = classify_elements(left, right) if HAVE_SAME_PARENT(cl): @@ -1077,18 +1077,19 @@ cdef class Element(SageObject): """ Virtual addition method for elements with identical parents. - This is meant to implement the fact that there is no ``_add_`` - method: it looks up a Python ``_add_`` method and calls that. - This indirectly also looks up ``_add_`` in the category. - If there is no Python ``_add_`` method, an exception is raised - saying that addition is not implemented. + This default Cython implementation of ``_add_`` calls the + eponymous Python method in the class of ``self``, if it exists. + Otherwise an ``AttributeError`` exception is raised. - EXAMPLES:: + For general context on coercion, see :mod:`sage.structure.element`. - sage: 23._add_(5) - 28 + EXAMPLES:: - :: + sage: class MyElement(Element): + ....: def _add_(self, other): return 42 + sage: x = MyElement(Parent()) + sage: x+x + 42 sage: from sage.structure.element import Element sage: e = Element(Parent()) @@ -1115,7 +1116,7 @@ cdef class Element(SageObject): Top-level subtraction operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See extensive documentation of :mod:`sage.structure.element`. EXAMPLES:: From 35b64a881aaec4c1ca236a37e9a21df4624aeda4 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 6 Sep 2016 23:41:18 +0200 Subject: [PATCH 05/10] Fix division action --- src/sage/structure/coerce.pyx | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx index 209ed832122..e2e7d8b5dc5 100644 --- a/src/sage/structure/coerce.pyx +++ b/src/sage/structure/coerce.pyx @@ -474,7 +474,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): To get an actual valid map, we simply copy the weakly referenced coercion map:: - + sage: print(copy(left_morphism_ref())) Natural morphism: From: Integer Ring @@ -1768,8 +1768,13 @@ cdef class CoercionModel_cache_maps(CoercionModel): if op is div: # Division on right is the same acting on right by inverse, if it is so defined. - right_mul = self.get_action(R, S, mul) - if right_mul and not right_mul.is_left(): + right_mul = None + try: + right_mul = self.get_action(R, S, mul) + except NotImplementedError: + self._record_exception() + + if right_mul is not None and not right_mul.is_left(): try: action = ~right_mul if action.right_domain() != S: @@ -1780,11 +1785,18 @@ cdef class CoercionModel_cache_maps(CoercionModel): self._record_exception() # It's possible an action is defined on the fraction field itself. - if hasattr(S, '_pseudo_fraction_field'): + try: K = S._pseudo_fraction_field() + except AttributeError: + pass + else: if K is not S: - right_mul = self.get_action(R, K, mul) - if right_mul and not right_mul.is_left(): + try: + right_mul = self.get_action(R, K, mul) + except NotImplementedError: + self._record_exception() + + if right_mul is not None and not right_mul.is_left(): try: return PrecomposedAction(~right_mul, None, K.coerce_map_from(S)) except TypeError: # action may not be invertible From 2e8f9e4d29b444385dc507f516df90b7c2559cb9 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Wed, 7 Sep 2016 09:13:19 +0200 Subject: [PATCH 06/10] Return exception instead of error message --- src/sage/structure/coerce.pyx | 4 ++-- src/sage/structure/element.pxd | 4 ++-- src/sage/structure/element.pyx | 30 +++++++++++++++--------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx index e2e7d8b5dc5..237ab9de297 100644 --- a/src/sage/structure/coerce.pyx +++ b/src/sage/structure/coerce.pyx @@ -87,7 +87,7 @@ from operator import add, sub, mul, div, truediv, iadd, isub, imul, idiv from .sage_object cimport SageObject, rich_to_bool from .parent cimport Set_PythonType, Parent_richcmp_element_without_coercion -from .element cimport bin_op_error_message, parent_c, Element +from .element cimport bin_op_exception, parent_c, Element from .coerce_actions import LeftModuleAction, RightModuleAction, IntegerMulAction from .coerce_exceptions import CoercionException from sage.categories.map cimport Map @@ -1107,7 +1107,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): # We should really include the underlying error. # This causes so much headache. - raise TypeError(bin_op_error_message(op, x, y)) + raise bin_op_exception(op, x, y) cpdef canonical_coercion(self, x, y): r""" diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index 0a9eec00ce9..c581238af0f 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -162,8 +162,8 @@ cdef inline parent_c(x): return parent(x) -cdef un_op_error_message(op, x) -cdef bin_op_error_message(op, x, y) +cdef unary_op_exception(op, x) +cdef bin_op_exception(op, x, y) cdef class Element(SageObject): diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 09aadf9e4c4..f27f3399e2b 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -183,17 +183,17 @@ def make_element(_class, _dict, parent): return make_element_old(_class, _dict, parent) -cdef un_op_error_message(op, x): +cdef unary_op_exception(op, x): try: op = op.__name__ op = _coerce_op_symbols[op] except (AttributeError, KeyError): pass px = parent(x) - return f"unsupported operand parent for {op}: '{px}'" + return TypeError(f"unsupported operand parent for {op}: '{px}'") -cdef bin_op_error_message(op, x, y): +cdef bin_op_exception(op, x, y): try: op = op.__name__ op = _coerce_op_symbols[op] @@ -201,7 +201,7 @@ cdef bin_op_error_message(op, x, y): pass px = parent(x) py = parent(y) - return f"unsupported operand parent(s) for {op!r}: '{px}' and '{py}'" + return TypeError(f"unsupported operand parent(s) for {op!r}: '{px}' and '{py}'") def is_Element(x): @@ -1101,7 +1101,7 @@ cdef class Element(SageObject): try: python_op = (self)._add_ except AttributeError: - raise TypeError(bin_op_error_message('+', self, other)) + raise bin_op_exception('+', self, other) else: return python_op(other) @@ -1144,7 +1144,7 @@ cdef class Element(SageObject): try: python_op = (self)._sub_ except AttributeError: - raise TypeError(bin_op_error_message('-', self, other)) + raise bin_op_exception('-', self, other) else: return python_op(other) @@ -1168,7 +1168,7 @@ cdef class Element(SageObject): try: python_op = (self)._neg_ except AttributeError: - raise TypeError(un_op_error_message('unary -', self)) + raise unary_op_exception('unary -', self) else: return python_op() @@ -1213,7 +1213,7 @@ cdef class Element(SageObject): try: python_op = (self)._mul_ except AttributeError: - raise TypeError(bin_op_error_message('*', self, other)) + raise bin_op_exception('*', self, other) else: return python_op(other) @@ -1304,7 +1304,7 @@ cdef class Element(SageObject): try: python_op = (self)._div_ except AttributeError: - raise TypeError(bin_op_error_message('/', self, other)) + raise bin_op_exception('/', self, other) else: return python_op(other) @@ -1350,7 +1350,7 @@ cdef class Element(SageObject): try: python_op = (self)._floordiv_ except AttributeError: - raise TypeError(bin_op_error_message('//', self, other)) + raise bin_op_exception('//', self, other) else: return python_op(other) @@ -1401,7 +1401,7 @@ cdef class Element(SageObject): try: python_op = (self)._mod_ except AttributeError: - raise TypeError(bin_op_error_message('%', self, other)) + raise bin_op_exception('%', self, other) else: return python_op(other) @@ -1917,7 +1917,7 @@ cdef class RingElement(ModuleElement): try: frac = self._parent.fraction_field() except AttributeError: - raise TypeError(bin_op_error_message('/', self, other)) + raise bin_op_exception('/', self, other) return frac(self, other) def __invert__(self): @@ -2633,7 +2633,7 @@ cdef class Vector(ModuleElement): return left._dot_product_coerce_(right) cpdef _dot_product_coerce_(Vector left, Vector right): - raise TypeError(bin_op_error_message('*', left, right)) + raise bin_op_exception('*', left, right) cpdef _pairwise_product_(Vector left, Vector right): raise TypeError("unsupported operation for '%s' and '%s'"%(parent(left), parent(right))) @@ -2655,7 +2655,7 @@ cdef class Vector(ModuleElement): raise ZeroDivisionError("division by zero vector") else: raise ArithmeticError("vector is not in free module") - raise TypeError(bin_op_error_message('/', self, right)) + raise bin_op_exception('/', self, right) def _magma_init_(self, magma): """ @@ -3361,7 +3361,7 @@ cdef class CoercionModel: cpdef bin_op(self, x, y, op): if parent(x) is parent(y): return op(x,y) - raise TypeError(bin_op_error_message(op, x, y)) + raise bin_op_exception(op, x, y) cpdef richcmp(self, x, y, int op): x, y = self.canonical_coercion(x, y) From 59a04e46a93e6c0b4fe811fabf9c8eee7ed8e3e8 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 6 Sep 2016 18:26:13 +0200 Subject: [PATCH 07/10] Improve documentation and tests --- src/sage/categories/associative_algebras.py | 19 - src/sage/categories/unital_algebras.py | 20 +- src/sage/rings/infinity.py | 7 +- src/sage/structure/element.pyx | 564 ++++++++++++++++++-- 4 files changed, 510 insertions(+), 100 deletions(-) diff --git a/src/sage/categories/associative_algebras.py b/src/sage/categories/associative_algebras.py index aee68f2e6e7..8d386e44ca3 100644 --- a/src/sage/categories/associative_algebras.py +++ b/src/sage/categories/associative_algebras.py @@ -43,23 +43,4 @@ class AssociativeAlgebras(CategoryWithAxiom_over_base_ring): """ _base_category_class_and_axiom = (MagmaticAlgebras, "Associative") - class ElementMethods: - """ - An abstract class for elements of an associative algebra. - - TESTS:: - - sage: A = AlgebrasWithBasis(QQ).example(); A - An example of an algebra with basis: the free algebra - on the generators ('a', 'b', 'c') over Rational Field - sage: x = A.an_element() - sage: x - B[word: ] + 2*B[word: a] + 3*B[word: b] + B[word: bab] - sage: x.__mul__(x) - B[word: ] + 4*B[word: a] + 4*B[word: aa] + 6*B[word: ab] - + 2*B[word: abab] + 6*B[word: b] + 6*B[word: ba] - + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] - + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] - """ - Unital = LazyImport('sage.categories.algebras', 'Algebras', at_startup=True) diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index a77f34b08a8..abb60701dd2 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -150,23 +150,6 @@ def __init_extra__(self): except AssertionError: pass - class ElementMethods: - """ - TESTS:: - - sage: A = AlgebrasWithBasis(QQ).example(); A - An example of an algebra with basis: the free algebra - on the generators ('a', 'b', 'c') over Rational Field - sage: x = A.an_element() - sage: x - B[word: ] + 2*B[word: a] + 3*B[word: b] + B[word: bab] - sage: x.__mul__(x) - B[word: ] + 4*B[word: a] + 4*B[word: aa] + 6*B[word: ab] - + 2*B[word: abab] + 6*B[word: b] + 6*B[word: ba] - + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] - + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] - """ - class WithBasis(CategoryWithAxiom_over_base_ring): class ParentMethods: @@ -282,5 +265,4 @@ def from_base_ring_from_one_basis(self, r): sage: A(3) 3*B[word: ] """ - return self.term(self.one_basis(), r) #. - + return self.term(self.one_basis(), r) diff --git a/src/sage/rings/infinity.py b/src/sage/rings/infinity.py index 46161f93644..ca8e5d95b52 100644 --- a/src/sage/rings/infinity.py +++ b/src/sage/rings/infinity.py @@ -95,9 +95,10 @@ ... ValueError: quotient of number < oo by number < oo not defined -What happened above is that 0 is canonically coerced to "a number -less than infinity" in the unsigned infinity ring, and the quotient -is then not well-defined. +What happened above is that 0 is canonically coerced to "A number less +than infinity" in the unsigned infinity ring. Next, Sage tries to divide +by multiplying with its inverse. Finally, this inverse is not +well-defined. :: diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index f27f3399e2b..6d7c6a432b3 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -23,7 +23,7 @@ AUTHORS: :class:`Element`, see :trac:`20767` The Abstract Element Class Hierarchy ------------------------------------- +==================================== This is the abstract class hierarchy, i.e., these are all abstract base classes. @@ -53,11 +53,11 @@ abstract base classes. How to Define a New Element Class ---------------------------------- +================================= Elements typically define a method ``_new_c``, e.g., -:: +.. code-block:: cython cdef _new_c(self, defining data): cdef FreeModuleElement_generic_dense x @@ -68,63 +68,155 @@ Elements typically define a method ``_new_c``, e.g., that creates a new sibling very quickly from defining data with assumed properties. +.. _element_arithmetic: + +Arithmetic for Elements +----------------------- + Sage has a special system in place for handling arithmetic operations -for all Element subclasses. There are various rules that must be -followed by both arithmetic implementers and callers. +for all instances of subclasses of :class:`Element`. There are various +rules that must be followed by both arithmetic implementers and callers. + +A quick summary for the impatient +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- When writing pure *Python* code: + + - To implement addition for any :class:`Element` subclass, override + ``def _add_(self, other)`` instead of ``__add__``. In ``_add_``, + you may assume that ``self`` and ``other`` have the same parent. + +- When writing *Cython* code: -A quick summary for the impatient: + - To implement addition for any :class:`Element` subclass, override + ``cpdef _add_(self, other)`` instead of ``__add__``. In ``_add_``, + you may assume that ``self`` and ``other`` have the same parent. -- To implement addition for any Element class, override - ``def _add_(self, other)`` in Python or ``cpdef _add_(self, other)`` - in Cython. + - If you want to add ``x`` and ``y``, whose parents you know are + **identical**, you may call ``x._add_(y)`` in Cython. This will be the + fastest way to guarantee that the correct implementation gets called. + Of course you can still always use ``x + y``. -- If you want to add ``x`` and ``y``, whose parents you know are - **identical**, you may call ``x._add_(y)`` in Cython. This will be the - fastest way to guarantee that the correct implementation gets called. - Of course you can still always use ``x + y``. +When doing arithmetic with two elements having different parents, +the :mod:`coercion model ` is responsible for +"coercing" them to a common parent and performing arithmetic on the +coerced elements. -Now in more detail. The aims of this system are to provide (1) an efficient -calling protocol from both Python and Cython, (2) uniform coercion semantics -across Sage, (3) ease of use, (4) readability of code. +Arithmetic in more detail +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The aims of this system are to provide (1) an efficient calling protocol +from both Python and Cython, (2) uniform coercion semantics across Sage, +(3) ease of use, (4) readability of code. We will take addition as an example; all other operators are similar. There are two relevant functions, with differing names (single vs. double underscores). -- **def Element.__add__** +- **def Element.__add__(left, right)** This function is called by Python or Cython when the binary "+" - operator is encountered. It **assumes** that at least one of its + operator is encountered. It assumes that at least one of its arguments is an :class:`Element`. It has a fast pathway to deal with the most common case where both arguments have the same parent. Otherwise, it uses the coercion - framework to work out how to make them have the same parent. After - any necessary coercions have been performed, it calls ``_add_`` to - dispatch to the correct underlying addition implementation. + model to work out how to make them have the same parent. The + coercion model then adds the coerced elements (technically, it calls + ``operator.add``). Note that the result of coercion is not required + to be a Sage :class:`Element`, it could be a plain Python type. Note that although this function is declared as ``def``, it doesn't have the usual overheads associated with Python functions (either for the caller or for ``__add__`` itself). This is because Python has optimised calling protocols for such special functions. -- **def Element._add_** +- **def Element._add_(self, other)** - This is the function you should override to implement addition in a - subclass of ``Element``. + This is the function that you should override to implement addition + in a subclass of :class:`Element`. The two arguments to this function are guaranteed to have the **same - parent**. Its return value **must** have the **same parent** as its - arguments. + parent**, but not necessarily the same Python type. - In Cython code, if you want to add two objects and you know that - their parents are the same object, you are encouraged to call this + In Cython code, if you want to add two elements and you know that + their parents are identical, you are encouraged to call this function directly, instead of using ``x + y``. This only works if Cython knows that the left argument is an ``Element``. You can always cast explicitly: ``(x)._add_(y)`` to force this. When implementing ``_add_`` in a Cython extension type, use ``cpdef _add_`` instead of ``def _add_``. + +The difference in the names of the arguments (``left, right`` +versus ``self, other``) is intentional: ``self`` is guaranteed to be an +instance of the class in which the method is defined. In Cython, we know +that at least one of ``left`` or ``right`` is an instance of the class +but we do not know a priori which one. + +Examples +^^^^^^^^ + +We need some :class:`Parent` to work with:: + + sage: from sage.structure.parent import Parent + sage: class ExampleParent(Parent): + ....: def __init__(self, name, **kwds): + ....: Parent.__init__(self, **kwds) + ....: self.rename(name) + +We start with a very basic example of a Python class implementing +``_add_``:: + + sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _add_(self, other): + ....: return 42 + sage: p = ExampleParent("Some parent") + sage: x = MyElement(p) + sage: x + x + 42 + +When two different parents are involved, this no longer works since +there is no coercion:: + + sage: q = ExampleParent("Other parent") + sage: y = MyElement(q) + sage: x + y + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': 'Some parent' and 'Other parent' + +If ``_add_`` is not defined, an error message is raised, referring to +the parents:: + + sage: x = Element(p) + sage: x._add_(x) + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_add_' + sage: x + x + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': 'Some parent' and 'Some parent' + sage: y = Element(q) + sage: x + y + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': 'Some parent' and 'Other parent' + +We can also implement arithmetic in the category:: + + sage: class MyCategory(Category): + ....: def super_categories(self): + ....: return [Sets()] + ....: class ElementMethods: + ....: def _add_(self, other): + ....: return 42 + sage: p = ExampleParent("Parent with add", category=MyCategory()) + sage: x = Element(p) + sage: x + x + 42 """ #***************************************************************************** @@ -222,13 +314,22 @@ def is_Element(x): cdef class Element(SageObject): """ Generic element of a structure. All other types of elements - (RingElement, ModuleElement, etc) derive from this type. + (:class:`RingElement`, :class:`ModuleElement`, etc) + derive from this type. Subtypes must either call ``__init__()`` to set ``_parent``, or may set ``_parent`` themselves if that would be more efficient. .. automethod:: _cmp_ .. automethod:: _richcmp_ + .. automethod:: __add__ + .. automethod:: __sub__ + .. automethod:: __neg__ + .. automethod:: __mul__ + .. automethod:: __div__ + .. automethod:: __truediv__ + .. automethod:: __floordiv__ + .. automethod:: __mod__ """ def __getmetaclass__(_): from sage.misc.inherit_comparison import InheritComparisonMetaclass @@ -1016,11 +1117,20 @@ cdef class Element(SageObject): Top-level addition operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. EXAMPLES:: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _add_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: e + e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: e + e Traceback (most recent call last): @@ -1078,18 +1188,16 @@ cdef class Element(SageObject): Virtual addition method for elements with identical parents. This default Cython implementation of ``_add_`` calls the - eponymous Python method in the class of ``self``, if it exists. - Otherwise an ``AttributeError`` exception is raised. + Python method ``self._add_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. - For general context on coercion, see :mod:`sage.structure.element`. + See :ref:`element_arithmetic`. - EXAMPLES:: + EXAMPLES: - sage: class MyElement(Element): - ....: def _add_(self, other): return 42 - sage: x = MyElement(Parent()) - sage: x+x - 42 + This method is not visible from Python:: sage: from sage.structure.element import Element sage: e = Element(Parent()) @@ -1116,16 +1224,49 @@ cdef class Element(SageObject): Top-level subtraction operator for :class:`Element` invoking the coercion model. - See extensive documentation of :mod:`sage.structure.element`. + See :ref:`element_arithmetic`. EXAMPLES:: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _sub_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: e - e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: e - e Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '-': '' and '' + sage: 1 - e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '-': 'Integer Ring' and '' + sage: e - 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '-': '' and 'Integer Ring' + sage: int(1) - e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'int' and 'sage.structure.element.Element' + sage: e - int(1) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'sage.structure.element.Element' and 'int' + sage: None - e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'NoneType' and 'sage.structure.element.Element' + sage: e - None + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'sage.structure.element.Element' and 'NoneType' """ # See __add__ for comments cdef int cl = classify_elements(left, right) @@ -1140,7 +1281,28 @@ cdef class Element(SageObject): return NotImplemented cdef _sub_(self, other): - # See _add_ for documentation + """ + Virtual subtraction method for elements with identical parents. + + This default Cython implementation of ``_sub_`` calls the + Python method ``self._sub_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. + + See :ref:`element_arithmetic`. + + EXAMPLES: + + This method is not visible from Python:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e._sub_(e) + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_sub_' + """ try: python_op = (self)._sub_ except AttributeError: @@ -1155,6 +1317,15 @@ cdef class Element(SageObject): EXAMPLES:: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _neg_(self): + ....: return 42 + sage: e = MyElement(Parent()) + sage: -e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: -e Traceback (most recent call last): @@ -1164,7 +1335,28 @@ cdef class Element(SageObject): return self._neg_() cdef _neg_(self): - # See _add_ for documentation + """ + Virtual unary negation method for elements. + + This default Cython implementation of ``_neg_`` calls the + Python method ``self._neg_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. + + See :ref:`element_arithmetic`. + + EXAMPLES: + + This method is not visible from Python:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e._neg_() + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_neg_' + """ try: python_op = (self)._neg_ except AttributeError: @@ -1177,16 +1369,63 @@ cdef class Element(SageObject): Top-level multiplication operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. EXAMPLES:: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _mul_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: e * e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: e * e Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '*': '' and '' + sage: 1 * e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': 'Integer Ring' and '' + sage: e * 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': '' and 'Integer Ring' + sage: int(1) * e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for *: 'int' and 'sage.structure.element.Element' + sage: e * int(1) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for *: 'sage.structure.element.Element' and 'int' + sage: None * e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for *: 'NoneType' and 'sage.structure.element.Element' + sage: e * None + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for *: 'sage.structure.element.Element' and 'NoneType' + + :: + + sage: A = AlgebrasWithBasis(QQ).example(); A + An example of an algebra with basis: the free algebra + on the generators ('a', 'b', 'c') over Rational Field + sage: x = A.an_element() + sage: x + B[word: ] + 2*B[word: a] + 3*B[word: b] + B[word: bab] + sage: x.__mul__(x) + B[word: ] + 4*B[word: a] + 4*B[word: aa] + 6*B[word: ab] + + 2*B[word: abab] + 6*B[word: b] + 6*B[word: ba] + + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] + + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] """ cdef int cl = classify_elements(left, right) if HAVE_SAME_PARENT(cl): @@ -1205,11 +1444,27 @@ cdef class Element(SageObject): cdef _mul_(self, other): """ - Multiplication method for elements with identical parents. + Virtual multiplication method for elements with identical parents. + + This default Cython implementation of ``_mul_`` calls the + Python method ``self._mul_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. + + EXAMPLES: + + This method is not visible from Python:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e._mul_(e) + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_mul_' """ - # See _add_ for documentation try: python_op = (self)._mul_ except AttributeError: @@ -1228,7 +1483,7 @@ cdef class Element(SageObject): Top-level division operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. EXAMPLES:: @@ -1243,11 +1498,44 @@ cdef class Element(SageObject): :: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _div_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: e / e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: e / e Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '/': '' and '' + sage: 1 / e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '/': 'Integer Ring' and '' + sage: e / 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '/': '' and 'Integer Ring' + sage: int(1) / e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'int' and 'sage.structure.element.Element' + sage: e / int(1) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'sage.structure.element.Element' and 'int' + sage: None / e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'NoneType' and 'sage.structure.element.Element' + sage: e / None + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'sage.structure.element.Element' and 'NoneType' """ # See __add__ for comments cdef int cl = classify_elements(left, right) @@ -1266,7 +1554,7 @@ cdef class Element(SageObject): Top-level true division operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. EXAMPLES:: @@ -1281,11 +1569,44 @@ cdef class Element(SageObject): :: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _div_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: operator.truediv(e, e) + 42 + + TESTS:: + sage: e = Element(Parent()) sage: operator.truediv(e, e) Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '/': '' and '' + sage: operator.truediv(1, e) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '/': 'Integer Ring' and '' + sage: operator.truediv(e, 1) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '/': '' and 'Integer Ring' + sage: operator.truediv(int(1), e) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'int' and 'sage.structure.element.Element' + sage: operator.truediv(e, int(1)) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'sage.structure.element.Element' and 'int' + sage: operator.truediv(None, e) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'NoneType' and 'sage.structure.element.Element' + sage: operator.truediv(e, None) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for /: 'sage.structure.element.Element' and 'NoneType' """ # See __add__ for comments cdef int cl = classify_elements(left, right) @@ -1300,7 +1621,29 @@ cdef class Element(SageObject): return NotImplemented cdef _div_(self, other): - # See _add_ for documentation + """ + Virtual division method for elements with identical parents. + This is called for Python 2 division as well as true division. + + This default Cython implementation of ``_div_`` calls the + Python method ``self._div_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. + + See :ref:`element_arithmetic`. + + EXAMPLES: + + This method is not visible from Python:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e._div_(e) + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_div_' + """ try: python_op = (self)._div_ except AttributeError: @@ -1313,7 +1656,7 @@ cdef class Element(SageObject): Top-level floor division operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. EXAMPLES:: @@ -1327,11 +1670,44 @@ cdef class Element(SageObject): :: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _floordiv_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: e // e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: e // e Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '//': '' and '' + sage: 1 // e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '//': 'Integer Ring' and '' + sage: e // 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '//': '' and 'Integer Ring' + sage: int(1) // e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for //: 'int' and 'sage.structure.element.Element' + sage: e // int(1) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for //: 'sage.structure.element.Element' and 'int' + sage: None // e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for //: 'NoneType' and 'sage.structure.element.Element' + sage: e // None + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for //: 'sage.structure.element.Element' and 'NoneType' """ # See __add__ for comments cdef int cl = classify_elements(left, right) @@ -1346,7 +1722,28 @@ cdef class Element(SageObject): return NotImplemented cdef _floordiv_(self, other): - # See _add_ for documentation + """ + Virtual floor division method for elements with identical parents. + + This default Cython implementation of ``_floordiv_`` calls the + Python method ``self._floordiv_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. + + See :ref:`element_arithmetic`. + + EXAMPLES: + + This method is not visible from Python:: + + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e._floordiv_(e) + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_floordiv_' + """ try: python_op = (self)._floordiv_ except AttributeError: @@ -1359,7 +1756,7 @@ cdef class Element(SageObject): Top-level modulo operator for :class:`Element` invoking the coercion model. - See extensive documentation at the top of element.pyx. + See :ref:`element_arithmetic`. EXAMPLES:: @@ -1373,11 +1770,44 @@ cdef class Element(SageObject): :: sage: from sage.structure.element import Element + sage: class MyElement(Element): + ....: def _mod_(self, other): + ....: return 42 + sage: e = MyElement(Parent()) + sage: e % e + 42 + + TESTS:: + sage: e = Element(Parent()) sage: e % e Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '%': '' and '' + sage: 1 % e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '%': 'Integer Ring' and '' + sage: e % 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '%': '' and 'Integer Ring' + sage: int(1) % e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for %: 'int' and 'sage.structure.element.Element' + sage: e % int(1) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for %: 'sage.structure.element.Element' and 'int' + sage: None % e + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for %: 'NoneType' and 'sage.structure.element.Element' + sage: e % None + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for %: 'sage.structure.element.Element' and 'NoneType' """ # See __add__ for comments cdef int cl = classify_elements(left, right) @@ -1393,11 +1823,27 @@ cdef class Element(SageObject): cdef _mod_(self, other): """ - Modulo method for elements with identical parents. + Virtual modulo method for elements with identical parents. + + This default Cython implementation of ``_mod_`` calls the + Python method ``self._mod_`` if it exists. This method may be + defined in the ``ElementMethods`` of the category of the parent. + If the method is not found, a ``TypeError`` is raised + indicating that the operation is not supported. + + See :ref:`element_arithmetic`. + + EXAMPLES: + + This method is not visible from Python:: - See extensive documentation at the top of element.pyx. + sage: from sage.structure.element import Element + sage: e = Element(Parent()) + sage: e._mod_(e) + Traceback (most recent call last): + ... + AttributeError: 'sage.structure.element.Element' object has no attribute '_mod_' """ - # See _add_ for documentation try: python_op = (self)._mod_ except AttributeError: @@ -3449,11 +3895,11 @@ def coerce_binop(method): sage: S. = PolynomialRing(ZZ,sparse=True) sage: f = x^2 sage: g = x - sage: f.gcd(g) #indirect doctest + sage: f.gcd(g) #indirect doctest x sage: T = PolynomialRing(QQ, name='x', sparse=True) sage: h = 1/2*T(x) - sage: u = f.gcd(h); u #indirect doctest + sage: u = f.gcd(h); u #indirect doctest x sage: u.parent() == T True @@ -3511,8 +3957,8 @@ def coerce_binop(method): TESTS: - Test that additional arguments given to the method does not override the - `self` argument, see #21322:: + Test that additional arguments given to the method do not override + the ``self`` argument, see :trac:`21322`:: sage: f.gcd(g, 1) Traceback (most recent call last): From 2d409484e23387d98d8a697aaac76a70017f53e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Wed, 7 Sep 2016 18:10:34 +0200 Subject: [PATCH 08/10] 20767: Proofreading doc + doc of cdef _add_ trick + TODO about _add_long / _mul_long --- src/sage/structure/element.pyx | 70 +++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 6d7c6a432b3..2909eb2e10e 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -73,29 +73,30 @@ with assumed properties. Arithmetic for Elements ----------------------- -Sage has a special system in place for handling arithmetic operations -for all instances of subclasses of :class:`Element`. There are various -rules that must be followed by both arithmetic implementers and callers. +Sage has a special system for handling arithmetic operations on Sage +elements (that is instances of :class:`Element`), in particular to +manage uniformly mixed arithmetic operations using the :mod:`coercion +model `. We describe here the rules that must +be followed by both arithmetic implementers and callers. A quick summary for the impatient ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- When writing pure *Python* code: +To implement addition for any :class:`Element` subclass, override the +``def _add_(self, other)`` method instead of the usual Python +``__add__`` :python:`special method `. +Within ``_add_(self, other)``, you may assume that ``self`` and +``other`` have the same parent. - - To implement addition for any :class:`Element` subclass, override - ``def _add_(self, other)`` instead of ``__add__``. In ``_add_``, - you may assume that ``self`` and ``other`` have the same parent. +If the implementation is generic across all elements in a given +category `C`, then this method can be put in ``C.ElementMethods``. -- When writing *Cython* code: +When writing *Cython* code, ``_add_`` should be a ``cpdef method``: +``cpdef _add_(self, other)``. - - To implement addition for any :class:`Element` subclass, override - ``cpdef _add_(self, other)`` instead of ``__add__``. In ``_add_``, - you may assume that ``self`` and ``other`` have the same parent. - - - If you want to add ``x`` and ``y``, whose parents you know are - **identical**, you may call ``x._add_(y)`` in Cython. This will be the - fastest way to guarantee that the correct implementation gets called. - Of course you can still always use ``x + y``. +If you want to add ``x`` and ``y``, whose parents you know are +**identical**, the fastest idiom is ``x+y`` in Python, and +``x._add_(y)`` in Cython. Of course you can always use ``x + y``. When doing arithmetic with two elements having different parents, the :mod:`coercion model ` is responsible for @@ -126,7 +127,7 @@ There are two relevant functions, with differing names ``operator.add``). Note that the result of coercion is not required to be a Sage :class:`Element`, it could be a plain Python type. - Note that although this function is declared as ``def``, it doesn't + Note that, although this function is declared as ``def``, it doesn't have the usual overheads associated with Python functions (either for the caller or for ``__add__`` itself). This is because Python has optimised calling protocols for such special functions. @@ -154,6 +155,11 @@ instance of the class in which the method is defined. In Cython, we know that at least one of ``left`` or ``right`` is an instance of the class but we do not know a priori which one. +.. TODO:: + + Briefly mention _add_long and _mul_long, and refer to the + detailed documentation about them (in __add__ / __mul__?) + Examples ^^^^^^^^ @@ -205,7 +211,7 @@ the parents:: ... TypeError: unsupported operand parent(s) for '+': 'Some parent' and 'Other parent' -We can also implement arithmetic in the category:: +We can also implement arithmetic generically in categories:: sage: class MyCategory(Category): ....: def super_categories(self): @@ -213,10 +219,36 @@ We can also implement arithmetic in the category:: ....: class ElementMethods: ....: def _add_(self, other): ....: return 42 - sage: p = ExampleParent("Parent with add", category=MyCategory()) + sage: p = ExampleParent("Parent in my category", category=MyCategory()) sage: x = Element(p) sage: x + x 42 + +Implementation details +^^^^^^^^^^^^^^^^^^^^^^ + +Implementing the above features actually takes a bit of magic. Casual +callers and implementers can safely ignore it, but here are the +details for the curious. + +To achieve fast arithmetic, it's critical to have a fast path to call +from Cython the ``_add_`` method of a Cython object. This is achieved +by declaring the ``_add_`` method in the class +:class:`Element`. Remember however that the abstract classes coming +from categories come after :class:`Element` in the Method Resolution +Order (or fake Method Resolution Order in case of a Cython +class). Hence any generic implementation of `_add_` in such an +abstract class would be in principle shadowed by `Element._add_`. + +This is worked around by defining ``Element._add_`` as a ``cdef`` and +not ``cpdef`` method. Let now see what happens upon calling +`x.__add__(y)`` when `x` and `y` are instances of a class that does +not implement `_add_`. In the case of a Python level call, +``Element._add_`` will be invisible, and the method lookup will +continue down the MRO and find the `_add_` method in the category. In +the case of a Cython level call, `Element._add_` will be called, but +latter is implemented to trigger a Python level call to `_add_` which +will succeed as desired. """ #***************************************************************************** From fec4abe946abb0dffed85f36864af71f09abdb04 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Thu, 8 Sep 2016 11:00:15 +0200 Subject: [PATCH 09/10] Further doc additions --- src/sage/structure/element.pyx | 68 +++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 2909eb2e10e..f0f6eabb58c 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -91,13 +91,9 @@ Within ``_add_(self, other)``, you may assume that ``self`` and If the implementation is generic across all elements in a given category `C`, then this method can be put in ``C.ElementMethods``. -When writing *Cython* code, ``_add_`` should be a ``cpdef method``: +When writing *Cython* code, ``_add_`` should be a cpdef method: ``cpdef _add_(self, other)``. -If you want to add ``x`` and ``y``, whose parents you know are -**identical**, the fastest idiom is ``x+y`` in Python, and -``x._add_(y)`` in Cython. Of course you can always use ``x + y``. - When doing arithmetic with two elements having different parents, the :mod:`coercion model ` is responsible for "coercing" them to a common parent and performing arithmetic on the @@ -140,14 +136,17 @@ There are two relevant functions, with differing names The two arguments to this function are guaranteed to have the **same parent**, but not necessarily the same Python type. + When implementing ``_add_`` in a Cython extension type, use + ``cpdef _add_`` instead of ``def _add_``. + In Cython code, if you want to add two elements and you know that their parents are identical, you are encouraged to call this function directly, instead of using ``x + y``. This only works if Cython knows that the left argument is an ``Element``. You can always cast explicitly: ``(x)._add_(y)`` to force this. - - When implementing ``_add_`` in a Cython extension type, use - ``cpdef _add_`` instead of ``def _add_``. + In plain Python, ``x + y`` is always the fastest way to add two + elements because the special method ``__add__`` is optimized + unlike the normal method ``_add_``. The difference in the names of the arguments (``left, right`` versus ``self, other``) is intentional: ``self`` is guaranteed to be an @@ -155,10 +154,14 @@ instance of the class in which the method is defined. In Cython, we know that at least one of ``left`` or ``right`` is an instance of the class but we do not know a priori which one. -.. TODO:: - - Briefly mention _add_long and _mul_long, and refer to the - detailed documentation about them (in __add__ / __mul__?) +For addition and multiplication (not for other operators), there is a +fast path for operations with a Python ``int`` (which corresponds +to a C ``long``). Implement ``cdef _add_long(self, long n)`` or +``cdef _mul_long(self, long n)`` with optimized code for ``self + n`` +or ``self * n``. These are assumed to be commutative, so they are also +called for ``n + self`` or ``n * self``. +From Cython code, you can also call ``_add_long`` or ``_mul_long`` +directly. Examples ^^^^^^^^ @@ -231,24 +234,31 @@ Implementing the above features actually takes a bit of magic. Casual callers and implementers can safely ignore it, but here are the details for the curious. -To achieve fast arithmetic, it's critical to have a fast path to call -from Cython the ``_add_`` method of a Cython object. This is achieved -by declaring the ``_add_`` method in the class -:class:`Element`. Remember however that the abstract classes coming -from categories come after :class:`Element` in the Method Resolution -Order (or fake Method Resolution Order in case of a Cython -class). Hence any generic implementation of `_add_` in such an -abstract class would be in principle shadowed by `Element._add_`. - -This is worked around by defining ``Element._add_`` as a ``cdef`` and -not ``cpdef`` method. Let now see what happens upon calling -`x.__add__(y)`` when `x` and `y` are instances of a class that does -not implement `_add_`. In the case of a Python level call, +To achieve fast arithmetic, it is critical to have a fast path in Cython +to call the ``_add_`` method of a Cython object. So we would like +to declare ``_add_`` as a ``cpdef`` method of class :class:`Element`. +Remember however that the abstract classes coming +from categories come after :class:`Element` in the method resolution +order (or fake method resolution order in case of a Cython +class). Hence any generic implementation of ``_add_`` in such an +abstract class would in principle be shadowed by ``Element._add_``. +This is worked around by defining ``Element._add_`` as a ``cdef`` +instead of a ``cpdef`` method. Concrete implementations in subclasses +should be ``cpdef`` or ``def`` methods. + +Let us now see what happens upon evaluating ``x + y`` when ``x`` and ``y`` +are instances of a class that does not implement ``_add_`` but where +``_add_`` is implemented in the category. +First, ``x.__add__(y)`` is called, where ``__add__`` is implemented +in :class:`Element`. +Assuming that ``x`` and ``y`` have the same parent, a Cython call to +``x._add_(y)`` will be done. +The latter is implemented to trigger a Python level call to ``x._add_(y)`` +which will succeed as desired. + +In case that Python code calls ``x._add_(y)`` directly, ``Element._add_`` will be invisible, and the method lookup will -continue down the MRO and find the `_add_` method in the category. In -the case of a Cython level call, `Element._add_` will be called, but -latter is implemented to trigger a Python level call to `_add_` which -will succeed as desired. +continue down the MRO and find the ``_add_`` method in the category. """ #***************************************************************************** From 25c9d7d6effbc50e9aeb0d7e8a7e18d92da30aa2 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Thu, 8 Sep 2016 11:55:18 +0200 Subject: [PATCH 10/10] Add tests for _add_long and _mul_long --- src/sage/structure/element.pyx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index f0f6eabb58c..9f61124bbd3 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -1258,6 +1258,20 @@ cdef class Element(SageObject): cdef _add_long(self, long n): """ Generic path for adding a C long, assumed to commute. + + EXAMPLES:: + + sage: cython( # long time + ....: ''' + ....: from sage.structure.element cimport Element + ....: cdef class MyElement(Element): + ....: cdef _add_long(self, long n): + ....: return n + ....: ''') + sage: e = MyElement(Parent()) # long time + sage: i = int(42) + sage: i + e, e + i # long time + (42, 42) """ return coercion_model.bin_op(self, n, add) @@ -1517,6 +1531,20 @@ cdef class Element(SageObject): cdef _mul_long(self, long n): """ Generic path for multiplying by a C long, assumed to commute. + + EXAMPLES:: + + sage: cython( # long time + ....: ''' + ....: from sage.structure.element cimport Element + ....: cdef class MyElement(Element): + ....: cdef _mul_long(self, long n): + ....: return n + ....: ''') + sage: e = MyElement(Parent()) # long time + sage: i = int(42) + sage: i * e, e * i # long time + (42, 42) """ return coercion_model.bin_op(self, n, mul)