|
Description:
This package does simple polynomial manipulation: adding, multiplying, taking to powers, evaluating at a value, taking integrals and derivatives. Nothing as sophisticated as Mathematica, but useful all the same. I find that I make lots of dumb errors in multiplying out polynomials by hand (when I don't have Mathematica at my disposal), and this little script helps prevent those errors.
Source: Text Source
"""\
Polynomial.py - A set of utilities to manipulate polynomials. This consists
of a set of simple functions to convert polynomials to a Python list, and
manipulate the resulting lists for multiplication, addition, and
power functions. There is also a class that wraps these functions
to make their use a little more natural.
This can also evaluate the polynomials at a value, and take integrals
and derivatives of the polynomials.
Written by Rick Muller.
"""
import re
class Polynomial:
def __init__(self,val):
if type(val) == type([]):
self.plist = val
elif type(val) == type(''):
self.plist = parse_string(val)
else:
raise "Unknown argument to Polynomial: %s" % val
return
def __add__(self,other): return Polynomial(add(self.plist,plist(other)))
def __radd__(self,other): return Polynomial(add(self.plist,plist(other)))
def __sub__(self,other): return Polynomial(sub(self.plist,plist(other)))
def __rsub__(self,other): return -Polynomial(sub(self.plist,plist(other)))
def __mul__(self,other): return Polynomial(multiply(self.plist,
plist(other)))
def __rmul__(self,other): return Polynomial(multiply(self.plist,
plist(other)))
def __neg__(self): return -1*self
def __pow__(self,e): return Polynomial(power(self.plist,e))
def __repr__(self): return tostring(self.plist)
def __call__(self,x1,x2=None): return peval(self.plist,x1,x2)
def integral(self): return Polynomial(integral(self.plist))
def derivative(self): return Polynomial(derivative(self.plist))
def plist(term):
"Force term to have the form of a polynomial list"
try:
pl = term.plist
return pl
except:
pass
if type(term) == type(1.0) or type(term) == type(1):
return [term]
elif type(term) == type(''):
return parse_string(term)
else:
raise "Unsupported term can't be corced into a plist: %s" % term
return None
def peval(plist,x,x2=None):
"""\
Eval the plist at value x. If two values are given, the
difference between the second and the first is returned. This
latter feature is included for the purpose of evaluating
definite integrals.
"""
val = 0
if x2:
for i in range(len(plist)): val += plist[i]*(pow(x2,i)-pow(x,i))
else:
for i in range(len(plist)): val += plist[i]*pow(x,i)
return val
def integral(plist):
"""\
Return a new plist corresponding to the integral of the input plist.
This function uses zero as the constant term, which is okay when
evaluating a definite integral, for example, but is otherwise
ambiguous.
The math forces the coefficients to be turned into floats.
Consider importing __future__ division to simplify this.
"""
if not plist: return []
new = [0]
for i in range(len(plist)):
c = plist[i]/(i+1.)
if c == int(c): c = int(c)
new.append(c)
return new
def derivative(plist):
"""\
Return a new plist corresponding to the derivative of the input plist.
"""
new = []
if not plist: return new
for i in range(1,len(plist)): new.append(i*plist[i])
return new
def add(p1,p2):
"Return a new plist corresponding to the sum of the two input plists."
if len(p1) > len(p2):
new = [i for i in p1]
for i in range(len(p2)): new[i] += p2[i]
else:
new = [i for i in p2]
for i in range(len(p1)): new[i] += p1[i]
return new
def sub(p1,p2): return add(p1,mult_const(p2,-1))
def mult_const(p,c):
"Return a new plist corresponding to the input plist multplied by a const"
return [c*pi for pi in p]
def multiply(p1,p2):
"Return a new plist corresponding to the product of the two input plists"
if len(p1) > len(p2): short,long = p2,p1
else: short,long = p1,p2
new = []
for i in range(len(short)): new = add(new,mult_one(long,short[i],i))
return new
def mult_one(p,c,i):
"""\
Return a new plist corresponding to the product of the input plist p
with the single term c*x^i
"""
new = [0]*i
for pi in p: new.append(pi*c)
return new
def power(p,e):
"Return a new plist corresponding to the e-th power of the input plist p"
assert int(e) == e, "Can only take integral power of a plist"
new = [1]
for i in range(e): new = multiply(new,p)
return new
def parse_string(str=None):
"""\
Do very, very primitive parsing of a string into a plist.
'x' is the only term considered for the polynomial, and this
routine can only handle terms of the form:
7x^2 + 6x - 5
and will choke on seemingly simple forms such as
x^2*7 - 1
or
x**2 - 1
"""
termpat = re.compile('([-+]?\s*\d*\.?\d*)(x?\^?\d?)')
res_dict = {}
for n,p in termpat.findall(str):
n,p = n.strip(),p.strip()
if not n and not p: continue
n,p = parse_n(n),parse_p(p)
if res_dict.has_key(p): res_dict[p] += n
else: res_dict[p] = n
highest_order = max(res_dict.keys())
res = [0]*(highest_order+1)
for key,value in res_dict.items(): res[key] = value
return res
def parse_n(str):
"Parse the number part of a polynomial string term"
if not str: return 1
elif str == '-': return -1
elif str == '+': return 1
return eval(str)
def parse_p(str):
"Parse the power part of a polynomial string term"
pat = re.compile('x\^?(\d)?')
if not str: return 0
res = pat.findall(str)[0]
if not res: return 1
return int(res)
def strip_leading_zeros(p):
"Remove the leading (in terms of high orders of x) zeros in the polynomial"
for i in range(len(p)-1,-1,-1):
if p[i]: break
return p[:i+1]
def tostring(p):
"""\
Convert a plist into a string. This looks overly complex at first,
but most of the complexity is caused by special cases.
"""
p = strip_leading_zeros(p)
str = []
for i in range(len(p)-1,-1,-1):
if p[i]:
if i < len(p)-1:
if p[i] >= 0: str.append('+')
else: str.append('-')
str.append(tostring_term(abs(p[i]),i))
else:
str.append(tostring_term(p[i],i))
return ' '.join(str)
def tostring_term(c,i):
"Convert a single coefficient c and power e to a string cx^i"
if i == 1:
if c == 1: return 'x'
elif c == -1: return '-x'
return "%sx" % c
elif i:
if c == 1: return "x^%d" % i
elif c == -1: return "-x^%d" % i
return "%sx^%d" % (c,i)
return "%s" % c
def test():
print tostring([1,2.,3])
print tostring([1,2,-3])
print tostring([1,-2,3])
print tostring([0,1,2])
print tostring([0,1,2,0])
print tostring([0,1])
print tostring([0,1.0])
print tostring(add([1,2,3],[1,-2,3]))
print tostring(multiply([1,1],[-1,1]))
print tostring(power([1,1],2))
print Polynomial([1,2,3]) + Polynomial([1,2])
print Polynomial([1,2,3]) + 1
print Polynomial([1,2,3])-1
print 1-Polynomial([1,2,3])
print 1+Polynomial([1,2,3])
print Polynomial([1,2,3])*-1
print -1*Polynomial([1,2,3])
print -Polynomial([1,2,3])
print ''
for m in range(1,4):
print 'P^a_%d = ' % m,\
1 - Polynomial([1,-1])**m*Polynomial([1,m])
print 'P^b_%d = ' % m,\
Polynomial([0,1])**m*Polynomial([1+m,-m])
print ''
print parse_string('+3x - 4x^2 + 7x^5-x+1')
print Polynomial(parse_string('+3x - 4x^2 + 7x^5-x+1'))
print Polynomial('+3x - 4x^2 + 7x^5-x+1')
print Polynomial('+3x -4x^2+7x^5-x+1') - 7
print Polynomial('5x+x^2')
print integral([])
print integral([1])
print integral([0,1])
print derivative([0,0,0.5])
p = Polynomial('x')
ip = p.integral()
dp = p.derivative()
print ip,dp
print ip(0,1)
if __name__ == '__main__': test()
Discussion:
I'm constantly making mistakes when I multiply out simple polynomials, and this program lets
me evaluate, add, subtract, multiply, and take powers of simple polynomials. There are lots
of limitations to the functionality here. All polynomials have to use 'x' as the dependent variable,
and the syntax that you may use is fairly limited. Still, you can do some neat things like:
>>> from Polynomial import *
>>> Polynomial('x^2+5')**2-1
x^4 + 10x^2 + 24
>>> Polynomial('x^2+5')**2-'x'
x^4 + 10x^2 - x + 25
|