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
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
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
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
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
# 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
######################### ## 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 ##