A morphism is a structure preserving map between two parents. Sage supports the creation of morphisms.
In Sage, the typical way you specify a morphism from a "Parent with Generators" (which is most parents), is to give the images of the generators. However, one can define morphisms using whatever method you want, and apply them however you want ("im_gens" is just what the data is called).
{{{id=3| /// }}}Recall our example from last time, in which we created a new parent data type GoldenIntegers. Suppose we would like the ring of golden integers to be viewed naturally as embedded in some other specific ring, from the point of view of the coercion model. Without modifying Sage itself, the only way to do this is to learn about morphisms and:
We will explain all of these approaches below.
{{{id=42| /// }}}Here's our code from last time. Note that I implemented _mul_ this time.
{{{id=1| class GoldenIntegers(Ring): def __init__(self): Ring.__init__(self, base=ZZ) def _repr_(self): return "The Golden Ring" def gen(self, i=0): if i == 0: return GoldenElement(self, 0, 1) else: raise IndexError def ngens(self): return 1 def _element_constructor_(self, x): if isinstance(x, GoldenElement): return x return GoldenElement(self, ZZ(x), ZZ(0)) def _coerce_map_from_(self, S): print "_coerce_map_from_(%s, %s)"%(self, S) if S is ZZ: return True if S == Integers(13): return True class GoldenElement(RingElement): def __init__(self, parent, a, b): RingElement.__init__(self, parent) # sets self._parent to parent self._a = a self._b = b def _repr_(self): return "%s + %s*(1+sqrt(5))/2"%(self._a, self._b) def _add_(left, right): return GoldenElement(left.parent(), left._a+right._a, left._b+right._b) def _sub_(left, right): return GoldenElement(left.parent(), left._a-right._a, left._b-right._b) def _mul_(left, right): (a,b,c,d) = (left._a,left._b,right._a,right._b) return GoldenElement(left.parent(), a*c + b*d, b*c + a*d + b*d) /// }}} {{{id=6| R = GoldenIntegers(); R /// The Golden Ring }}}Let's try the second method, of registering a new coercion. First we create the homset of homomorphisms from R to RDF (=real double precision field). In Sage, homsets are normal parent objects, just like any other parents.
{{{id=7| H = Hom(R, RDF); H /// Set of Homomorphisms from The Golden Ring to Real Double Field }}} {{{id=12| gamma_in_RDF = RDF( (1+sqrt(5))/2 ); gamma_in_RDF /// 1.61803398875 }}}A homomorphism is simply specified by giving the image of the generators.
{{{id=8| phi = H([gamma_in_RDF]); phi /// Traceback (most recent call last): File "Checking with %debug on the console, we find that this is because:
ipdb> morphism.RingHomomorphism_im_gens(self, im_gens, check=check)
*** NotImplementedError: Verification of correctness of homomorphisms from The Golden Ring not yet implemented.
This is a message that is produced in parent.pyx because we didn't define the method _is_valid_homomorphism_ yet. Let's do it:
{{{id=18| R._is_valid_homomorphism_? ///File: /Users/wstein/sage/install/sage-4.6/devel/sage/sage/structure/parent.pyx
Type: <type ‘builtin_function_or_method’>
Definition: R._is_valid_homomorphism_(codomain, im_gens)
Docstring:
Return True if im_gens defines a valid homomorphism from self to codomain; otherwise return False.
If determining whether or not a homomorphism is valid has not been implemented for this ring, then a NotImplementedError exception is raised.
It works!
{{{id=10| R = GoldenIntegers(); H = Hom(R, RDF); phi = H([ gamma_in_RDF ]); phi /// Ring morphism: From: The Golden Ring To: Real Double Field Defn: 0 + 1*(1+sqrt(5))/2 |--> 1.61803398875 }}}But we can't use it yet.
{{{id=22| gamma = R.0; phi(gamma) /// Traceback (most recent call last): File "Again, looking at the above traceback and possibly consulting the source code of Sage, we find that there is another method that we have to define: _im_gens_
{{{id=21| gamma._im_gens_? ///File: /Users/wstein/sage/install/sage-4.6/devel/sage/sage/structure/element.pyx
Type: <type 'builtin_function_or_method'>
Definition: gamma._im_gens_(codomain, im_gens)
Docstring:
Return the image of self in codomain under the map that sends the images of the generators of the parent of self to the tuple of elements of im_gens.
No problem:
{{{id=11| class GoldenElement(RingElement): def __init__(self, parent, a, b): RingElement.__init__(self, parent) # sets self._parent to parent self._a = a self._b = b def _im_gens_(self, codomain, im_gens): return codomain(self._a) + codomain(self._b)*im_gens[0] def _repr_(self): return "%s + %s*(1+sqrt(5))/2"%(self._a, self._b) def _add_(left, right): return GoldenElement(left.parent(), left._a+right._a, left._b+right._b) def _sub_(left, right): return GoldenElement(left.parent(), left._a-right._a, left._b-right._b) def _mul_(left, right): (a,b,c,d) = (left._a,left._b,right._a,right._b) return GoldenElement(left.parent(), a*c + b*d, b*c + a*d + b*d) /// }}} {{{id=26| R = GoldenIntegers(); H = Hom(R, RDF); phi = H([ gamma_in_RDF ]) gamma = R.0; phi(gamma) /// 1.61803398875 }}} {{{id=27| phi(3 + gamma) /// _coerce_map_from_(The Golden Ring, Integer Ring) 4.61803398875 }}} {{{id=28| 2*gamma /// 0 + 2*(1+sqrt(5))/2 }}} {{{id=29| phi(7 + 2*gamma) /// 10.2360679775 }}}So finally, we have a working morphism. Let's use it to register a new coercion!
{{{id=30| gamma + RDF(2.3) /// _coerce_map_from_(The Golden Ring, Real Double Field) _coerce_map_from_(The Golden Ring, Rational Field) Traceback (most recent call last): File "What went wrong? The problem is that Sage already started automatically deciding on coercions, and once that happpens, for efficiency and consistency reasons you are not allowed to register further coercions. The solution is to register the coercion map right when you make R.
{{{id=33| R = GoldenIntegers(); H = Hom(R, RDF); phi = H([ gamma_in_RDF ]) phi.register_as_coercion() /// }}}Now, let's try it out:
{{{id=34| gamma = R.0 gamma + RDF(2.5) /// _coerce_map_from_(The Golden Ring, Real Double Field) 4.11803398875 }}}Nice! Notice that the coercion ended up going into the real double field.
{{{id=40| /// }}}In order to create a working morphism from instances of our GoldenRing class to some other ring $S$, we did the following:
We also learned about register_as_coercion, which must be called immediately after you make your parent. Using that we can inform Sage that it should add some morphisms to the coercion model.
{{{id=44| /// }}}Finally, there is also the embedding= option. We abuse it below. (I say "abuse", since in fact the golden ring doesn't really embed into RDF, since RDF is a finite set, due to rounding.)
{{{id=38| class GoldenIntegers(Ring): def __init__(self): Ring.__init__(self, base=ZZ) phi = self.hom([RDF( (1+sqrt(5))/2 )]) self._populate_coercion_lists_(embedding=phi) def _is_valid_homomorphism_(self, codomain, im_gens): if len(im_gens) != 1: return False if im_gens[0]**2 - im_gens[0] - 1 == 0: return True return False def _repr_(self): return "The Golden Ring" def gen(self, i=0): if i == 0: return GoldenElement(self, 0, 1) else: raise IndexError def ngens(self): return 1 def _element_constructor_(self, x): if isinstance(x, GoldenElement): return x return GoldenElement(self, ZZ(x), ZZ(0)) def _coerce_map_from_(self, S): print "_coerce_map_from_(%s, %s)"%(self, S) if S is ZZ: return True if S == Integers(13): return True /// }}} {{{id=45| R = GoldenIntegers(); gamma = R.0 gamma + RDF(2.3) /// _coerce_map_from_(The Golden Ring, Real Double Field) 3.91803398875 }}}Since RDF embeds into the symbolic ring, the following automatically works.
{{{id=48| gamma + sqrt(2) /// _coerce_map_from_(The Golden Ring, Symbolic Ring) sqrt(2) + 1.61803398875 }}}However, it seems a bit weird to turn (1+sqrt(5))/2 into the number 1.61803398875, just to put it somewhere where we could represent (1+sqrt(5))/2 exactly! Let's try embedding into SR, instead.
{{{id=46| class GoldenIntegers(Ring): def __init__(self): Ring.__init__(self, base=ZZ) phi = self.hom([( 1+sqrt(5))/2 ]) self._populate_coercion_lists_(embedding=phi) def _is_valid_homomorphism_(self, codomain, im_gens): if len(im_gens) != 1: return False if im_gens[0]**2 - im_gens[0] - 1 == 0: return True return False def _repr_(self): return "The Golden Ring" def gen(self, i=0): if i == 0: return GoldenElement(self, 0, 1) else: raise IndexError def ngens(self): return 1 def _element_constructor_(self, x): if isinstance(x, GoldenElement): return x return GoldenElement(self, ZZ(x), ZZ(0)) def _coerce_map_from_(self, S): print "_coerce_map_from_(%s, %s)"%(self, S) if S is ZZ: return True if S == Integers(13): return True /// }}} {{{id=51| R = GoldenIntegers(); gamma = R.0 gamma + sqrt(2) /// _coerce_map_from_(The Golden Ring, Symbolic Ring) sqrt(2) + 1/2*sqrt(5) + 1/2 }}}But now the following won't work, because there isn't a canonical map in any direction.
{{{id=52| gamma + RDF(2.3) /// _coerce_map_from_(The Golden Ring, Real Double Field) _coerce_map_from_(The Golden Ring, Symbolic Ring) _coerce_map_from_(The Golden Ring, The Golden Ring) _coerce_map_from_(The Golden Ring, Integer Ring) _coerce_map_from_(The Golden Ring, Rational Field) Traceback (most recent call last): File "There is a canonical coercion from the golden ring to the symbolic ring, and one from RDF to the symbolic ring, but the coercion system doesn't know how to figure out that this is the best place to map things to. (And I don't know how to tell it to do so...)
{{{id=58| /// }}}There is something called the _convert_method_name, which allows you to add a method to your elements, to make it so certain rings automatically can coerce elements in. For example, below we figure out what the method name is for arbitrary precision floating point numbers, then define it for our GoldenElement class. Now there is automatically a coercion map to arbitrary precision reals. This is similar to the builtin Python methods like __float__.
{{{id=57| RR._convert_method_name /// '_mpfr_' }}} {{{id=53| class GoldenElement(RingElement): def __init__(self, parent, a, b): RingElement.__init__(self, parent) # sets self._parent to parent self._a = a self._b = b def _mpfr_(self, F): return self._a + self._b * (1+F(5).sqrt())/2 def __float__(self): return float(self._a) + float(self._b) + float( (1+sqrt(5))/2 ) def _im_gens_(self, codomain, im_gens): return codomain(self._a) + codomain(self._b)*im_gens[0] def _repr_(self): return "%s + %s*(1+sqrt(5))/2"%(self._a, self._b) def _add_(left, right): return GoldenElement(left.parent(), left._a+right._a, left._b+right._b) def _sub_(left, right): return GoldenElement(left.parent(), left._a-right._a, left._b-right._b) def _mul_(left, right): (a,b,c,d) = (left._a,left._b,right._a,right._b) return GoldenElement(left.parent(), a*c + b*d, b*c + a*d + b*d) /// }}} {{{id=55| R = GoldenIntegers() x = 2*R.0 + 5 RealField(200)(x) /// _coerce_map_from_(The Golden Ring, Integer Ring) _coerce_map_from_(The Golden Ring, Real Field with 200 bits of precision) 8.2360679774997896964091736687312762354406183596115257242709 }}} {{{id=69| float(x) /// 8.6180339887498949 }}} {{{id=64| RealField(300)(x) /// _coerce_map_from_(The Golden Ring, Real Field with 300 bits of precision) 8.23606797749978969640917366873127623544061835961152572427089724541052092563780489941441441 }}} {{{id=65| RR(x) /// _coerce_map_from_(The Golden Ring, Real Field with 53 bits of precision) 8.23606797749979 }}} {{{id=67| /// }}} {{{id=71| /// }}} {{{id=70| /// }}} {{{id=68| /// }}}