What is coercion all about?
The primary goal of the coercion is to be able to transparently do arithmetic, comparisons, etc. between elements of distinct sets.
As a concrete example, when one writes 1 + 1/2 one wants to perform arithmetic on the operands as rational numbers, despite the left being an integer. This makes sense given the obvious and natural inclusion of the integers into the rational numbers. The goal of the coercion system is to facilitate this (and more complicated arithmetic) without having to explicitly map everything over into the same domain, and at the same time being strict enough to not resolve ambiguity or accept nonsense. Here are some examples
sage: 1 + 1/2 3/2 sage: R.<x,y> = ZZ sage: R Multivariate Polynomial Ring in x, y over Integer Ring sage: parent(x) Multivariate Polynomial Ring in x, y over Integer Ring sage: parent(1/3) Rational Field sage: x+1/3 x + 1/3 sage: parent(x+1/3) Multivariate Polynomial Ring in x, y over Rational Field Assume we want to do $a + b$ with $a \in R$ and $b \in S$ sage: GF(5)(1) + CC(I) Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for '+': 'Finite Field of size 5' and 'Complex Field with 53 bits of precision'
Parents and Elements
Parents objects in concrete categories, and Elements are their members. Parents are first-class objects. Most things Sage either are parents or have a parent. Whenever one sees the word Parent one can think Set. Here are some examples:
sage: parent(1) Integer Ring sage: parent(1) is ZZ True sage: ZZ Integer Ring sage: parent(1.50000000000000000000000000000000000) Real Field with 123 bits of precision sage: parent(x) Symbolic Ring sage: x^sin(x) x^sin(x) sage: R.<t> = Qp(5) sage: f = t^3-5; f (1 + O(5^20))*t^3 + (4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + 4*5^10 + 4*5^11 + 4*5^12 + 4*5^13 + 4*5^14 + 4*5^15 + 4*5^16 + 4*5^17 + 4*5^18 + 4*5^19 + 4*5^20 + O(5^21)) sage: parent(f) Univariate Polynomial Ring in t over 5-adic Field with capped relative precision 20 sage: f = EllipticCurve('37a').lseries().taylor_series(10); f 0.997997869801216 + 0.00140712894524925*z - 0.000498127610960097*z^2 + 0.000118835596665956*z^3 - 0.0000215906522442707*z^4 + (3.20363155418419e-6)*z^5 + O(z^6) sage: parent(f) Power Series Ring in z over Complex Field with 53 bits of precision
There is an important distinction between Parents and types
sage: a = GF(5).random_element() sage: b = GF(7).random_element() sage: type(a) <type 'sage.rings.integer_mod.IntegerMod_int'> sage: type(b) <type 'sage.rings.integer_mod.IntegerMod_int'> sage: type(a) == type(b) True sage: parent(a) Finite Field of size 5 sage: parent(a) == parent(b) False
However, non-sage objects don't really have parents, but we still want to be able to reason with them, so their type is used instead.
sage: a = int(10) sage: parent(a) <type 'int'>
In fact, under the hood, a special parent "The set of all Python objects of type T" is used in these cases.
Note that parents are not always as tight as possible.
sage: parent(1/2) Rational Field sage: parent(2/1) Rational Field
Maps between Parents
Many parents come with maps to and from other parents.
Sage makes a distinction between being able to convert between various parents, and coerce between them. Conversion is explicit and tries to make sense of an object in the target domain if at all possible. It is invoked by calling
sage: ZZ(5) 5 sage: ZZ(10/5) 2 sage: QQ(10) 10 sage: parent(QQ(10)) Rational Field sage: a = GF(5)(2); a 2 sage: parent(a) Finite Field of size 5 sage: parent(ZZ(a)) Integer Ring
Conversions need not be canonical (they may for example involve a choice of lift) or even make sense mathematically (e.g. constructions of some kind).
sage: ZZ("123") 123 sage: ZZ['x']([4,3,2,1]) x^3 + 2*x^2 + 3*x + 4 sage: a = Qp(5, 10)(1/3); a 2 + 3*5 + 5^2 + 3*5^3 + 5^4 + 3*5^5 + 5^6 + 3*5^7 + 5^8 + 3*5^9 + O(5^10) sage: ZZ(a) 6510417
On the other hand, Sage has the notion of a coercion which is a canonical morphism (occasionally up to a conventional choice made by developers) between parents. A coercion from one parent to another must be defined on the whole domain, and always succeeds. As it may be invoked implicitly, is should be obvious and natural (in both the mathematically rigorous and colloquial sense of the word). Up to inescapable rounding issues that arise with inexact representations, these coercion morphisms should all commute.
They is can be discovered via the has_coerce_map_from method, and if needed explicitly invoked with the coerce method.
sage: QQ.has_coerce_map_from(ZZ) True sage: QQ.has_coerce_map_from(RR) False sage: ZZ['x'].has_coerce_map_from(QQ) False sage: ZZ['x'].has_coerce_map_from(ZZ) True sage: ZZ['x'].coerce(5) 5 sage: ZZ['x'].coerce(5).parent() Univariate Polynomial Ring in x over Integer Ring sage: ZZ['x'].coerce(5/1) Traceback (most recent call last): ... TypeError: no cannonical coercion from Rational Field to Univariate Polynomial Ring in x over Integer Ring
Suppose we want to add two element, a and b, whose parents are R and S respectively. When we type a+b then
If R is S, call a._add_(b)
If there is a coercion \phi: S \rightarrow R, call a._add_(\phi(b))
If there is a coercion \phi: R \rightarrow S, call \phi(a)._add_(b)
Look for T such that there is a coercion \phi_S: S \rightarrow Z and \phi_R: R \rightarrow Z, call \phi(a)._add_(\phi(b))
sage: parent(1 + 1/2) Rational Field sage: cm = sage.structure.element.get_coercion_model() sage: cm.bin_op(77, 9, gcd) 1 sage: cm.explain(ZZ, QQ) Coercion on left operand via Natural morphism: From: Integer Ring To: Rational Field Arithmetic performed after coercions. Result lives in Rational Field Rational Field sage: cm.explain(ZZ['x','y'], QQ['x']) Coercion on left operand via Call morphism: From: Multivariate Polynomial Ring in x, y over Integer Ring To: Multivariate Polynomial Ring in x, y over Rational Field Coercion on right operand via Call morphism: From: Univariate Polynomial Ring in x over Rational Field To: Multivariate Polynomial Ring in x, y over Rational Field Arithmetic performed after coercions. Result lives in Multivariate Polynomial Ring in x, y over Rational Field Multivariate Polynomial Ring in x, y over Rational Field
There are also actions.
sage: cm.explain(ZZ['x'], ZZ, operator.mul) Action discovered. Right scalar multiplication by Integer Ring on Univariate Polynomial Ring in x over Integer Ring Result lives in Univariate Polynomial Ring in x over Integer Ring Univariate Polynomial Ring in x over Integer Ring sage: cm.explain(ZZ['x'], ZZ, operator.div) Action discovered. Right inverse action by Rational Field on Univariate Polynomial Ring in x over Integer Ring with precomposition on right by Natural morphism: From: Integer Ring To: Rational Field Result lives in Univariate Polynomial Ring in x over Rational Field Univariate Polynomial Ring in x over Rational Field sage: f = QQ.coerce_map_from(ZZ) sage: f(3).parent() Rational Field sage: QQ.coerce_map_from(int) Native morphism: From: Set of Python objects of type 'int' To: Rational Field sage: QQ.has_coerce_map_from(RR) False sage: QQ['x'].get_action(QQ) Right scalar multiplication by Rational Field on Univariate Polynomial Ring in x over Rational Field sage: (QQ^2).get_action(QQ) Right scalar multiplication by Rational Field on Vector space of dimension 2 over Rational Field sage: QQ['x'].get_action(RR) Right scalar multiplication by Real Field with 53 bits of precision on Univariate Polynomial Ring in x over Rational Field
How to Implement
Special methods to implement:
Arithmetic on elements_add_, _sub_, _mul_, _div_
This is where the binary arithmetic operators should be implemented. Unlike Python's __add__, both operands are guaranteed to have the same Parent at this point.
Coercion for Parents _coerce_map_from_
Given two parents R and S, R._coerce_map_from_(S) is called to determine if there is a coercion \phi: S \rightarrow R. Note that the function is called on the potential codomain. To indicate that there is no coercion from S to R (self), return False or None. This is the default behavior. If there is a coercion, return True (in which case an morphism using R._element_constructor_ will be created) or an actual Morphism object with S as the domain and R as the codomain.
There is a utility function Parent._coerce_map_via which makes it easy to specify coercions to self via a list of basecases.
Actions for Parents _get_action_ or _rmul_, _lmul_, _r_action_, _l_action_
Suppose one wants R to act on S. Some examples of this could be R = \QQ, S = \QQ[x] or R = Gal(S/\QQ) where S is a number field. There are several ways to implement this:
If R is the base of S (as in the first example), simply implement _rmul_ and/or _lmul_ on the Elements of S.
In this case r * s gets handled as s._rmul_(r) and s * r as s._lmul_(r). The argument to _rmul_ and _lmul_ are guaranteed to be Elements of the base of S (with coercion happening beforehand if necessary).
If R acts on S, one can alternatively define the methods _r_action_ and/or _l_action_ on the Elements of R.
There is no constraint on the type or parents of objects passed to these methods, raise a TypeError or ValueError if the wrong kind of object is passed in to indicate the action is not appropriate here.
If R acts on S or S acts on R, one may override R._get_action_ to return an actual Action object to be used.
- This is how non-multiplicative actions must be implemented, and is the most powerful (and completed) way to do things.
Element conversion/construction for Parents _element_constructor_ not __call__
The Parent.__call__ method dispatches to _element_constructor_. When someone writes R(x, foo=y), this is the method that eventually gets called in most cases. See the documentation on the method below.
Parents may also call self._populate_coercion_lists_ method in their __init__ functions to pass any callable for use instead of _element_constructor_, provide a list of Parents with coercions to self (as an alternative to implementing _coerce_map_from_), provide special construction methods (like _integer_ for ZZ), etc. There is extensive documentation in the docstring of that method.
What is provided
Let x be in R, and consider S(x). First, a coercion R -> S is searched for. Then a coercion S -> R is looked for (and the inverse attempted). Last of all _element_constructor_ is called.
coerce_map_from, convert_map_from (returns an actual morphism), has_coerce_map_from (bool)
Discovering new parents
New parents are discovered using a heuristic algorithm in sage/category/pushout.py.
sage: CC.construction() (AlgebraicClosureFunctor, Real Field with 53 bits of precision) sage: RR.construction() (CompletionFunctor, Rational Field) sage: Qp(5).construction() (CompletionFunctor, Rational Field) sage: c, R = RR.construction() sage: QQ.completion(5, 100) 5-adic Field with capped relative precision 100 sage: a = CC.construction() sage: a.commutes(c) False sage: RR == c(QQ) True sage: QQ.construction() (FractionField, Integer Ring) sage: ZZ.construction()