【问题标题】:Durand-kerner implementation doesn't workDurand-kerner 实现不起作用
【发布时间】:2010-10-30 07:14:38
【问题描述】:

这个 Durand-Kerner 算法 (here) 的实现有什么问题?

def durand_kerner(poly, start=complex(.4, .9), epsilon=10**-16):#float('-inf')):
    roots = []
    for e in xrange(poly.degree):
        roots.append(start ** e)
    while True:
        new = []
        for i, r in enumerate(roots):
            new_r = r - (poly(r))/(reduce(operator.mul, [(r - r_1) for j, r_1 in enumerate(roots) if i != j]))
            new.append(new_r)
        if all(n == roots[i] or abs(n - roots[i]) < epsilon for i, n in enumerate(new)):
            return new
        roots = new

当我尝试它时,我必须用 KeyboardInterrupt 停止它,因为它不会停止!
polypypol 库的多项式实例。

提前谢谢你, 魔方

编辑:使用 numpy 多项式需要 9 次迭代:

In [1]: import numpy as np

In [2]: roots.d1(np.poly1d([1, -3, 3, -5]))
3
[(1.3607734793516519+2.0222302921553128j), (-1.3982133295376746-0.69356635962504309j), (3.0374398501860234-1.3286639325302696j)]
[(0.98096328371966801+1.3474626910848715j), (-0.3352519326012724-0.64406860772816388j), (2.3542886488816044-0.70339408335670761j)]
[(0.31718054925650596+0.93649454851955749j), (0.49001572078718736-0.9661410790307261j), (2.1928037299563066+0.029646530511168612j)]
[(0.20901563897345796+1.5727420147652911j), (0.041206038662691125-1.5275192097633465j), (2.7497783223638508-0.045222805001944255j)]
[(0.21297050700971876+1.3948274731404162j), (0.18467846583682396-1.3845653821841168j), (2.6023510271534573-0.010262090956299326j)]
[(0.20653075193800668+1.374878742771485j), (0.20600107336130213-1.3746529207714699j), (2.5874681747006911-0.00022582200001499547j)]
[(0.20629950692533283+1.3747296033941407j), (0.20629947661265013-1.374729584400741j), (2.5874010164620169-1.899339978055233e-08j)]
[(0.20629947401589896+1.3747296369986031j), (0.20629947401590082-1.3747296369986042j), (2.5874010519682002+9.1830687539942581e-16j)]
[(0.20629947401590029+1.3747296369986026j), (0.20629947401590026-1.3747296369986026j), (2.5874010519681994+1.1832913578315177e-30j)]
Out[2]: 
[(0.20629947401590029+1.3747296369986026j),
 (0.20629947401590029-1.3747296369986026j),
 (2.5874010519681994+0j)]

使用 pypol 多项式它永远不会完成(这可能是 pypol 中的一个错误):

In [3]: roots.d2(poly1d([1, -3, 3, -5]))
^C---------------------------------------------------------------------------
KeyboardInterrupt

但我找不到错误!

EDIT2:将 __call__ 方法与 Martin 的 Poly 进行比较:

>>> p = Poly(-5, 3, -3, 1)
>>> from pypol import poly1d
>>> p2 = poly1d([1, -3, 3, -5])

>>> for i in xrange(-100000, 100000):
    assert p(i) == p2(i)


>>>
>>> for i in xrange(-10000, 10000):
    assert p(complex(1, i)) == p2(complex(1, i))


>>> for i in xrange(-10000, 10000):
    assert p(complex(i, i)) == p2(complex(i, i))


>>> 

EDIT3:如果根不是复数,pypol 可以正常工作:

In [1]: p = pypol.funcs.from_roots([4, -2, 443, -11212])

In [2]: durand_kerner(p)
Out[2]: [(4+0j), (443+0j), (-2+0j), (-11212+0j)]

所以它只在根是复数时才有效!

EDIT4:我为 numpy 多项式编写了一个稍微不同的实现,并看到在一次迭代之后(维基百科多项式的)根是不同的:

In [4]: d1(numpyp.poly1d([1, -3, 3, -5]))
Out[4]: 
[(0.98096328371966801+1.3474626910848715j),
 (-0.3352519326012724-0.64406860772816388j),
 (2.3542886488816044-0.70339408335670761j)]

In [5]: d2(pypol.poly1d([1, -3, 3, -5]))
Out[5]: 
[(0.9809632837196679+1.3474626910848717j),
 (-0.33525193260127306-0.64406860772816377j),
 (2.3542886488816048-0.70339408335670772j)] ## here

EDIT5:嘿!如果我将行:if all(n == roots[i] ... ) 更改为 if all(str(n) == str(roots[i]) ... ),它将完成并返回正确的根!!!

In [9]: p = pypol.poly1d([1, -3, 3, -5])

In [10]: roots.durand_kerner(p)
Out[10]: 
[(0.20629947401590029+1.3747296369986026j),
 (0.20629947401590013-1.3747296369986026j),
 (2.5874010519681994+0j)]

但问题是:为什么它适用于不同的复数比较??

更新
现在它可以工作了,我已经做了一些测试:

In [1]: p = pypol.poly1d([1, -3, 3, -1])

In [2]: p
Out[2]: + x^3 - 3x^2 + 3x - 1

In [3]: pypol.roots.cubic(p)
Out[3]: (1.0, 1.0, 1.0)

In [4]: durand_kerner(p)
Out[4]: 
((1+0j),
 (1.0000002484566535-2.708692281244913e-17j),
 (0.99999975147728026+2.9792265510301965e-17j))

In [5]: q = x ** 3 - 1

In [6]: q
Out[6]: + x^3 - 1

In [7]: pypol.roots.cubic(q)
Out[7]: (1.0, (-0.5+0.8660254037844386j), (-0.5-0.8660254037844386j))

In [8]: durand_kerner(q)
Out[8]: ((1+0j), (-0.5-0.8660254037844386j), (-0.5+0.8660254037844386j))

【问题讨论】:

  • 评论,因为这只是一个猜测(或复制/粘贴问题),但 if all... 行是否应该缩进,从而在 for i,r... 循环中?目前,i 将始终是 enumerate(roots) 中的最后一个值。
  • 这不是复制/过去的问题,它不在 for 循环中,因为我必须检查 所有 元素,而不仅仅是当前元素。不过我也可以试试if r == roots[i] or abs(r - roots[i]) &lt; epsilon: return new
  • 如果我改变它只返回一个不正确的根...
  • 在浮点精度范围内对我来说似乎工作正常。你能举一个输入失败的例子吗?
  • 您是否尝试过将“print new”放入循环中,并将结果与​​ Wikipedia 文章中的示例给出的 6 次迭代表进行比较?哪个根引起了麻烦?为什么?预期的根是否如此之大,以至于尾数中仅 1 ulp 的差异仍然大于 epsilon?

标签: python algorithm infinite-loop polynomial-math


【解决方案1】:

你的算法看起来不错,它适用于维基百科中的示例

import operator
class Poly:
    def __init__(self, *koeff):
        self.koeff = koeff
        self.degree = len(koeff)-1

    def __call__(self, val):
        res = 0
        x = 1
        for k in self.koeff:
            res += x*k
            x *= val
        return res

def durand_kerner(poly, start=complex(.4, .9), epsilon=10**-16):#float('-inf')):
    roots = []
    for e in xrange(poly.degree):
        roots.append(start ** e)
    while True:
        new = []
        for i, r in enumerate(roots):
            new_r = r - (poly(r))/(reduce(operator.mul, [(r - r_1) 
                                     for j, r_1 in enumerate(roots) if i != j]))
            new.append(new_r)
        if all((n == roots[i] or abs(n - roots[i]) < epsilon) for i, n in enumerate(new)):
            return new
        roots = new

print durand_kerner(Poly(-5,3,-3,1))

给予

[(0.20629947401590026+1.3747296369986026j), 
 (0.20629947401590026-1.3747296369986026j), 
 (2.5874010519681994+8.6361685550944446e-78j)]

【讨论】:

  • 我可以确认 Martin 的结果(自制的 Poly 类略有不同)。更多信息:进行了 12 次迭代;运行 Windows XP SP3 的 AMD Turion 64 CPU 上的 Python 2.7(32 位版本)。也得到了解决 X**3 == 1 的正确结果。
  • 其实 Poly([-1,0,0,1]) 需要更大的 epsilon;使用默认的 epsilon,两个复数根随着 abs(prev_root - curr_root) 在 1.1102230246251565e-16 上振荡
【解决方案2】:

关于您的“EDIT 5”:发生这种情况是因为 str() 没有将数字格式化为完整精度。

>>> print str((2.5874010519681994+8.636168555094445e-78j))
(2.58740105197+8.63616855509e-78j)
>>> print repr((2.5874010519681994+8.636168555094445e-78j))
(2.5874010519681994+8.636168555094445e-78j)
>>>

所以不要那样做。

无论如何,代码中的相等性测试:

if all(n == roots[i] or abs(n - roots[i]) < epsilon for i, n in enumerate(new)):

是多余的;如果n == roots[i],那么abs(n - roots[i]) 将为零,所以你可以这样做

if all(abs(n - roots[i]) < epsilon for i, n in enumerate(new)):

并花一些精力来确定epsilon 的默认值应该是什么;正如我在评论中指出的那样,解决 X**3 == 1 会收敛,但您的默认 epsilon 太小,无法永远停止循环。 1.12e-16 看起来是默认 epsilon 的更好选择。

为了消遣,试试不适合的 Poly([-1, 3, -3, 1]) ...三个根都等于 (1+0j) ...它需要 600 多次迭代,并且在最后 10 次左右的迭代惊人地跳跃,直到它刚刚从左侧字段的远处得出一个合理的解决方案。

【讨论】:

  • 谢谢!!我做了一些测试;但我不能在这里过去,所以请查看我的第一篇文章。
  • 但是如果我必须找到三次多项式的根,我更喜欢使用pypol.roots.cubic
  • @rubik:以 degree-3 poly 为例,说明您的默认 epsilon 可能不起作用。你能证明只有 3 度多边形会出现振荡问题吗?
  • 不,我不能,但这是一个例子。是否有适用于所有多项式的 epsilon 值?我不这么认为。但是 1.12e-16 是一个很好的价值,最终用户将能够更改它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-27
  • 2016-04-28
相关资源
最近更新 更多