# This module defines a univariate rational function class
#
# Written by Konrad Hinsen <hinsen@cnrs-orleans.fr>
# last revision: 2002-9-3
#

from Polynomial import Polynomial
import Numeric

class RationalFunction:

    """Rational Function

    Instances of this class represent rational functions
    in a single variable. They can be evaluated like functions.

    Constructor: RationalFunction(|numerator|, |denominator|)

    Arguments:

    |numerator|, |denominator| -- polynomials or sequences of numbers that
                                  represent the polynomial coefficients

    Rational functions support addition, subtraction, multiplication,
    and division.
    """

    def __init__(self, numerator, denominator=[1.]):
        if hasattr(numerator, 'is_polynomial'):
            self.numerator = numerator
        else:
            self.numerator = Polynomial(numerator)
        if hasattr(denominator, 'is_polynomial'):
            self.denominator = denominator
        else:
            self.denominator = Polynomial(denominator)
        self._normalize()

    is_rational_function = 1

    def __call__(self, value):
        return self.numerator(value)/self.denominator(value)

    def __repr__(self):
        return "RationalFunction(%s,%s)" % (repr(list(self.numerator.coeff)),
                                            repr(list(self.denominator.coeff)))

    def _normalize(self):
        self.numerator = self._truncate(self.numerator)
        self.denominator = self._truncate(self.denominator)
        n = 0
        while 1:
            if self.numerator.coeff[n] != 0:
                break
            if self.denominator.coeff[n] != 0:
                break
            n = n + 1
            if n == len(self.numerator.coeff):
                break
        if n > 0:
            self.numerator = Polynomial(self.numerator.coeff[n:])
            self.denominator = Polynomial(self.denominator.coeff[n:])
        factor = self.denominator.coeff[-1]
        if factor != 1.:
            self.numerator = self.numerator/factor
            self.denominator = self.denominator/factor

    def _truncate(self, poly):
        if poly.coeff[-1] != 0.:
            return poly
        coeff = poly.coeff
        while len(coeff) > 1 and coeff[-1] == 0.:
            coeff = coeff[:-1]
        return Polynomial(coeff)

    def __coerce__(self, other):
        if hasattr(other, 'is_rational_function'):
            return (self, other)
        elif hasattr(other, 'is_polynomial'):
            return (self, RationalFunction(other, [1.]))
        else:
            return (self, RationalFunction([other], [1.]))

    def __mul__(self, other):
        return RationalFunction(self.numerator*other.numerator,
                                self.denominator*other.denominator)
    __rmul__ = __mul__

    def __div__(self, other):
        return RationalFunction(self.numerator*other.denominator,
                                self.denominator*other.numerator)

    def __rdiv__(self, other):
        return RationalFunction(other.numerator*self.denominator,
                                other.denominator*self.numerator)

    def __add__(self, other):
        return RationalFunction(self.numerator*other.denominator+
                                self.denominator*other.numerator,
                                self.denominator*other.denominator)
    __radd__ = __add__

    def __sub__(self, other):
        return RationalFunction(self.numerator*other.denominator-
                                self.denominator*other.numerator,
                                self.denominator*other.denominator)

    def __rsub__(self, other):
        return RationalFunction(other.numerator*self.denominator-
                                other.denominator*self.numerator,
                                self.denominator*other.denominator)

    def divide(self, shift=0):
        """Returns a polynomial and a rational function such that the
        sum of the two is equal to the original rational function. The
        returned rational function's numerator is of lower order than
        its denominator.

        The argument |shift| (default: 0) specifies a positive integer
        power of the independent variable by which the numerator
        is multiplied prior to division.
        """
        num = Numeric.array(self.numerator.coeff, copy=1)
        if shift > 0:
            num = Numeric.concatenate((shift*[0.], num))
        den = self.denominator.coeff
        den_order = len(den)
        coeff = []
        while len(num) >= den_order:
            q = num[-1]/den[-1]
            coeff.append(q)
            num[-den_order:] = num[-den_order:]-q*den
            num = num[:-1]
        if not coeff:
            coeff = [0]
        coeff.reverse()
        if len(num) == 0:
            num = [0]
        return Polynomial(coeff), RationalFunction(num, den)

    def zeros(self):
        "Returns an array containing the zeros."
        return self.numerator.zeros()

    def poles(self):
        "Returns an array containing the poles."
        return self.denominator.zeros()


# Test code

if __name__ == '__main__':

    p = Polynomial([1., 1.])
    invp = 1./p
    pr = RationalFunction(p)
    print pr+invp

