def sturm(f): """Compute the Sturm sequence of given polynomial. Usage: ====== The input is assumed to be a square-free and univariate polynomial, either as a SymPy expression or as instance of Polynomial. The output is a list representing f's Sturm sequence, which is built similarly to the euclidian algorithm, beginning with f and its derivative. The result can be used in L{count_real_roots}. References: =========== Davenport, Siret, Tournier: Computer Algebra, 1988 """ if not isinstance(f, Polynomial): f = Polynomial(f) seq = [f] seq.append(f.diff(f.var[0])) while seq[-1].sympy_expr is not S.Zero: seq.append(-(div_.div(seq[-2], seq[-1])[-1])) return seq[:-1]
def sqf_part(f, var=None, order=None): """Returns the square-free part of f. Usage: ====== Computes the square-free part of f. The input is assumed to be a univariate polynomial, either as a SymPy expression or an instance of Polynomial. In the first case, you can optionally specify the variables and monomial order with the arguments 'var' and 'order'. The result is returned as an instance of Polynomial. Examples: ========= >>> x = Symbol('x') >>> print sqf_part(2*x**3 + 2*x**2) 2*x + 2*x**2 References: =========== Gathen, Gerhard: Modern Computer Algebra, Cambridge University Press, 1. edition, p. 370 Also see L{sqf}. """ if not isinstance(f, Polynomial): f = Polynomial(f, var=var, order=order) # The gcd of f with its derivative is the multiple part. ff = div_.gcd(f, f.diff(f.var[0])) return div_.div(f, ff)[0]
def div(f, g, var=None, order=None, coeff=None): """Division with remainder. A thin wrapper that returns SymPy expressions coming from L{div_.div}. """ q, r = div_.div(f, g, var, order, coeff) if isinstance(q, list): return map(lambda x: x.sympy_expr, q), r.sympy_expr else: return q.sympy_expr, r.sympy_expr
def sqf(f, var=None, order=None, coeff=None): """Square-free decomposition. Usage: ====== Computes a decomposition of f in a1 * a2**2 * ... * an**n, where the ai are pairwise prime and square-free polynomials. The input is assumed to be a univariate polynomial, either as a SymPy expression or an instance of Polynomial. In the first case, you can optionally specify the variables and monomial order with the arguments 'var' and 'order'. If the argument 'coeff' is set to 'int', the constant factors are redistributed to the different ai, so that they have integer coefficients. Otherwise, they are made monic. A list is returned, with an instance of Polynomial at each index, which represents the multiplicity (except beginning with 1 instead of 0). Examples: ========= >>> x = Symbol('x') >>> a = sqf(3 - 12*x - 4*x**3 + 4*x**4 + 13*x**2) >>> for i, f in enumerate(a): print (i + 1), f 1 12 + 4*x**2 2 (-1/2) + x >>> b = sqf(3 - 12*x - 4*x**3 + 4*x**4 + 13*x**2, coeff='int') >>> for i, f in enumerate(b): print (i + 1), f 1 3 + x**2 2 (-1) + 2*x References: =========== Gathen, Gerhard: Modern Computer Algebra, Cambridge University Press, 1. edition, p. 371 Also see L{sqf_part}, L{factor}. """ if not isinstance(f, Polynomial): f = Polynomial(f, var=var, order=order) # Check for constant polynomials: if f.var == [] or f.coeffs[0][1] is S.Zero: return [f] f = [f] while f[-1].coeffs[0][1] is not S.Zero: f.append(div_.gcd(f[-1], f[-1].diff(f[-1].var[0]))) g = [] for i in range(1, len(f)): g.append(div_.div(f[i - 1], f[i])[0]) a = [] for i in range(0, len(g) - 1): a.append(div_.div(g[i], g[i + 1])[0]) a.append(g[-1]) if coeff == "int": # Redistribute the constants. ca = int(a[0].content()) c = ca for i, p in enumerate(a[1:]): # Compute lcm of denominators in coeffs: l, a[i + 1] = p.as_integer() c /= int(l) ** (i + 2) ca = Integer(ca / c) a[0] = Polynomial(coeffs=tuple([(t[0] / ca,) + t[1:] for t in a[0].coeffs]), var=a[0].var, order=a[0].order) return a
def kronecker_mv(f): """One step in multivariate factorization, see L{factor}.""" # Given a list of factors, re-assemble all m-subsets of them. def factor_combinations(lisp, m): def recursion(fa, lisp, m): if m == 0: yield fa else: for i, fa2 in enumerate(lisp[0 : len(lisp) + 1 - m]): for el in recursion(fa2 * fa, lisp[i + 1 :], m - 1): yield el for i, fa in enumerate(lisp[0 : len(lisp) + 1 - m]): for el in recursion(fa, lisp[i + 1 :], m - 1): yield el # First sort the variables by occuring exponents. # Then get degree bound, that is larger than all individual degrees. max_exp = {} for v in f.var: max_exp[v] = 0 for term in f.coeffs: for v, exponent in zip(f.var, term[1:]): if exponent > max_exp[v]: max_exp[v] = exponent new_var = list(f.var) new_var.sort(key=lambda v: max_exp[v], reverse=True) f = Polynomial(f.sympy_expr, var=new_var, order=f.order) d = int(max_exp[f.var[0]]) + 1 # Now reduce the polynomial f to g in just variable, by the # substitution x_i -> y**(d**i) g = f.sympy_expr y = Symbol("y", dummy=True) for i, v in enumerate(f.var): g = g.subs(v, y ** (d ** i)) g = Polynomial(g, var=y, order=f.order) # We can now call the univariate factorization algorithm for g. g_factors = factor(g)[1:] # Don't use constant factor. constant_factor = Polynomial(S.One, var=f.var, order=f.order) # Trial division with all combinations of factors of g. tested = [] result = [] for m in range(1, len(g_factors) / 2 + 1): for cand in factor_combinations(g_factors, m): if cand in tested: continue # Inverse reduction ff = S.Zero for term in cand.coeffs: ff_term = term[0] y_deg = term[1] for v in f.var: v_deg = int(y_deg) % d y_deg = (y_deg - v_deg) / d ff_term *= v ** v_deg ff += ff_term if ff is S.One: continue candidate = Polynomial(ff, var=f.var, order=f.order) # Make leading_coefficient positive: if candidate.coeffs[0][0] < 0: candidate = Polynomial( coeffs=[(-t[0],) + t[1:] for t in candidate.coeffs], var=candidate.var, order=candidate.order ) q, r = div_.div(f, candidate, coeff="int") if r.sympy_expr is S.Zero: # found a factor result.append(candidate) f = q else: tested.append(cand) # Check if f is constant. if f.coeffs[0][1:] == tuple([S.Zero] * len(f.var)): constant_factor = f f = Polynomial(S.One, var=f.var, order=f.order) break if f.sympy_expr is S.One: break if f.sympy_expr is not S.One: result.append(f) return [constant_factor] + result
def kronecker(f): """One step in univariate factorization, see L{factor}.""" def lagrange_base(pos): """Compute the base polynomials used for Lagrange interpolation. They are constructed such that they evaluate to 1 at their position but 0 at all other points. """ l = [] for x in pos: l.append(Polynomial(coeffs=((S.One, S.One), (-x, S.Zero)), var=f.var, order=f.order)) b = [] for i, x in enumerate(pos): p = Polynomial(coeffs=((S.One, S.Zero),), var=f.var, order=f.order) for ll in l[:i] + l[i + 1 :]: p *= ll c = S.One for xx in pos[:i] + pos[i + 1 :]: c *= x - xx p = Polynomial(coeffs=tuple(map(lambda t: (t[0] / c,) + t[1:], p.coeffs)), var=p.var, order=p.order) b.append(p) return b def combine(divs): """Combine the divisors of all coefficients.""" # Don't try negative divisors for first value. lst = map(lambda el: [el], divs[0]) for choices in divs[1:]: lst2 = [] for el in lst: for new in choices: # Also try negative divisors: lst2.append(el + [new]) lst2.append(el + [-new]) lst = lst2 return lst # Half the degree for a possible polynomial divisor g. deg = int(f.coeffs[0][1]) / 2 # Points for interpolation pos = map(Rational, range(0 - deg / 2, deg + 1 - deg / 2)) # Reusable Lagrange base polynomials. base = lagrange_base(pos) # Evaluate at points. values = map(f, pos) # All divisors of the values give possible values for g. divs = map(integer_divisors, map(int, values)) # Assemble all possible divisor combinations. combs = combine(divs) # Construct candidates for g. cands = [] for comb in combs: cand = Polynomial(S.Zero, var=f.var, order=f.order) for c, b in zip(comb, base): cand += Polynomial(coeffs=tuple([(c * term[0],) + term[1:] for term in b.coeffs]), var=b.var, order=b.order) # Filter out constant and non-integer polynomials! if not (len(cand.coeffs) == 1 and cand.coeffs[0][1] is S.Zero): if all(map(lambda t: t[0].is_integer, cand.coeffs)): cands.append(cand) # Make leading coefficient positive: for cand in cands: if cand.coeffs[0][0] < S.Zero: cand = Polynomial( coeffs=tuple([(t[0] * S.NegativeOne,) + t[1:] for t in cand.coeffs]), var=cand.var, order=cand.order ) # Filter double entries: cands2 = [] for cand in cands: if not cand in cands2: cands2.append(cand) cands = cands2 # TODO: Too many candidates? # TODO: Use iterators instead of lists! for g in cands: q, r = div_.div(f, g, coeff="int") if r.sympy_expr is S.Zero: return kronecker(q) + kronecker(g) else: # No divisor found, f irreducible. # TODO: Try again with divisors of smaller degree? return [f]
def groebner(f, var=None, order=None, reduced=True): """Computes a (reduced) Groebner base for a given list of polynomials. Usage: ====== The input consists of a list of polynomials, either as SymPy expressions or instances of Polynomials. In the first case, you should also specify the variables and the monomial order with the arguments 'var' and 'order'. Only the first polynomial is checked for its type, the rest is assumed to match. By default, this algorithm returns the unique reduced Groebner base for the given ideal. By setting reduced=False, you can prevent the reduction steps. Examples: ========= >>> x, y = symbols('xy') >>> G = groebner([x**2 + y**3, y**2-x], order='lex') >>> for g in G: print g x - y**2 y**3 + y**4 Notes: ====== Groebner bases are used to choose specific generators for a polynomial ideal. Because these bases are unique, you can check for ideal equality, by comparing the Groebner bases. To see if one polynomial lies in on ideal, divide by the elements in the base and see if the remainder if 0. They can also be applied to equation systems: By choosing lexicographic ordering, you can eliminate one variable at a time, given that the ideal is zero-dimensional (finite number of solutions). Here, an improved version of Buchberger's algorithm is used. For all pairs of polynomials, the s-polynomial is computed, by mutually eliminating the leading terms of these 2 polynomials. It's remainder (after division by the base) is then added. Sometimes, it is easy to see, that one s-polynomial will be reduced to 0 before computing it. At the end, the base is reduced, by trying to eliminate as many terms as possible with the leading terms of other base elements. The final step is to make all polynomials monic. References: =========== Cox, Little, O'Shea: Ideals, Varieties and Algorithms, Springer, 2. edition, p. 87 """ if isinstance(f, Basic): f = [f] if not isinstance(f[0], Polynomial): if var is None: var = merge_var(*map(lambda p: p.atoms(type=Symbol), f)) if isinstance(var, Symbol): var = [var] f = map(lambda p: Polynomial(p, var=var, order=order), f) # Filter out the zero elements. f = filter(lambda p: p.sympy_expr is not S.Zero, f) # Empty Ideal. if len(f) == 0: return [Polynomial(S.Zero)] # Stores the unchecked combinations for s-poly's. b = [] s = len(f) for i in range(0, s - 1): for j in range(i + 1, s): b.append((i, j)) while b: # TODO: Choose better (non-arbitrary) pair: sugar method? i, j = b[0] crit = False lcm = term_lcm(f[i].coeffs[0], f[j].coeffs[0]) # Check if leading terms are relativly prime. if lcm[1:] != term_mult(f[i].coeffs[0], f[j].coeffs[0])[1:]: # TODO: Don't operate on the whole lists, do nested ifs instead? kk = filter(lambda k: k != i and k != j, range(0, s)) kk = filter(lambda k: not (min(i, k), max(i, k)) in b, kk) kk = filter(lambda k: not (min(j, k), max(j, k)) in b, kk) # Check if the lcm is divisible by another base element. kk = filter(lambda k: term_is_mult(lcm, f[k].coeffs[0]), kk) crit = not bool(kk) if crit: factor_i = Polynomial(coeffs=(term_div(lcm, f[i].coeffs[0]),), var=f[0].var, order=f[0].order) factor_j = Polynomial(coeffs=(term_div(lcm, f[j].coeffs[0]),), var=f[0].var, order=f[0].order) s_poly = f[i] * factor_i - f[j] * factor_j s_poly = div_.div(s_poly, f)[-1] # reduce if s_poly.sympy_expr is not S.Zero: # we still have to add it to the base. s += 1 f.append(s_poly) for t in range(0, s - 1): # With a new element come b.append((t, s - 1)) # new combinationas to test. b = b[1:] # Checked one more. # We now have one possible Groebner base, probably too big. if not reduced: return f # We can get rid of all elements, where the leading term can be # reduced in the ideal of the remaining leading terms, that is, # can be divided by one of the other leading terms. blacklist = [] for p in f: if filter(lambda x: term_is_mult(p.coeffs[0], x.coeffs[0]), filter(lambda x: not x in blacklist and x != p, f)): blacklist.append(p) for p in blacklist: f.remove(p) # We can now sort the basis elements according to their leading # term. f.sort(cmp=lambda a, b: term_cmp(a.coeffs[0], b.coeffs[0], a.order), reverse=True) # Divide all basis elements by their leading coefficient, to get a # leading 1. for i, p in enumerate(f): c = p.coeffs[0][0] f[i] = Polynomial(coeffs=tuple(map(lambda t: (t[0] / c,) + t[1:], p.coeffs)), var=p.var, order=p.order) # We now have a minimal Groebner basis, which is still not unique. # The next step is to reduce all basis elements in respect to the # rest of the base (without touching the leading terms). # As the basis is already sorted, the rest gets smaller each time. for i, p in enumerate(f[0:-1]): pp = div_.div(p, f[i + 1 :])[-1] f[i] = pp return f