Opened 11 years ago

Closed 10 years ago

#6245 closed enhancement (fixed)

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 Merged in: sage-4.4.alpha2
Authors: Jason Grout, Carl Witty, Florent Hivert Reviewers: Ross Kyprianou
Report Upstream: N/A Work issues:
Branch: Commit:
Dependencies: Stopgaps:

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 (1)

trac-6245-infix-decorator.patch (7.5 KB) - added by jason 10 years ago.
rebased

Download all attachments as: .zip

Change History (18)

comment:1 Changed 11 years 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 

comment:2 Changed 10 years 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]

comment:3 Changed 10 years 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 :).

comment:4 Changed 10 years ago by jason

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

comment:5 Changed 10 years ago by jason

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

comment:6 Changed 10 years 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

comment:7 follow-up: Changed 10 years 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?

comment:8 in reply to: ↑ 7 Changed 10 years 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).

comment:9 Changed 10 years ago by rossk

  • Report 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 10 years ago by jason

rebased

comment:10 follow-up: Changed 10 years ago by jason

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

comment:11 Changed 10 years ago by jason

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

comment:12 in reply to: ↑ 10 Changed 10 years 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

comment:13 Changed 10 years 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)

comment:14 follow-up: Changed 10 years 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'

comment:15 in reply to: ↑ 14 Changed 10 years 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

comment:16 Changed 10 years ago by rossk

  • Reviewers set to Ross Kyprianou
  • Status changed from needs_review to positive_review

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

comment:17 Changed 10 years ago by jhpalmieri

  • Merged in set to sage-4.4.alpha2
  • Resolution set to fixed
  • Status changed from positive_review to closed

Merged into 4.4.alpha2.

Note: See TracTickets for help on using tickets.