#----------A polynomial class which supports addition,
#          multiplication, and evaluation--------------
class poly:
	def __init__(self,coefs):
		self.coefs = coefs
	#
	#Call method, to evaluate the value of the polynomial
	def __call__(self,x):
            a = self.coefs #Just a convenient local synonym for the coefs
            value = a[-1]
            for i in range(2,len(a)+1):
                value = value*x + a[-i]
            return value
        #
        #Methods to handle addition
        def __add__(self,other):
            #Check what type other is and act accordingly
            if isinstance(other,poly):
                #Find out which polynomial is higher order and
                #act accordingly. In essence, pad out the coefficient
                #list of the lower order polynomial with zeros.
                if len(self.coefs)>= len(other.coefs):
                    P = self.coefs
                    Q = other.coefs
                else:
                    P = other.coefs
                    Q = self.coefs
                Q = Q + [0. for i in range(len(P)-len(Q))] #Pad with zeros
                return poly([P[i]+Q[i] for i in range(len(P))])
            #
            #Handle case where the other operand is a number
            T = type(other)
            if (T == type(1))or(T==type(1L))or(T ==type(1.))or(T== type(1.+2.j)):
                return poly([a+other for a in self.coefs])
        #If p and q are polynomials, the above method will handle
        #p+q and q+p, and it will also handle p+2., etc. However,
        #it won't handle 2.+p, because the float object (2.) does
        #not know how to add itself to a polynomial. This is why we need
        #to define an __radd__method, which will be called in cases
        #like this
        def __radd__(self,other):
            return self.__add__(other) #Note we don't include the "self"
                                       #argument when calling a method!
        #
        #----------------------------------------------------------------
        #
        #Methods to handle multiplication
	def __mul__(self,other):
            #Check for type of "other" operand and act accordingly
            if isinstance(other,poly):
		n = len(self.coefs)
		m = len(other.coefs)
		coefs = [self.convolve(i,self.coefs,other.coefs) for i in range(n+m -1)]
		return poly(coefs)
            #
            #Handle case where the other operand is a number
            T = type(other)
            if (T == type(1))or(T==type(1L))or(T ==type(1.))or(T== type(1.+2.j)):
                return poly([a*other for a in self.coefs])
        #
        #Handle cases like 2*p, as for addition
        def __rmul__(self,other):
            return self.__mul__(other)
        #
        #---------------------------------------------------------------
	#
	#Method to build a string that prints out the polynomial in
	#a nice algebraic format. Note that we use
	#the %r format code instead of %f, since %r automatically
	#prints out the coeff in the way its own __repr__ method
	#would. This allows us to handle complex, integer, etc.
	#without trouble.
	def __repr__(self):
		str = ''
		for i in range(len(self.coefs)):
                        if i == 0:
			  str += ' %r'%self.coefs[i]
			elif i==1:
                          str += ' %rx'%self.coefs[i]
			else:
                          str += ' %rx**%d'%(self.coefs[i],i)
                        if i < len(self.coefs)-1:
			  str += '+'
		return str
	#
	#A utility routine to compute the convolution of the
	#two lists of coefficients. This is used in polynomial
	#multiplication. This could be made more efficient using
	#Numeric arrays, since the Numeric.convolve(...) function
	#is much faster than the Python loop used below. 
	def convolve(self,i,c1,c2):
		sum = 0
		lim1 = max(0,i-(len(c2)-1))
		lim2 = min(len(c1),i+1)
		for j in range(lim1,lim2):
			sum += c1[j]*c2[i-j]
		return sum

