Ticket #6245 (closed enhancement: fixed)

Opened 15 months ago

Last modified 4 months ago

make a custom infix operator decorator

Reported by: jason Owned by: cwitty
Priority: major Milestone: sage-4.4
Component: misc Keywords:
Cc: cwitty, hivert, mhansen, kcrisman Author(s): Jason Grout, Carl Witty, Florent Hivert
Report Upstream: N/A Reviewer(s): Ross Kyprianou
Merged in: sage-4.4.alpha2 Work issues:

Description

It would be nice to incorporate the code developed in  http://groups.google.com/group/sage-devel/browse_thread/thread/100de89e7d402134/fe89570b403344ae (and in the same spirit as the backslash operator). An example of the final developed code is at  http://sagenb.org/home/pub/565.

Attachments

trac-6245-infix-decorator.patch Download (7.5 KB) - added by jason 5 months ago.
rebased

Change History

  Changed 15 months ago by jason

  • cc cwitty, hivert added

For reference, the code is:

class infix_operator:
    def __init__(self, function, left=None, right=None):
        self.function = function
        self.left = left
        self.right = right

    def __rmul__(self, left):
        if self.right is None:
            if self.left is None:
                return infix_operator(self.function, left=left)
            else:
                raise SyntaxError, "Infix operator already has its left argument"
        else:
            return self.function(left, self.right)

    def __mul__(self, right):
        if self.left is None:
            if self.right is None:
                return infix_operator(self.function, right=right)
            else:
                raise SyntaxError, "Infix operator already has its right argument"
        else:
            return self.function(self.left, right) 
        	

And several examples (doctests?) are:

# This emul operator returns the element-wise product of two lists...
@infix_operator
def emul(a,b):
    return [i*j for i,j in zip(a,b)] 
        	
a=[1,2,3]
b=[3,4,5]

# Returns [3,8,15]
a *emul* b 
        	
@infix_operator
def hadamard_product(a, b):
   if a.nrows()!=b.nrows() or a.ncols()!=b.ncols():
       raise ValueError, "Matrices must have the same dimensions in a Hadamard product"
   return matrix(a.nrows(), a.ncols(), [x*y for x, y in zip(a.list(), b.list())]) 
        	
A=random_matrix(ZZ,3)
B=random_matrix(ZZ,3)
A *hadamard_product* B 

  Changed 12 months ago by jason

The patch is an initial go at this. The documentation for the function needs to change (it still is just the same docs as the code I followed in plot/misc.py).

Here are some examples:

sage: from sage.misc.misc import infix_operator
sage: # A post-fix operator
sage: @infix_operator('or')
sage: def pipe(a,b):
...       return b(a)
...
sage: print (x |pipe| cos)
sage: print pi |pipe| n
sage: print pi |pipe| n |pipe| cos
cos(x)
3.14159265358979
-1.00000000000000
sage: # an infix dot product
sage: @infix_operator('multiply')
sage: def dot(a,b):
...       return a.dot_product(b)
...       
...
sage: vector([1,2]) *dot* vector([2,4])
10
sage: # an infix sum
sage: @infix_operator('add')
sage: def esum(a,b):
...       return [i+j for i,j in zip(a,b)]
...
sage: [1,2,4] +esum+ [3,-1,2]
[4, 1, 6]

  Changed 12 months ago by jason

  • cc mhansen added
  • summary changed from make a custom infix operator decorator to [with patch, needs work] make a custom infix operator decorator

CCing mhansen, since he probably has very enlightening things to say about how I dealt with the decorators :).

  Changed 12 months ago by jason

  • author set to Jason Grout, Carl Witty, Florent Hivert

  Changed 12 months ago by jason

I cleaned up the code a bit and made much better documentation.

  Changed 12 months ago by jason

  • cc kcrisman added
  • summary changed from [with patch, needs work] make a custom infix operator decorator to [with patch, needs review] make a custom infix operator decorator

follow-up: ↓ 8   Changed 12 months ago by kcrisman

I have no comment on the technical side, but wonder - are you limiting "object" to be or, add, or multiply? What if someone wanted to create an infix for, say, factorial - would they have to change this code?

in reply to: ↑ 7   Changed 12 months ago by jason

Replying to kcrisman:

I have no comment on the technical side, but wonder - are you limiting "object" to be or, add, or multiply? What if someone wanted to create an infix for, say, factorial - would they have to change this code?

I only chose a few precedence levels (that's really the issue here). There's no reason we couldn't put all of the special functions in (like subtract, for example).

  Changed 7 months ago by rossk

  • upstream set to N/A

Applied patch to 4.3.2

(Cool functionality - Can review quickly once this is addressed)

applying trac-6245-infix-decorator.patch
patching file sage/misc/misc.py
Hunk #1 FAILED at 2219
1 out of 1 hunks FAILED -- saving rejects to file sage/misc/misc.py.rej
patch failed, unable to continue (try -v)
patch failed, rejects left in working dir
errors during apply, please fix and refresh trac-6245-infix-decorator.patch

Changed 5 months ago by jason

rebased

follow-up: ↓ 12   Changed 5 months ago by jason

I rebased the patch to Sage 4.3.4. Can you look at it again?

  Changed 5 months ago by jason

  • summary changed from [with patch, needs review] make a custom infix operator decorator to make a custom infix operator decorator

in reply to: ↑ 10   Changed 5 months ago by rossk

Replying to jason:

I rebased the patch to Sage 4.3.4. Can you look at it again?

Will aim to look at this later today

  Changed 5 months ago by rossk

Can confirm all examples work as indicated and all tests passed for me

sage: def dot(a,b):
sage:     return a.dot_product(b)
sage: dot=infix_operator('multiply')(dot)
sage: u=vector([1,2,3])
sage: v=vector([5,4,3])
sage: u *dot* v

22

# Also these examples here show precedence works as 
#   expected (i.e. * before +)
#
sage: def eadd(a,b): 
sage:     return a.parent([i+j for i,j in zip(a,b)])
sage: 
sage: eadd=infix_operator('add')(eadd)
sage: u=vector([1,2,3])
sage: v=vector([5,4,3])
sage: print u +eadd+ v
sage: print 2*u +eadd+ v
sage: print v +eadd+ 2*u 
sage: print v +eadd+ u*2 
sage: print (v +eadd+ u)*2  	

(6, 6, 6)
(7, 8, 9)
(7, 8, 9)
(7, 8, 9)
(12, 12, 12)

# Last example: function composition not commutative as expected
sage: def thendo(a,b): return b(a)
sage: thendo=infix_operator('or')(thendo)
sage: print x |thendo| cos |thendo| (lambda x: x^2)
sage: print x |thendo| (lambda x: x^2) |thendo| cos 

cos(x)^2
cos(x^2)

follow-up: ↓ 15   Changed 5 months ago by rossk

Noticed references to __rmul__ in the code so I thought I might try setting up a situation

where __mul__ fails so __rmul__ would be exercised. In setting up the test code, I got an

error but cant see whats wrong (any thoughts?)

class Fraction:
  def __init__(self, numerator, denominator=1):
    self.numerator = numerator
    self.denominator = denominator
  def __str__(self):
    return "%d/%d" % (self.numerator, self.denominator)
  def zmul(self, other):
    return Fraction(self.numerator*other.numerator,
                  self.denominator*other.denominator)
  def zrmul(self, other):
    return Fraction(other*self.numerator,
                  self.denominator)
  def __mul__(self, other):
    return Fraction(self.numerator*other.numerator,
                  self.denominator*other.denominator)
  def __rmul__(self, other):
    return Fraction(other*self.numerator,self.denominator)

# multiplication operator using methods
print Fraction(2,3).zmul(Fraction(5,4))

10/12

# multiplication infix operator
def dot(a,b):
  return a.zmul(b)
dot = infix_operator('multiply')(dot)
print Fraction(2,3) *dot* Fraction(5,4)

crashes with

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "_sage_input_59.py", line 9, in <module>
    open("___code___.py","w").write("# -*- coding: utf-8 -*-\n" + _support_.preparse_worksheet_cell(base64.b64decode("Z...<abbreviated>...Y="),globals())+"\n"); execfile(os.path.abspath("___code___.py"))
  File "", line 1, in <module>
    
  File "/tmp/tmpIR_EGe/___code___.py", line 8, in <module>
    u *dot* v
  File "", line 1, in <module>
    
  File "", line 13, in __mul__
    
AttributeError: dot instance has no attribute 'numerator'

in reply to: ↑ 14   Changed 5 months ago by jason

Replying to rossk:

Noticed references to __rmul__ in the code so I thought I might try setting up a situation
where __mul__ fails so __rmul__ would be exercised. In setting up the test code, I got an
error but cant see whats wrong (any thoughts?)

The problem is that your mul function does not check its arguments. Note that:

sage: Fraction(2,3)*matrix(2,1,[1,2])
Traceback (click to the left of this block for traceback)
...
AttributeError: 'sage.matrix.matrix_integer_dense.Matrix_integer_dense'
object has no attribute 'numerator'

Instead, you should check your arguments in the mul function before blindly calling the .numerator method, or at least you should catch the error, like this:

class Fraction:
  def __init__(self, numerator, denominator=1):
    self.numerator = numerator
    self.denominator = denominator
  def __str__(self):
    return "Fraction(%d,%d)" % (self.numerator, self.denominator)
  __repr__=__str__
  def zmul(self, other):
    return Fraction(self.numerator*other.numerator,
                  self.denominator*other.denominator)
  def __mul__(self, other):
    try:
        return Fraction(self.numerator*other.numerator,
                  self.denominator*other.denominator)
    except:
        return NotImplemented

  Changed 5 months ago by rossk

  • status changed from needs_review to positive_review
  • reviewer set to Ross Kyprianou

Thanks Jason - I appreciate what your code is doing a bit more now and see where I erred - thanks for the explanation. I would think these are enough tests to update this to a positive review.

class Fraction:
  def __init__(self, numerator, denominator=1):
    self.numerator = numerator
    self.denominator = denominator
  def __str__(self):
    return "Fraction(%d,%d)" % (self.numerator, self.denominator)
  __repr__=__str__
  def zmul(self, other):
    return Fraction(self.numerator*other.numerator,
                  self.denominator*other.denominator)
  def __mul__(self, other):
    try:
        return Fraction(self.numerator*other.numerator,
                  self.denominator*other.denominator)
    except:
        return NotImplemented
  def __rmul__(self, other):
    try:
      return Fraction(other*self.numerator,self.denominator)
    except:
        return NotImplemented
    
def dot(a,b): 
    return a*b
dot = infix_operator('multiply')(dot)

u = Fraction(2,3)
v = Fraction(5,4)
print u *dot* v
print 3 *dot* v

  Changed 4 months ago by jhpalmieri

  • status changed from positive_review to closed
  • resolution set to fixed
  • merged set to sage-4.4.alpha2

Merged into 4.4.alpha2.

Note: See TracTickets for help on using tickets.