Beispiel #1
0
def poly_factor(poly):

    P = poly.copy()

    out = []
    # Divide out the content
    if P.content != 1:
        out.append(QPoly([P.content]))
        P = P.primitive_part

    # Use rational roots to find linear factors
    # A root may have multiplicity so we check each root until it doesn't
    # divide anymore
    lin = rational_roots(poly)
    for f in lin:
        while True:
            d, m = divmod(P, QPoly([-f.n, f.d]))
            if m == QPoly([0]):
                out.append(QPoly([-f.n, f.d]))
                P = P // out[-1]
            else:
                break

    # Then do some Kronecker factorization on what's left
    out += kronecker_factorization(P)
    out.sort(key=len, reverse=True)

    return out
    def cast_to_poly(self, N):
        """Truncates the power series and returns a polynomial"""

        P = QPoly([0])
        x = QPoly([0, 1])
        for pos, val in enumerate(self.a):
            if pos > N:
                break
            P += val * (x - self.c)**pos
        return P
Beispiel #3
0
def complete_the_square(poly):
    """Returns a tuple (x,y,z) such that x(y)^2+z is equal to poly"""
    assert type(poly) == QPoly
    assert len(poly) == 3, "Must be a quadratic"
    assert poly[1] % 2 == Rational(0), "Linear coefficient must be even"
    a = poly[2]
    h = poly[1] // (2 * a)
    k = poly[0] - a * (h * h)

    hq = QPoly([poly[1] // (2 * a), 1])

    return QPolySum([QPolyProd([QPoly([a]), hq, hq]),
                     QPoly([k])])  #   a, QPoly( [h,1] ), k
Beispiel #4
0
def lagrange_interpolation(X, Y):
    """Lagrange Polynomial"""
    final = QPoly([0])
    for x, y in zip(X, Y):
        out = QPoly([y])
        for m in X:
            if m != x:
                d = Rational(1, (x - m))
                print(d)
                P = QPoly([-m, 1])
                print(P)
                print(P * d)
                print(out)
                print(out * P * d)
                print()
                out *= P * d
        final += out
    return final
def rfunc_asymptotes(rfunc):
    """Approximate asymptotes of a rational function"""
    assert type(rfunc) == RFunc
    dN = rfunc.N.degree()
    dD = rfunc.D.degree()

    out = []

    if dN == dD + 1:
        out += [rfunc.N // rfunc.D]
    elif dN == dD:
        out += [QPoly([rfunc.N[-1] / rfunc.D[-1]])]
    elif dN < dD:
        out += [QPoly([0])]

    out += [QPoly([i]) for i in qpoly_roots(rfunc.D)]

    return out
Beispiel #6
0
def kronecker_factorization(poly):
    """Modified kroneck factorization"""
    deg = poly.degree()
    fdeg = deg // 2

    # Skip constant and linear terms, these could be factored using Kronecker's
    # method but we will remove them before this.
    if fdeg <= 1:
        return [poly]

    # TODO: Need a better way to choose points
    # TODO: should search positive and negative and try to find values that
    # evaluate to small numbers
    points = [i for i in range(fdeg + 1)]
    val_at_points = [poly(i) for i in points]

    F = [reversed(factorization(e.n, negatives=True)) for e in val_at_points]

    for evs in product(*F):
        L = lagrange_interpolation(points, evs)

        # Ignore factors with negative leading coefficient
        if L[-1] < 0:
            continue
        # Ignore trivial factors
        if L == QPoly([1]) or L == poly:
            continue
        # Skip possible factors with non-integer coefficients
        if any(x.d != 1 for x in L):
            continue

        # Try the division
        q, r = divmod(poly, L)

        # Remainder must be zero
        if r == QPoly([0]):
            # all cofficients of the quotient must be integers
            if all(x.d == 1 for x in q):
                A = kronecker_factorization(L)
                B = kronecker_factorization(q)
                return A + B

    return [poly]
    def __mul__(self, other):
        assert type(other) == int

        if other == 0:
            return QPolySum([QPoly([0])])

        A = self.terms.copy()
        B = A.copy()
        for i in range(other - 1):
            A.update(B)
        return QPolySum(A)
def qpoly_roots(poly, den_lim=1000, iter_lim=1000):

    P = poly.copy()
    rr = rational_roots(poly)

    roots = []

    for i in rr:
        roots.append(i)
        P = P // QPoly([-i, 1])

    intervals = sturm_root_isolation(P)

    for i in intervals:
        roots.append(bisection_method(P, i[0], i[1], den_lim, iter_lim))

    return sorted(roots)
    def __mul__(self, other):

        A = self.terms.copy()

        if type(other) == int:
            return self * QPoly([other])

        if type(other) == QPoly:
            A.update([other])

        elif type(other) == QPolyProd:
            A.update(other.terms)

        else:
            return NotImplemented

        return QPolyProd(A)
def sturm_sequence(poly):
    """Sequence of polynomials used for Sturm's Theorem"""
    assert type(poly) == QPoly

    p0 = poly
    p1 = poly.derivative()

    yield p0

    while True:
        if p1 == QPoly([0]):
            break
        p0, p1 = p1, -(p0 % p1)
        yield p0

        if len(p0) == 1:
            break
Beispiel #11
0
def poly_egcd(P, Q):
    """Bézout's identity for two polynomials"""
    assert type(P) == QPoly
    assert type(Q) == QPoly

    if Q.degree() > P.degree():
        P, Q = Q, P

    r0, r1 = P, Q
    s0, s1 = 1, 0
    t0, t1 = 0, 1

    while r1 != QPoly([0]):
        q = r0 // r1
        r0, r1 = r1, r0 - q * r1
        s0, s1 = s1, s0 - q * s1
        t0, t1 = t1, t0 - q * t1

    return r0.primitive_part, s0, t0
#
#    norm = F[0]*C + F[1]*D
#    print("normalizing factor",norm)
#    C = C//norm
#    D = D//norm
#
#    t0 = RFunc( D*f, F[0] )
#    t1 = RFunc( C*f, F[1] )
#    out = f"{t0}  +  {t1}"
#
#    print(out)
#    print(t0+t1)

if __name__ == '__main__':

    R = QPoly([-3, 1, 1])
    approx_root = newtons_method(R, 2)

    print(f"R = {R}")
    print(
        f"by Newton's method R has a root at approximately: {approx_root}\nwhich is {approx_root.digits(5)}"
    )

    print("\n\n")
    approx_root = bisection_method(R, 0, 2, 10)
    print(f"R = {R}")
    print(
        f"by the bisection method R has a root at approximately: {approx_root}\nwhich is {approx_root.digits(5)}"
    )

    print("\n\n")
 def cast_to_poly(self):
     """Add everything together as a QPoly"""
     out = QPoly([0])
     for val, mul in self.terms.items():
         out += val * mul
     return out
Beispiel #14
0
    # Then do some Kronecker factorization on what's left
    out += kronecker_factorization(P)
    out.sort(key=len, reverse=True)

    return out


def factored_form(poly):
    F = poly_factor(poly)
    return QPolyProd(F)


if __name__ == '__main__':

    S = QPoly([-1, 1]) * QPoly([5, -7, 3]) * QPoly([-1, 1]) * QPoly([7, 2]) * 3
    print(S)
    print("Factored version of S")
    print(factored_form(S))

    print()
    print()
    x = [1, 2, 3]
    y = [1, 8, 27]
    print(f"Lagrange Interpolation of\nx = {x}\ny = {y}")
    print(lagrange_interpolation(x, y))
    #
    #
    #    print()
    #    print()
    #    S = QPoly( [-1,1] ) * QPoly( [3,3,3] ) * QPoly( [-1,2] ) * QPoly( [1,1,0,1] )
                if len(i) == 1:
                    out.append(f"{i.pretty_name}^{{{pwr}}}")
                else:
                    out.append(f"({i.pretty_name})^{{{pwr}}}")

        J = "".join(out)
        J = J.replace("$", "")
        return f"${J}$"

    # Things that are like attributes can be access as properties
    pretty_name = property(_pretty_name)


if __name__ == '__main__':

    P = QPoly([1, 2, 3])
    Q = QPoly([6, -2])
    R = QPoly([5])
    S = QPoly([7])

    sum_of_polys = QPolySum([P, S, Q, P, R, R, R])
    print(sum_of_polys)
    print(sum_of_polys.cast_to_poly())
    print()

    prod_of_polys1 = QPolyProd([P, S, R])
    prod_of_polys2 = QPolyProd([P, S])
    print(prod_of_polys1)
    print(prod_of_polys1.cast_to_poly())

    T = QPolySum([prod_of_polys1, prod_of_polys2])
 def cast_to_poly(self):
     """Multiply everything together as a QPoly"""
     out = QPoly([1])
     for val, pwr in self.terms.items():
         out *= val**pwr
     return out
Beispiel #17
0
#########################

## TODO: Test convergents and semi-convergents
c = CFrac([5, 11, 7, 2])

c_minitests = [
    (c, "[5; 11, 7, 2]"),
    (c.as_rational(), "850/167"),
    (c.pretty_name, "$5+\cfrac{1}{11+\cfrac{1}{7+\cfrac{1}{2}}}$"),
]

##################
## Polynonmials ##
##################

P = QPoly(["3/2", 0, 1, "11.6"])
Q = QPoly([-5, 1])

p_minitests = [(P, "58/5x^3 + x^2 + 3/2"), (Q, "x - 5"),
               (P + Q, "58/5x^3 + x^2 + x - 7/2"),
               (P * Q, "58/5x^4 - 57x^3 - 5x^2 + 3/2x - 15/2"), (P**0, "1"),
               (P**2, "3364/25x^6 + 116/5x^5 + x^4 + 174/5x^3 + 3x^2 + 9/4"),
               (P.integral(2), "29/10x^4 + 1/3x^3 + 3/2x + 2"),
               (P.derivative(), "174/5x^2 + 2x"), (P.content, "1/10"),
               (P.primitive_part, "116x^3 + 10x^2 + 15"),
               (P.monic_part, "x^3 + 5/58x^2 + 15/116"),
               (P // Q, "58/5x^2 + 59x + 295"), (P % Q, "2953/2"),
               (P / Q, "(58/5x^3 + x^2 + 3/2) / (x - 5)")]

########################
## Rational Functions ##