Best Practices for extending functionality of a sage class

69 views
Skip to first unread message

Dinakar Muthiah

unread,
Jun 5, 2014, 12:18:55 PM6/5/14
to sage-s...@googlegroups.com
Hello,

I want to know the best practices for extending the functionality of a sage class. For example, I would like to add the following method to the Partition class in sage:

#\lambda^(i) from Carrell-Goulding paper
def i_part(self,i):
    if i==0:
        return self
    elif i<0:
        return (self.conjugate().i_part(-i)).conjugate()
    zero_one_sequence = self.zero_one_sequence()
    num_ones = zero_one_sequence.count(1)
    if num_ones < i:
        zero_one_sequence += (i-num_ones)*[1]
    index_one_list = [index for index in range(len(zero_one_sequence)) if zero_one_sequence[index] == 1]
    zero_one_sequence[index_one_list[i-1]]=0
    return Partitions().from_zero_one(zero_one_sequence)


My first inclination is to subclass Partition and add this as a method to the subclass. However, I am not able to make this work.

So what I have done so far is to just include the above function definition in a module followed by the following line:

Partition.i_part = i_part

So I am dynamically changing the Partition class. This doesn't seem like a good practice in general, so I would like to know what is the preferred solution.

Dinakar

kcrisman

unread,
Jun 5, 2014, 1:11:36 PM6/5/14
to sage-s...@googlegroups.com

I want to know the best practices for extending the functionality of a sage class. For example, I would like to add the following method to the Partition class in sage:

#\lambda^(i) from Carrell-Goulding paper
def i_part(self,i):
    if i==0:
        return self
    elif i<0:
        return (self.conjugate().i_part(-i)).conjugate()
    zero_one_sequence = self.zero_one_sequence()
    num_ones = zero_one_sequence.count(1)
    if num_ones < i:
        zero_one_sequence += (i-num_ones)*[1]
    index_one_list = [index for index in range(len(zero_one_sequence)) if zero_one_sequence[index] == 1]
    zero_one_sequence[index_one_list[i-1]]=0
    return Partitions().from_zero_one(zero_one_sequence)


My first inclination is to subclass Partition and add this as a method to the subclass. However, I am not able to make this work.


What exactly did you write?  A new initialize (__init__) would be a good first step.

Dinakar Muthiah

unread,
Jun 5, 2014, 5:50:22 PM6/5/14
to sage-s...@googlegroups.com
I literally wrote the following at the top of my module:

#\lambda^(i) from Carrell-Goulding paper
def i_part(self,i):
    if i==0:
        return self
    elif i<0:
        return (self.conjugate().i_part(-i)).conjugate()
    zero_one_sequence = self.zero_one_sequence()
    num_ones = zero_one_sequence.count(1)
    if num_ones < i:
        zero_one_sequence += (i-num_ones)*[1]
    index_one_list = [index for index in range(len(zero_one_sequence)) if zero_one_sequence[index] == 1]
    zero_one_sequence[index_one_list[i-1]]=0
    return Partitions().from_zero_one(zero_one_sequence)

Partition.i_part = i_part

Then if later I wrote:

p = Partition([3,2,1])

I can call 

p.i_part(2)


This works. I just want to know whether there is a better or more standard solution.

When you say a new __init__ would be good, how do you mean to implement that?

Dinakar

Andrew

unread,
Jun 5, 2014, 11:55:57 PM6/5/14
to sage-s...@googlegroups.com
Hi Dinakur,

The best way of doing this would be to open a track ticket, edit partition.py, add your method to the Partition class and then submit this for review so that it can be included in a future release of sage. If you decide to go this route then you would need to add some documentation and some tests. I have not come across this definition before but if you think that it would be useful for others then I recommend doing this -- I could help you put it together. If you do want to submit this to sage then I'd  recommend changing the name to something more meaningful like CarrellGoulding_partition.

If you don't want to go to the effort of incorporating this method into sage then here is a hack that I use occasionally, although I should add that python purists will probably disapprove of this (and perhaps vocally, in response to my post!). First define the following function somewhere:


def add_method_to_class(klass):
  r
"""
  Add a new method to a pre-existing class.
  """

 
def add_func(func):
    setattr
(klass,func.func_name,func)
   
return func
 
return add_func

Once you have this then you can write:

#\lambda^(i) from Carrell-Goulding paper
@add_method_to_class(Partition)

def i_part(self,i):
   
if i==0:
       
return self
   
elif i<0:
       
return (self.conjugate().i_part(-i)).conjugate()
    zero_one_sequence
= self.zero_one_sequence()
    num_ones
= zero_one_sequence.count(1)
   
if num_ones < i:
        zero_one_sequence
+= (i-num_ones)*[1]
    index_one_list
= [index for index in range(len(zero_one_sequence)) if zero_one_sequence[index] == 1]
    zero_one_sequence
[index_one_list[i-1]]=0
   
return Partitions().from_zero_one(zero_one_sequence)

Once you attach this file to your sage session your new method will be attached to any partition:

sage: mu=Partition([4,3,1])
sage
: mu.i_part(0)
[4, 3, 1]
sage
: mu.i_part(1)
[3, 2]
sage
: mu.i_part(2)
[3, 2, 1, 1]
sage
: mu.i_part(3)
[3, 2, 2, 1]
sage
: mu.i_part(-3)
[5, 4]


Andrew

Nils Bruin

unread,
Jun 6, 2014, 3:07:17 AM6/6/14
to sage-s...@googlegroups.com
On Thursday, June 5, 2014 2:50:22 PM UTC-7, Dinakar Muthiah wrote:
Partition.i_part = i_part

Then if later I wrote:

p = Partition([3,2,1])

I can call 

p.i_part(2)

That works. Of course, without the "monkey-patching" (changing code on a class after its original definition), you could also just write

i_part(p,2)

which is the same number of characters and works without modifying global objects that may be used elsewhere. The great thing about python is that you don't have to be hung up on implementing everything as a method. Often, plain old functions are a much cleaner and more concise way of expressing your computations.

Of course, as pointed out, if you think the functionality warrants wider adoption, you should create a ticket and provide a code change proposal. For incorporation into sage proper, the function would almost certainly needs to be placed as a method somewhere, to get it out of the way of naming conflicts. For your own code projects you probably don't have to bother.

Dinakar Muthiah

unread,
Jun 6, 2014, 10:13:50 AM6/6/14
to sage-s...@googlegroups.com
Ideally, I would like to define a subclass of Partition called MyPartition and include all my custom methods. I think this is a standard way to extend libraries, but for some reason this doesn't work at all. Is there a solution that is more in the spirit of subclassing?

Nils Bruin

unread,
Jun 6, 2014, 10:23:41 PM6/6/14
to sage-s...@googlegroups.com
On Friday, June 6, 2014 7:13:50 AM UTC-7, Dinakar Muthiah wrote:
Ideally, I would like to define a subclass of Partition called MyPartition and include all my custom methods. I think this is a standard way to extend libraries, but for some reason this doesn't work at all. Is there a solution that is more in the spirit of subclassing?

That tends to be a rather heavy-weight solution, which you should probably only undertake if you really want to store additional data  on the objects themselves. One reason is that any routine you inherit from the superclass that produces partitions itself will not take efforts to return an instance of your subclass but will construct an instance of the original class (it can't, even if it has access to the subclass via "type(self)": instantiating new instances of the subclass may need additional data that the superclass isn't aware of!). You'd end up wrapping all those routines if you want your new instances to persist through operations. That's probably only a good design if you have good arguments why your MyPartition is a different kind of object than Partition.

Using normal functions or monkey-patching them in as methods (which is only acceptable in your own private code) is going to be the easiest solution. Or edit partition.py and include your code there. Unless you get your modification accepted upstream that's going to be a rather painful solution wrt. upgrading and portability, though.

Andrew

unread,
Jun 6, 2014, 10:40:05 PM6/6/14
to sage-s...@googlegroups.com
On Saturday, 7 June 2014 00:13:50 UTC+10, Dinakar Muthiah wrote:
Ideally, I would like to define a subclass of Partition called MyPartition and include all my custom methods. I think this is a standard way to extend libraries, but for some reason this doesn't work at all. Is there a solution that is more in the spirit of subclassing?

On Saturday, 7 June 2014 00:13:50 UTC+10, Dinakar Muthiah wrote:
Ideally, I would like to define a subclass of Partition called MyPartition and include all my custom methods. I think this is a standard way to extend libraries, but for some reason this doesn't work at all. Is there a solution that is more in the spirit of subclassing?

The problem is that a partition in sage is not an isolated object, rather, it is the element class of Partitions. If you want to subclass Partition then you also need to create a parent.  Also, as Nils says below you are likely to run into problems because any (most?) method(s) of MyPartition that returns a partition will return a Partition rather than a MyPartition. This said, if you really want to do this then you need something like the following:

from sage.structure.element import Element
from sage.structure.global_options import GlobalOptions
from sage.combinat.partition import PartitionOptions

class MyPartition(Partition):
   
@staticmethod
   
def __classcall_private__(cls, mu=None, **keyword):
       
return MyPartitions()(super(MyPartition,cls).__classcall_private__(cls,mu,**keyword))

   
def fred(self):
       
return "fred"

class MyPartitions(Partitions):
   
Element=MyPartition
    global_options
=PartitionOptions

Quite likely there will be more tweaks to get this to work fully, but it certainly gets things going:
%attach mypartiiton.py
sage
: MyPartition([3,2])
[3, 2]
sage
: mu=MyPartition([3,2]).cells()
sage
: mu=MyPartition([3,2]).fred()






 

Andrew

unread,
Jun 6, 2014, 10:43:27 PM6/6/14
to sage-s...@googlegroups.com
Oops, cut and past the wrong bits at the end - and google's syntax highlighting was going haywire:

sage: MyPartition([3,2])
[3, 2]

sage
: MyPartition([3,2]).cells()
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
sage
: MyPartition([3,2]).fred()
'fred'


Reply all
Reply to author
Forward
0 new messages