RE: [Edu-sig] Algebra + Python
Getting Poly, Fraction and Matrix to interoperate has been fun. I get into these operator precedence situations where two objects have different implementations of __mul__, so the __rmul__ of one triggers where what I want is the __mul__ of the other. So I end up doing type checking to force the result I want. For example, Poly knows how to coerce a Fraction into becoming a polynomial of degree 0, but Fraction shouldn't ever try to change a polynomial into a Fraction. So when it comes to a Fraction * a Poly, it's the Poly version of * that takes precedence over the Fraction version -- but only because I type check, e.g. def __mul__(self,n): if type(n).__name__=='instance': if n.__class__.__name__ == "Poly": print "Fraction -> Poly" return n.__mul__(self) if n.__class__.__name__ == "Matrix": print "Fraction -> Fraction" return n.__mul__(self) f = self.mkfract(n) return Fraction(self.numer*f.numer, self.denom*f.denom) Anyway, below is an example of a session at the command line:
C = Matrix([map(Fraction,[1,1,1]), map(Fraction,[2,3,4]), map(Fraction,[5,8,9])]) C [1, 1, 1] [2, 3, 4] [5, 8, 9]
C.inverse() [(5.0/2), (1.0/2), (-1.0/2)] [-1.0, -2.0, 1.0] [(-1.0/2), (3.0/2), (-1.0/2)]
A = Matrix([[Poly([1,-3],'t'),2], [Fraction(1,2),Poly([1,-4],'t')]]) A [t - 3, 2] [(1/2), t - 4]
A.det() # determinant of A x**2 - 7*x + 11
I = Matrix([[1,0,0],[0,1,0],[0,0,1]]) # identity matrix t = Poly([1,0],'t') # t t*I [t, 0, 0] [0, t, 0] [0, 0, t]
A = Matrix([[1,1,2],[0,3,2],[1,3,9]]) A [1, 1, 2] [0, 3, 2] [1, 3, 9]
(t*I-A).det() # characteristic polynomial of A t**3 - 13*t**2 + 31*t - 17
p=(t*I-A).det() # t = 10 p(10) -7
Note that polynomials now optionally permit some variable other than x. However, you can't multiply two polynomials with differing variables and expect their identities to stay separate. Getting evaluation to work no matter the letter required a small modification to __call__ in polynomial.py. I've stowed polynomial,pyfraction and simplematrix as a package called mathobjects. simplematrix is dependent on ciphers.py however, which was my last project, and which has to do with permutations in the context of group theory and cryptography. A simple way to compute the determinant is to add the signed products of all permutations of matrix elements, choosing one from each row (a total of row! possibilities). ciphers.py is in charge of returning a list of all those possibilities (breaks down when n is double-digit or higher -- but that's OK, as this is throwaway code)). Kirby
Hi, Kirby. On Sun, 6 May 2001, Kirby Urner wrote:
For example, Poly knows how to coerce a Fraction into becoming a polynomial of degree 0, but Fraction shouldn't ever try to change a polynomial into a Fraction. So when it comes to a Fraction * a Poly, it's the Poly version of * that takes precedence over the Fraction version -- but only because I type check, e.g.
def __mul__(self,n): if type(n).__name__=='instance': if n.__class__.__name__ == "Poly": print "Fraction -> Poly" return n.__mul__(self) if n.__class__.__name__ == "Matrix": print "Fraction -> Fraction" return n.__mul__(self)
f = self.mkfract(n) return Fraction(self.numer*f.numer, self.denom*f.denom)
Python has a built-in function "isinstance" to help you do this. In addition, my personal preference would be to simply use * instead of calling __mul__ explicitly, although that's more of a style issue. Anyway here's what i would write: class Fraction: ... def __mul__(self, n): if isinstance(n, Poly) or isinstance(n, Matrix): return n * self f = self.mkfract(n) return Fraction(self.numer * f.numer, self.denom * f.denom) Very nice work, by the way! -- ?!ng
= Ka-Ping Yee = Kirby
Python has a built-in function "isinstance" to help you do this.
Yes, that cleans up my syntax quite a bit. I'd played around with isinstance before and drew some wrong conclusions -- it's more useful than I'd realized. The only advantage of doing it the other way is my pyfractions module could remain ignorant of Poly and Matrix objects and yet still refer back to their methods: i.e. n.__class__.__name__ == "Matrix" can be truth-tested without the global name Matrix having any meaning in this module (pyfraction). But it's a very small price to pay to put: from simplematrix import Matrix (and similar for Poly) at the top (since I'm testing for these kinds of object, I might as well make this module aware of them). Note: I've also added Sqmatrix as a subclass of Matrix (for square matrices), as so many matrix ops only make sense when applied to square matrices.
In addition, my personal preference would be to simply use * instead of calling __mul__ explicitly, although that's more of a style issue. Anyway here's what i would write:
I was hesitant to do this because the reason I'm in Fraction's __mul__ method in the first place is the Matrix or Poly __mul__ method was ignored in favor of Fraction's in some "poly * fraction" or "fraction * matrix" type expression. By putting: print -> "Fraction" in Fraction's __mul__, I can see when it's being called:
I = Sqmatrix([[1,0,0],[0,1,0],[0,0,1]]) f = Fraction(1,2) f*I -> Fraction [(1/2), 0, 0] [0, (1/2), 0] [0, 0, (1/2)]
I*f [(1/2), 0, 0] [0, (1/2), 0] [0, 0, (1/2)]
So Fraction is trapping these cases and forcing us to use the passed object's __mul__ method instead of Fraction's. I was thinking that using * in place of n.__mul__ might just trigger a repeat of the same situation (i.e. might beget the same ambiguity I'm coding to avoid).... But so far, going with * is working OK. You must have clearer insight into this issue, or at least aren't quite so confused. Is it the case that __mul__ always takes precedence over __rmul__ if both are defined? That would explain the above I think.
class Fraction: ... def __mul__(self, n): if isinstance(n, Poly) or isinstance(n, Matrix): return n * self f = self.mkfract(n) return Fraction(self.numer * f.numer, self.denom * f.denom)
Very nice work, by the way!
Thanks. I appreciate the tips -- thanks to which, I'm gradually getting more proficient with this nifty language.
-- ?!ng
Kirby
participants (2)
-
Ka-Ping Yee
-
Kirby Urner