Do we really need Element, RingElement, AdditiveGroupElement, ...?

289 views
Skip to first unread message

Jeroen Demeyer

unread,
Nov 23, 2015, 4:55:40 AM11/23/15
to sage-devel
Sage has a lot of classes like Element, ModuleElement, RingElement,
CommutativeRingElement, ...

I have often wondered if we really need all these classes. What's the
compelling reason that we don't have just Element? All these different
classes are really just frontends to the coercion model implementing
various arithmetic methods. Of course, you could argue that Element
should not have __add__ and __mul__ (since not all elements can be added
or multiplied). But it doesn't really hurt to have them and raise
NotImplementedError (unless I'm missing something).

Unifying all these Element classes will simplify element.pyx a lot for
the better. It would also make it easier to add support for new
methods: for example __floordiv__ still doesn't use the coercion model.


Jeroen.

Simon King

unread,
Nov 23, 2015, 5:46:22 AM11/23/15
to sage-...@googlegroups.com
Hi Jeroen,
I have a ticket that goes into that direction. I think #18756 or #18758
or both. But currently I have no resources to work on them, and at least
one of them needs work.

Best regards,
Simon

Eric Gourgoulhon

unread,
Nov 23, 2015, 8:24:02 AM11/23/15
to sage-devel
Hi,


Le lundi 23 novembre 2015 10:55:40 UTC+1, Jeroen Demeyer a écrit :
 Of course, you could argue that Element
should not have __add__ and __mul__ (since not all elements can be added
or multiplied). But it doesn't really hurt to have them and raise
NotImplementedError (unless I'm missing something).


Maybe a too naive remark: what you propose seems against standard object-oriented programming. Isn't it more clear to have a generic base class Element and implement __add__ and __mul__ only in derived classes, when relevant? Sorry, I don't know the details of the element classes, so you may have good reasons to do this...

Best wishes,

Eric.

Jeroen Demeyer

unread,
Nov 23, 2015, 8:36:21 AM11/23/15
to sage-...@googlegroups.com
On 2015-11-23 14:24, Eric Gourgoulhon wrote:
> Hi,
>
> Le lundi 23 novembre 2015 10:55:40 UTC+1, Jeroen Demeyer a écrit :
>
> Of course, you could argue that Element
> should not have __add__ and __mul__ (since not all elements can be
> added
> or multiplied). But it doesn't really hurt to have them and raise
> NotImplementedError (unless I'm missing something).
>
>
> Maybe a too naive remark: what you propose seems against standard
> object-oriented programming. Isn't it more clear to have a generic base
> class Element and implement __add__ and __mul__ only in derived classes,
> when relevant?

I get your point, but it's not black or white: we need to look at
advantages and disadvantages of having so many classes. One problem with
your "object-oriented programming" comment is that we cannot have
multiple inheritance for these classes, since they are extension types
(a.k.a. cdef classes). So we need to reimplement certain methods several
times. Then there is also some performance overhead of having all these
different classes.

The names are also confusing: isinstance(x, FieldElement) is *not* a
good way to check if x is in a field. We have the category framework for
that.

Jeroen.

David Roe

unread,
Nov 23, 2015, 8:37:57 AM11/23/15
to sage-devel
The reason is that most of what would go in the intermediate classes (RingElement, ModuleElement, etc) should go on the categories instead.  Right now there's a bunch of intermediate classes that don't do much, and in fact encourage bad programming (since someone might test isinstance(RingElement) not knowing that not all ring elements inherit from it).

I agree with flattening the structure and just having Element.  We should also get rid of parent_old.Parent....
David
 

Best wishes,

Eric.

--
You received this message because you are subscribed to the Google Groups "sage-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sage-devel+...@googlegroups.com.
To post to this group, send email to sage-...@googlegroups.com.
Visit this group at http://groups.google.com/group/sage-devel.
For more options, visit https://groups.google.com/d/optout.

Eric Gourgoulhon

unread,
Nov 23, 2015, 9:52:08 AM11/23/15
to sage-devel, roed...@gmail.com
Thanks Jeroen and David for your answer and explanations. It's true that OOP with deep hierarchy is not the only possible approach. For instance, there is also the paradigm of component aggregation, see e.g.
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Maybe the latter is closer (at least in spirit) to the category framework in Sage.

Eric.

Travis Scrimshaw

unread,
Nov 23, 2015, 11:58:36 AM11/23/15
to sage-devel, roed...@gmail.com

Thanks Jeroen and David for your answer and explanations. It's true that OOP with deep hierarchy is not the only possible approach. For instance, there is also the paradigm of component aggregation, see e.g.
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Maybe the latter is closer (at least in spirit) to the category framework in Sage.

I don't quite think so as we still have a hierarchy setup through the category framework, and we are just adding that hierarchy into the class MRO at runtime. Although the axiom approach does have this sort of flavor to it. (BTW, that was an interesting article.)

However, I'm +1 for flatting things down to Element as it would probably solve #15160 and #15947.

Best,
Travis

Simon King

unread,
Nov 26, 2015, 5:05:38 AM11/26/15
to sage-...@googlegroups.com
Hi Eric,

On 2015-11-23, Eric Gourgoulhon <egourg...@gmail.com> wrote:
> Le lundi 23 novembre 2015 10:55:40 UTC+1, Jeroen Demeyer a =C3=A9crit :
>>
>> Of course, you could argue that Element=20
>> should not have __add__ and __mul__ (since not all elements can be added=
>=20
>> or multiplied). But it doesn't really hurt to have them and raise=20
>> NotImplementedError (unless I'm missing something).=20
>>
>>
> Maybe a too naive remark: what you propose seems against standard=20
> object-oriented programming. Isn't it more clear to have a generic base=20
> class Element and implement __add__ and __mul__ only in derived classes,=20
> when relevant? Sorry, I don't know the details of the element classes, so=
> you may have good reasons to do this...

Yes and no.

Yes, you want that addition only works for elements that can be added.

No, because EVERY element (not only those that can be added!) should be
aware of the coercion model.

That's to say, IMHO, there should be __add__ and __mul__ for Element, in
order to bind all elements to the coercion model. And if you want to
implement addition, then you can provide an "_add_" (single, not
double underscore!) method. Or you can define an additive action on the
level of the parent. Likewise for multiplication: Implement _mul_ for
multiplication (say, of two elements that belong to the same ring),
implement _rmul_ for the multiplicative action of the base ring on an algebra,
and implement further multiplicative actions if you need them.

That's perfectly OO!

Indeed, if you do not implement any of the above, then the default
__add__ method will fail with a TypeError. And if you implement any of
the above, then it will propagate to sub-types.

Best regards,
Simon


Simon King

unread,
Nov 26, 2015, 5:10:19 AM11/26/15
to sage-...@googlegroups.com
Hi Travis,

On 2015-11-23, Travis Scrimshaw <tsc...@ucdavis.edu> wrote:
> However, I'm +1 for flatting things down to Element as it would probably
> solve #15160 <http://trac.sagemath.org/ticket/15160> and #15947
><http://trac.sagemath.org/ticket/15947>.

See #18756 or #18758. I think these would solve it eventually.

Simon King

unread,
Nov 26, 2015, 5:15:10 AM11/26/15
to sage-...@googlegroups.com
Hi David,

On 2015-11-23, David Roe <roed...@gmail.com> wrote:
> The reason is that most of what would go in the intermediate classes
> (RingElement, ModuleElement, etc) should go on the categories instead.

Indeed that's the purpose of the tickets that I mentioned. There is some
difference in performance, though. So it still makes sense to hard-code
certain things in Cython classes such as RingElement.

Best regards,
Simon

Jeroen Demeyer

unread,
Nov 26, 2015, 5:18:54 AM11/26/15
to sage-...@googlegroups.com
I don't really see how those tickets are related to getting rid of
classes like RingElement.

Simon King

unread,
Nov 27, 2015, 8:00:04 AM11/27/15
to sage-...@googlegroups.com
Hi Jeroen,

On 2015-11-26, Jeroen Demeyer <jdem...@cage.ugent.be> wrote:
>> On 2015-11-23, Travis Scrimshaw <tsc...@ucdavis.edu> wrote:
>>> However, I'm +1 for flatting things down to Element as it would probably
>>> solve #15160 <http://trac.sagemath.org/ticket/15160> and #15947
>>> <http://trac.sagemath.org/ticket/15947>.
>>
>> See #18756 or #18758. I think these would solve it eventually.
>
> I don't really see how those tickets are related to getting rid of
> classes like RingElement.

Perhaps I recall wrongly what I did in these tickets. Anyway, what I
recall is this:
- Implement the generic coercion framework in Element rather than in
RingElement.
- Make it possible to implement an action via parent methods of a
category.
- Preserve RingElement and friends for backwards compatibility and for
applications for which few nanoseconds slowdown in multiplication
matter.

Best regards,
Simon

Jonas Jermann

unread,
Nov 27, 2015, 6:30:37 PM11/27/15
to sage-...@googlegroups.com
Hi

In one case I was forced to use CommutativeAlgebraElement because I
needed it's operations (e.g. __mul__) even though the category/class
from the parent (module) doesn't support it:

It sometimes makes sense to view (homogeneous) elements of a graded
ring as module elements (because operations might be specific to that
module and not to the whole ring).

On the other hand we still want to be able to multiply those *module*
elements of possibly different homogeneous parts.

So if we inherit from CommutativeAlgebraElement we get the __mul__
operation but we can still treat elements as module elements...

Or what is the "modern" approach/solution for this?
I found my own way of working with graded rings in sage but I also
noticed that the framework doesn't solve all problems and tricks
have to be used...


Best
Jonas

Simon King

unread,
Nov 28, 2015, 4:09:28 AM11/28/15
to sage-...@googlegroups.com
Hi Jonas,

On 2015-11-27, Jonas Jermann <jjer...@gmail.com> wrote:
> It sometimes makes sense to view (homogeneous) elements of a graded
> ring as module elements (because operations might be specific to that
> module and not to the whole ring).
>
> On the other hand we still want to be able to multiply those *module*
> elements of possibly different homogeneous parts.
>
> So if we inherit from CommutativeAlgebraElement we get the __mul__
> operation but we can still treat elements as module elements...

That's what I recommend to do. See the thematic tutorial on coercion and
categories ("How to create new algebraic structures").

> Or what is the "modern" approach/solution for this?

It is possible to do everything based on
sage.combinat.free_module.CombinatorialFreeModule. It is a clean
mathematical approach (for defining something like a multiplication on
a module, it is enough to define what happens on a basis). But it is
pure python code and it uses an amazing amount of indirections. So, I
would recommend against using it for time-critical applications. And
that's part of the motivation for the two tickets that I mentioned before:
I want to make it possible to define a multiplication action in some
Cython file, and let the categoy framework (written in Python) put the
appropriate fast Cython methods into the parent's action cache.

Best regards,
Simon


Travis Scrimshaw

unread,
Nov 28, 2015, 2:49:32 PM11/28/15
to sage-devel

> Or what is the "modern" approach/solution for this?

It is possible to do everything based on
sage.combinat.free_module.CombinatorialFreeModule. It is a clean
mathematical approach (for defining something like a multiplication on
a module, it is enough to define what happens on a basis). But it is
pure python code and it uses an amazing amount of indirections. So, I
would recommend against using it for time-critical applications. And
that's part of the motivation for the two tickets that I mentioned before:
I want to make it possible to define a multiplication action in some
Cython file, and let the categoy framework (written in Python) put the
appropriate fast Cython methods into the parent's action cache.

A lot of the functionality has also been abstracted up to the category ModulesWithBasis. However, I agree that there are a large number of indirections that we might want to simplify, and CombinatorialFreeModule would likely benefit from more cythonization (and common abstraction with sparse free modules).

Best,
Travis

 

Jonas Jermann

unread,
Nov 28, 2015, 3:22:46 PM11/28/15
to sage-...@googlegroups.com
What if it is inconvenient to define multiplication in terms of the
basis (e.g. what if we don't want to work with a basis unless it is
really necessary)?

What if we have infinite dimensional spaces but multiplication can
still be defined (without using a basis)?

Best
Jonas
> --
> You received this message because you are subscribed to the Google
> Groups "sage-devel" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to sage-devel+...@googlegroups.com
> <mailto:sage-devel+...@googlegroups.com>.
> To post to this group, send email to sage-...@googlegroups.com
> <mailto:sage-...@googlegroups.com>.

Simon King

unread,
Nov 28, 2015, 5:19:40 PM11/28/15
to sage-...@googlegroups.com
Hi Jonas,

On 2015-11-28, Jonas Jermann <jjer...@gmail.com> wrote:
> What if it is inconvenient to define multiplication in terms of the
> basis (e.g. what if we don't want to work with a basis unless it is
> really necessary)?
>
> What if we have infinite dimensional spaces but multiplication can
> still be defined (without using a basis)?

Nobody forces you to use CombinatorialFreeModule. The basic scheme for
creating a new algebraic structure is
1. Inherit from Parent and Element (or existing sub-classes), but
NEVER override double-underscore methods such as __add__, __mul__,
__call__, __repr__. As a rule of thumb, what you should implement is
the corresponding single underscore method.
2. A bit less "rule of thumb": In the single underscore methods, you can
rely on certain assumptions.
a) In _add_ and _mul_, it is guaranteed that both self and the other
argument belong to the same parent.
b) There is _rmul_ and _lmul_ for implementing scalar multiplication.
You can assume that the other argument belongs to the base ring of
the parent of self.
c) If there is a default __call__ method (e.g., for morphisms), then
in _call_ you can assume that the argument belongs to the domain of
the morphism.
3. When initialising the parent, at some point (usually indirectly),
Parent.__init__ is called, thereby providing an appropriate category of
the parent. That's enough for implementing the category framework for
the parent.
4. The class that you implemented for the elements of the parent should
be assigned to an attribute called "Element" of the parent (or of its
class).
5. The elements of the parent should NOT be directly created by calling
the class that you created for the elements. Instead, when P is the
parent, you should create the elements by passing the arguments to P
(i.e., you do P(a,b,...)). In particular, this should be taken into
account in your element's class' _add_ and _mul_ methods.

For details, see the thematic tutorial on "coercion and categories".

Best regards,
Simon


Simon King

unread,
Nov 28, 2015, 5:37:40 PM11/28/15
to sage-...@googlegroups.com
PS:

On 2015-11-28, Simon King <simon...@uni-koeln.de> wrote:
> On 2015-11-28, Jonas Jermann <jjer...@gmail.com> wrote:
>> What if it is inconvenient to define multiplication in terms of the
>> basis (e.g. what if we don't want to work with a basis unless it is
>> really necessary)?
>>
>> What if we have infinite dimensional spaces but multiplication can
>> still be defined (without using a basis)?

The basis of a combinatorial free module is not necessarily finite! You
can use infinite families.

Best regards,
Simon

Nicolas M. Thiery

unread,
Jan 21, 2016, 7:50:49 PM1/21/16
to sage-...@googlegroups.com
Hey,

Just a brief followup on this thread.

For the improvements to the libgap interface I am working on this
week, I need _div_ and __div__ to be available for all elements of a
(unital ...) multiplicative structure; not just those inheriting for
RingElement etc. So I added those methods in #19937 [1], in a
consistent way with what we already have for __sub__ and _sub_.

Which means one additional little __div__ method to move around in
case we decide to move all the __xxx__ methods in Element ...

I personally don't have a clear cut opinion about whether to keep the
__xxx__ methods in the categories (and in the specialized classes
XXXElement) or moving them all to Element. I am torn between the
niceness of coercion for all elements and the inconvenient of
namespace pollution.

If that's ok with you, given that #19937 is small and innocuous, that
we need _div_ anyway, and that __div__ is easy to move around later,
I'd rather merge #19937 now, without waiting for a potential move of
the __xxx__ methods.

Cheers,
Nicolas

[1] http://trac.sagemath.org/ticket/19937

--
Nicolas M. Thiéry "Isil" <nth...@users.sf.net>
http://Nicolas.Thiery.name/

Nicolas M. Thiery

unread,
Jan 21, 2016, 8:13:23 PM1/21/16
to sage-...@googlegroups.com
On Fri, Jan 22, 2016 at 12:50:46AM +0000, Nicolas M. Thiery wrote:
> For the improvements to the libgap interface I am working on this
> week

Btw, for the curious:

https://github.com/nthiery/sage-gap-semantic-interface/blob/master/gap_sage.py

Cheers,
Nicolas

Jeroen Demeyer

unread,
Jan 22, 2016, 4:34:12 AM1/22/16
to sage-devel
A related question now that I'm thinking about refactoring:

can we drop the in-place methods like __iadd__? May we assume that no
Element will need in-place methods with coercion?

The problem is when classes want to implement a special __add__ without
coercion (that is legitimate). If those classes do not also implement
__iadd__, stuff breaks because of the __iadd__ implemented in the
coercion model.

Note that classes can still implement in-place methods without coercion
if they want.

Jeroen.
Reply all
Reply to author
Forward
0 new messages