Hi Gonzalo, On 8 Jun., 07:04, Robert Bradshaw <rober...@math.washington.edu> wrote: > > One caveat, though. The standard "Ring" and "Algebra" parents both > > inherit from the ParentWithBase. Am I allowed to mix an element class > > derived from AlgebraElements with a parent class derived from Parent? > > I can't say for sure that there aren't any hidden assumptions, but I > don't think the Parent's class hierarchy constrains the Element's.
I agree with Robert. As much as I understand, ring elements do not do any special assumption on their parents. > > Also, is it my impression or this model seems to require a lot of > > boilerplate if I wanted to implement it fully (with categories, > > functors, etc)? I didn't mention functors, although I love the stuff in sage.categories.pushout :)) The full "boilerplate", if you want to do all of coercion model and category framework, should roughly be as described below. Let MyParent be the parent class and MyElement a class for the elements of a MyParent instance. Let P and E denote instances of MyParent and MyElement, respectively. I start with a summary of the steps, telling how important and how difficult they usually are, IMO: 0. Classes to inherit from: Easy, HAS to be done. 1. "Magic" Python methods: Mainly easy, you just need to write _repr_ instead of __repr__, etc. __cmp__ may be a bit more tricky. 2.a) Category for parents. Mainly easy: Just choose a category and initialise your parent with it. Should/Has to be done. 2.b) Category for elements: Mainly easy. Just do class MyParent(Parent): Element = MyElement def __init__... Should be done, if you use Python or wait until #11115 is merged. You may be required to provide certain element or parent methods, but that depends on the category you chose. 3. Basic coercion. Can range from "nothing to do at all" to "tedious". It is a must-have! 3.a) Conversion: 2.b) may actually be sufficient for it, otherwise implement _element_constructor_ 3.b) Coercion maps: Done with implementing one method, namely _coerce_map_from_. 4. Not-so-basic coercion with functors. Tricky, can be seen as cherry on the cake (so, not necessary but sometimes nice to have). Here are the gory details. 0. Classes to inherit from MyParent should inherit from sage.structure.parent.Parent and MyElement from sage.structure.element.Element, or of course from a sub- class. 1. "Magic" Python methods Do not use __repr__, __add__, __mul__ etc with double underscore, but with single underscore (_repr_, _add_,_mul_). The double underscore methods are inherited, and the generic code should not be overridden since it provides coercion. The case of __cmp__ is a bit more complicated -- please read the comments in the vicinity of sage.structure.parent.Parent.__cmp__. 2. Category framework 2.a) Initialisation of the parent class In MyParent.__init__, you should construct a category (or pass it as an argument to __init__), and you should call Parent.__init__(self,...,category=your_category). That is usually a small amount of work, since probably you will find an existing category that suites your needs --- Sets(), Algebras(basering), Rngs(), Rings(), etc. NOTE: If MyParent is written in Python, then you will find that its instances belong to a class MyParent_with_category -- that is a subclass of MyParent that is automatically obtained by adding stuff from the category framework 2.b) Initialisation of the element class The class MyParent should have an attribute called `Element`, and its value should be MyElement. The background is that the attribute Element is automatically mixed with stuff from the category and turned into a new class available as P.element_class (where P is an instance of MyParent). Note that this only works if MyParent is written in Python --- or you may apply #11115, because then it also works with Cython. 2.c) Required abstract methods Some categories require that you implement certain methods for MyParent or MyElement, such as lift() if your parent is a quotient. 2.d) Make the test suites work You are supposed to provide doc tests of the form TestSuite(P).run(). To make them work, you may need to implement further things, like MyParent._an_element_. The error messages of TestSuite may tell you what is missing. But you may actually be lucky, sometimes there are generic methods that do the job for you. 3. Coercion - the basics 3.a) Element conversion Note that this is CONVERSION. So, the aim is that P(bla) returns an element of P to the given argument bla -- but it is not necessarily the case that there is a coercion map from parent(bla) to P. 3.a).(i) The default It may be enough to do *nothing* at all: You provided MyParent.Element = MyElement in step 2.b), and the default is that P(bla) returns P.element_class(bla), and that will use the initialisation from MyElement like MyElement(P,bla). So, if MyElement.__init__ is able to understand anything convertible to P then you can rely on the default! 3.b).(ii) _element_constructor_ If you want to keep MyElement.__init__ simple, then you should provide MyParent with a method _element_constructor_. It should transform given arguments to something you can use to initialise MyElement. BUT NOTE: You should NOT return MyElement(P,...)!! Instead, return P.element_class(P,...)! 3.c) Coercion maps A coercion of "bla" into P is more than just a conversion P(bla), because in a coercion you must have a structure preserving map from the parent of bla to P. Think of the integers: You can convert 1.0 into ZZ, but there is no coercion from RR (the parent of 1.0) to ZZ. You should provide MyParent with a method _coerce_map_from_. Requirements: If there is a coercion from a parent S to P, then P._coerce_map_from_(S) should either return True or an actual map from S to P. (i) If it returns a map f, then an element e of S is coerced into the element f(e) of P (ii) If it just returns True, then P.coerce_map_from(S) will automatically create a map for you. Coercion of an element e of S into P boils down to calling P._element_constructor_(e). 4. Coercion -- the advanced stuff. This step is needed if you want to do arithmetic with elements from P and elements from S, where neither S coerces into P nor P coerces into S. A typical example is P=ZZ[x] and S=QQ. The result of an arithmetic operation between S and P lives in QQ[x], hence, neither in S nor in P. 4.a) The construction of P It is assumed that you can construct P out of a simpler parent by means of a construction that is supposed to be functorial (e.g., the construction of "forming a polynomial ring with variable x" transforms *any* ring R into a ring R[x], and any map from R to S yields a map from R[x] to S[x]). Then, you should provide MyParent with a method construction(), that returns a pair F,R, where F is a so-called construction functor, and F(R) == P. 4.b) The construction functor F See the examples in sage.categories.pushout. Basically, you need to implement a class MyFunctor inherited from sage.categories.pushout.ConstructionFunctor, and provide it with a method _apply_functor 4.c) Pushout of functors Assume that F1 is an instance of MyFunctor and F2 is any other construction functor, and assume that there is neither a coercion from F1(R) to F2(R) nor from F2(R) to F1(R). Assume further that you can think of a "canonical" parent S that can be obtained from R, such that both F1(R) and F2(R) coerce into S. Then, F1.pushout(F2) should return a functor F3 such that S==F3(R). Example from above: F1 is the fraction field constructor, F2 is the polynomial ring constructor with variable x, and R is ZZ. Then, F1(R)==QQ and F2(R)==ZZ[x] . You want S = QQ[x], hence, F3 = F2(F1), such that S==F3(R)=F2(F1(R)). Fortunately, this can be achieved very easily, using the default method pushout() of ConstructionFunctor. You just provide MyFunctor with an attribute "rank". If F1.rank is smaller than F2.rank then F3 will be "first F1, then F2". Example: sage: R = ZZ sage: F1 = QQ.construction()[0] sage: F2 = R['x'].construction()[0] sage: F1 FractionField sage: F2 Poly[x] sage: F1.rank 5 sage: F2.rank 9 sage: F1.pushout(F2) Poly[x](FractionField(...)) sage: F2.pushout(F1) Poly[x](FractionField(...)) 4.d) Merging functors This is when functors F1 and F2 are of the same rank. It may be that your parent comes in different implementations. For example, ZZ[x] can be in a dense or sparse implementation, based on NTL or FLINT. The construction functor should know about these details. And then, MyFunctor should be provided with a method merge() with the following properties: If F1 and F2 are two construction functors of the same rank, such that F1(R) and F2(R) are isomorphic objects in different implementation for ANY R, then F1.merge(F2) should return a functor F3 such that F3(R) is isomophic to F1(R) and F2(R) and there is a coercion from both F1(R) and F2(R) to F3(R). Otherwise, None should be returned. Example: If F1 returns dense 3x3 matrix spaces and F2 returns sparse 3x3 matrix spaces, and you decide that dense is the default, then F1.merge(F2) should return F1. sage: MD = MatrixSpace(QQ,3,3,sparse=False) sage: MS = MatrixSpace(QQ,3,3,sparse=True) sage: FD,_=MD.construction() sage: FS,_=MS.construction() sage: FD.rank 10 sage: FS.rank 10 sage: FD.is_sparse False sage: FS.is_sparse True sage: FD.merge(FS).is_sparse False sage: FS.merge(FD).is_sparse False So, if you want the full programme then it's much to do. But often, well-chosen parts of it are sufficient. Best regards, Simon -- To post to this group, send an email to sage-devel@googlegroups.com To unsubscribe from this group, send an email to sage-devel+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/sage-devel URL: http://www.sagemath.org