Ticket #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 | Work issues: | |
| Report Upstream: | N/A | Reviewers: | Ross Kyprianou |
| Authors: | Jason Grout, Carl Witty, Florent Hivert | Merged in: | sage-4.4.alpha2 |
| 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
Change History
comment:2 Changed 4 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 4 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:5 Changed 4 years ago by jason
I cleaned up the code a bit and made much better documentation.
comment:6 Changed 4 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: ↓ 8 Changed 4 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 4 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 3 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
comment:10 follow-up: ↓ 12 Changed 3 years ago by jason
I rebased the patch to Sage 4.3.4. Can you look at it again?
comment:11 Changed 3 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 3 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 3 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: ↓ 15 Changed 3 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 3 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 3 years ago by rossk
- Status changed from needs_review to positive_review
- Reviewers 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
comment:17 Changed 3 years ago by jhpalmieri
- Status changed from positive_review to closed
- Resolution set to fixed
- Merged in set to sage-4.4.alpha2
Merged into 4.4.alpha2.


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