def __init__(self, n=None, verbose=False): self.n = n prec = dps = Float.getdps() self.prec = prec # Reuse old nodes if n in _gausscache: cacheddps, xs, ws = _gausscache[n] if cacheddps >= dps: self.x = [x for x in xs] self.w = [w for w in ws] return if verbose: print ("calculating nodes for degree-%i Gauss-Legendre " "quadrature..." % n) Float.store() Float.setdps(2*prec + 5) self.x = [None] * n self.w = [None] * n pf = polyfunc(legendre(n, 'x'), True) for k in xrange(n//2 + 1): if verbose and k % 4 == 0: print " node", k, "of", n//2 x, w = _gaussnode(pf, k, n) self.x[k] = x self.x[n-k-1] = -x self.w[k] = self.w[n-k-1] = w _gausscache[n] = (dps, self.x, self.w) Float.revert()
def polyroots(poly, maxsteps=20): """ Numerically locate all (complex) roots of a given polynomial 'poly' using the Durand-Kerner method (http://en.wikipedia.org/wiki/ Durand-Kerner_method) This function returns a tuple (roots, err) where roots is a list of ComplexFloats sorted by absolute value, and err is an estimate of the maximum error. The 'poly' argument should be a SymPy expression representing a univariate polynomial. Example: >>> Float.setdps(15) >>> x = Symbol('x') >>> r, e = polyroots((x-3)*(x-2)) >>> r[0] ComplexFloat(real='1.9999999999999993', imag='-4.5917748078995606E-41') >>> r[1] ComplexFloat(real='3', imag='-4.6222318665293660E-33') >>> e Float('2.2204460492503131E-16') Polyroots attempts to achieve a maximum error less than the epsilon of the current working precision, but may fail to do so if the polynomial is badly conditioned. Usually the error can be reduced by increasing the 'maxsteps' parameter, although this will of course also reduce the speed proprtionally. It may also be necessary to increase the working precision. In particular, polyroots is easily fooled by multiple roots and roots that are very close together. In general, if n-multiple roots are present, polyroots will typically only locate their values to within 1/n of the working precision. For example, with the standard precision of ~15 decimal digits, the expected accuracy for a double root is 7-8 decimals: >>> Float.setdps(15) >>> r, e = polyroots((x-2)**2) >>> r[0] ComplexFloat(real='1.9999999832018203', imag='-2.5469994476319085E-8') >>> r[1] ComplexFloat(real='2.0000000110216827', imag='1.4784776969781909E-8') >>> e Float('5.4076851354766810E-8') An effective cure is to multiply the working precision n times (in the case of a double root, doubling it): >>> Float.setdps(30) >>> r, e = polyroots((x-2)**2, 40) >>> r[0].real Float('1.9999999999999999075008441778756') >>> e Float('1.9055980809840526328094566161453E-16') The result is good to 15 decimals as expected. """ # Must be monic poly = Polynomial(poly) poly = Polynomial(poly / poly.coeffs[0][0]) deg = poly.coeffs[0][1] f = polyfunc(poly) roots = [ComplexFloat(0.4+0.9j)**n for n in range(deg)] error = [Float(1) for n in range(deg)] for step in range(maxsteps): if max(error).ae(0): break for i in range(deg): if not error[i].ae(0): p = roots[i] x = f(p) for j in range(deg): if i != j: try: x /= (p-roots[j]) except ZeroDivisionError: continue roots[i] = p - x error[i] = abs(x) roots.sort(key=abs) err = max(error) err = max(err, Float((1, -Float._prec+1))) return roots, err