在python

时间:2016-03-04 12:07:23

标签: python algorithm numpy math scipy

[我想要的] 是找到四分之一函数的唯一最小正实根 a x ^ 4 + b x ^ 3 + c x ^ 2 + d x + e

[现有方法] 我的方程是用于碰撞预测,最大程度是四次函数,因为 f(x) = a x ^ 4 + b x ^ 3 + c x ^ 2 + d x + e a,b,c,d,e coef可以是正/负/零(实际浮动值)。所以我的函数 f(x)可以是四次,三次或二次,取决于a,b,c,d,e输入系数。

目前我使用numpy来查找root如下所示。

import numpy

root_output = numpy.roots([a,b,c,d,e])

来自numpy模块的“ root_output ”可以是所有可能的实/复根,具体取决于输入系数。所以我必须逐个查看“ root_output ”,并检查哪个根是最小的实数正值(root> 0?)

[问题] 我的程序需要多次执行 numpy.roots([a,b,c,d,e]),所以多次执行numpy.roots对我的项目来说太慢了。每次执行numpy.roots时,(a,b,c,d,e)值都会改变

我的尝试是在Raspberry Pi2上运行代码。以下是处理时间的示例。

  • 在PC上运行多次numpy.roots: 1.2秒
  • 在Raspberry Pi2上运行多次numpy.roots: 17秒

您能否指导我如何在最快的解决方案中找到最小的正实根?使用scipy.optimize或实现一些算法来加速寻找根或任何建议将是很好的。

谢谢。

[解决]

  • 二次函数只需要真正的正根(请注意除零)

    def SolvQuadratic(a, b ,c):
        d = (b**2) - (4*a*c)
        if d < 0:
            return []
    
        if d > 0:
            square_root_d = math.sqrt(d)
            t1 = (-b + square_root_d) / (2 * a)
            t2 = (-b - square_root_d) / (2 * a)
            if t1 > 0:
                if t2 > 0:
                    if t1 < t2:
                        return [t1, t2]
                    return [t2, t1]
                return [t1]
            elif t2 > 0:
                return [t2]
            else:
                return []
        else:
            t = -b / (2*a)
            if t > 0:
                return [t]
            return []
    
  • 对于四次函数的
  • 四次函数,您可以使用纯python / numba版本作为来自@ B.M。的以下答案。我还从@ B.M的代码中添加了另一个cython版本。你可以使用下面的代码作为.pyx文件,然后编译它比纯python快2倍(请注意舍入问题)。

    import cmath
    
    cdef extern from "complex.h":
        double complex cexp(double complex)
    
    cdef double complex  J=cexp(2j*cmath.pi/3)
    cdef double complex  Jc=1/J
    
    cdef Cardano(double a, double b, double c, double d):
        cdef double z0
        cdef double a2, b2
        cdef double p ,q, D
        cdef double complex r
        cdef double complex u, v, w
        cdef double w0, w1, w2
        cdef double complex r1, r2, r3
    
    
        z0=b/3/a
        a2,b2 = a*a,b*b
        p=-b2/3/a2 +c/a
        q=(b/27*(2*b2/a2-9*c/a)+d)/a
        D=-4*p*p*p-27*q*q
        r=cmath.sqrt(-D/27+0j)
        u=((-q-r)/2)**0.33333333333333333333333
        v=((-q+r)/2)**0.33333333333333333333333
        w=u*v
        w0=abs(w+p/3)
        w1=abs(w*J+p/3)
        w2=abs(w*Jc+p/3)
        if w0<w1:
          if w2<w0 : v = v*Jc
        elif w2<w1 : v = v*Jc
        else: v = v*J
        r1 = u+v-z0
        r2 = u*J+v*Jc-z0
        r3 = u*Jc+v*J-z0
        return r1, r2, r3
    
    cdef Roots_2(double a, double complex b, double complex c):
        cdef double complex bp
        cdef double complex delta
        cdef double complex r1, r2
    
    
        bp=b/2
        delta=bp*bp-a*c
        r1=(-bp-delta**.5)/a
        r2=-r1-b/a
        return r1, r2
    
    def SolveQuartic(double a, double b, double c, double d, double e):
        "Ferrarai's Method"
        "resolution of P=ax^4+bx^3+cx^2+dx+e=0, coeffs reals"
        "First shift : x= z-b/4/a  =>  P=z^4+pz^2+qz+r"
        cdef double z0
        cdef double a2, b2, c2, d2
        cdef double p, q, r
        cdef double A, B, C, D
        cdef double complex y0, y1, y2
        cdef double complex a0, b0
        cdef double complex r0, r1, r2, r3
    
    
        z0=b/4.0/a
        a2,b2,c2,d2 = a*a,b*b,c*c,d*d
        p = -3.0*b2/(8*a2)+c/a
        q = b*b2/8.0/a/a2 - 1.0/2*b*c/a2 + d/a
        r = -3.0/256*b2*b2/a2/a2 + c*b2/a2/a/16 - b*d/a2/4+e/a
        "Second find y so P2=Ay^3+By^2+Cy+D=0"
        A=8.0
        B=-4*p
        C=-8*r
        D=4*r*p-q*q
        y0,y1,y2=Cardano(A,B,C,D)
        if abs(y1.imag)<abs(y0.imag): y0=y1
        if abs(y2.imag)<abs(y0.imag): y0=y2
        a0=(-p+2*y0)**.5
        if a0==0 : b0=y0**2-r
        else : b0=-q/2/a0
        r0,r1=Roots_2(1,a0,y0+b0)
        r2,r3=Roots_2(1,-a0,y0-b0)
        return (r0-z0,r1-z0,r2-z0,r3-z0)
    

[法拉利方法的问题] 当四次方程的系数为[0.00614656,-0.0933333333333,0.527664995846,-1.31617928376,1.21906444869] numpy.roots和ferrari的输出时,我们面临的问题方法完全不同(numpy.roots是正确的输出)。

    import numpy as np
    import cmath


    J=cmath.exp(2j*cmath.pi/3)
    Jc=1/J

    def ferrari(a,b,c,d,e):
        "Ferrarai's Method"
        "resolution of P=ax^4+bx^3+cx^2+dx+e=0, coeffs reals"
        "First shift : x= z-b/4/a  =>  P=z^4+pz^2+qz+r"
        z0=b/4/a
        a2,b2,c2,d2 = a*a,b*b,c*c,d*d
        p = -3*b2/(8*a2)+c/a
        q = b*b2/8/a/a2 - 1/2*b*c/a2 + d/a
        r = -3/256*b2*b2/a2/a2 +c*b2/a2/a/16-b*d/a2/4+e/a
        "Second find y so P2=Ay^3+By^2+Cy+D=0"
        A=8
        B=-4*p
        C=-8*r
        D=4*r*p-q*q
        y0,y1,y2=Cardano(A,B,C,D)
        if abs(y1.imag)<abs(y0.imag): y0=y1
        if abs(y2.imag)<abs(y0.imag): y0=y2
        a0=(-p+2*y0)**.5
        if a0==0 : b0=y0**2-r
        else : b0=-q/2/a0
        r0,r1=Roots_2(1,a0,y0+b0)
        r2,r3=Roots_2(1,-a0,y0-b0)
        return (r0-z0,r1-z0,r2-z0,r3-z0)

    #~ @jit(nopython=True)
    def Cardano(a,b,c,d):
        z0=b/3/a
        a2,b2 = a*a,b*b
        p=-b2/3/a2 +c/a
        q=(b/27*(2*b2/a2-9*c/a)+d)/a
        D=-4*p*p*p-27*q*q
        r=cmath.sqrt(-D/27+0j)
        u=((-q-r)/2)**0.33333333333333333333333
        v=((-q+r)/2)**0.33333333333333333333333
        w=u*v
        w0=abs(w+p/3)
        w1=abs(w*J+p/3)
        w2=abs(w*Jc+p/3)
        if w0<w1:
          if w2<w0 : v*=Jc
        elif w2<w1 : v*=Jc
        else: v*=J
        return u+v-z0, u*J+v*Jc-z0, u*Jc+v*J-z0

    #~ @jit(nopython=True)
    def Roots_2(a,b,c):
        bp=b/2
        delta=bp*bp-a*c
        r1=(-bp-delta**.5)/a
        r2=-r1-b/a
        return r1,r2

    coef = [0.00614656, -0.0933333333333, 0.527664995846, -1.31617928376, 1.21906444869]
    print("Coefficient A, B, C, D, E", coef) 
    print("") 
    print("numpy roots: ", np.roots(coef)) 
    print("") 
    print("ferrari python ", ferrari(*coef))

3 个答案:

答案 0 :(得分:3)

另一个答案:

使用分析方法(FerrariCardan)执行此操作,并使用及时编译(Numba)加快代码速度:

首先看一下改进:

In [2]: P=poly1d([1,2,3,4],True)

In [3]: roots(P)
Out[3]: array([ 4.,  3.,  2.,  1.])

In [4]: %timeit roots(P)
1000 loops, best of 3: 465 µs per loop

In [5]: ferrari(*P.coeffs)
Out[5]: ((1+0j), (2-0j), (3+0j), (4-0j))

In [5]: %timeit ferrari(*P.coeffs) #pure python without jit
10000 loops, best of 3: 116 µs per loop    
In [6]: %timeit ferrari(*P.coeffs)  # with numba.jit
100000 loops, best of 3: 13 µs per loop

然后丑陋的代码:

订单4:

@jit(nopython=True)
def ferrari(a,b,c,d,e):
    "resolution of P=ax^4+bx^3+cx^2+dx+e=0"
    "CN all coeffs real."
    "First shift : x= z-b/4/a  =>  P=z^4+pz^2+qz+r"
    z0=b/4/a
    a2,b2,c2,d2 = a*a,b*b,c*c,d*d 
    p = -3*b2/(8*a2)+c/a
    q = b*b2/8/a/a2 - 1/2*b*c/a2 + d/a
    r = -3/256*b2*b2/a2/a2 +c*b2/a2/a/16-b*d/a2/4+e/a
    "Second find X so P2=AX^3+BX^2+C^X+D=0"
    A=8
    B=-4*p
    C=-8*r
    D=4*r*p-q*q
    y0,y1,y2=cardan(A,B,C,D)
    if abs(y1.imag)<abs(y0.imag): y0=y1 
    if abs(y2.imag)<abs(y0.imag): y0=y2 
    a0=(-p+2*y0.real)**.5
    if a0==0 : b0=y0**2-r
    else : b0=-q/2/a0
    r0,r1=roots2(1,a0,y0+b0)
    r2,r3=roots2(1,-a0,y0-b0)
    return (r0-z0,r1-z0,r2-z0,r3-z0) 

订单3:

J=exp(2j*pi/3)
Jc=1/J

@jit(nopython=True) 
def cardan(a,b,c,d):
    u=empty(2,complex128)
    z0=b/3/a
    a2,b2 = a*a,b*b    
    p=-b2/3/a2 +c/a
    q=(b/27*(2*b2/a2-9*c/a)+d)/a
    D=-4*p*p*p-27*q*q
    r=sqrt(-D/27+0j)        
    u=((-q-r)/2)**0.33333333333333333333333
    v=((-q+r)/2)**0.33333333333333333333333
    w=u*v
    w0=abs(w+p/3)
    w1=abs(w*J+p/3)
    w2=abs(w*Jc+p/3)
    if w0<w1: 
        if w2<w0 : v*=Jc
    elif w2<w1 : v*=Jc
    else: v*=J        
    return u+v-z0, u*J+v*Jc-z0,u*Jc+v*J-z0

订单2:

@jit(nopython=True)
def roots2(a,b,c):
    bp=b/2    
    delta=bp*bp-a*c
    u1=(-bp-delta**.5)/a
    u2=-u1-b/a
    return u1,u2  

可能需要进一步测试,但效率很高。

答案 1 :(得分:2)

没有循环的numpy解决方案是:

p=array([a,b,c,d,e])
r=roots(p)
r[(r.imag==0) & (r.real>=0) ].real.min()
除非您不需要精确度,否则

scipy.optimize方法会更慢:

In [586]: %timeit r=roots(p);r[(r.imag==0) & (r.real>=0) ].real.min()
1000 loops, best of 3: 334 µs per loop

In [587]: %timeit newton(poly1d(p),10,tol=1e-8)
1000 loops, best of 3: 555 µs per loop

In [588]: %timeit newton(poly1d(p),10,tol=1)
10000 loops, best of 3: 177 µs per loop

然后你必须找到分钟......

修改

对于2倍因子,自己做什么根源:

In [638]: b=zeros((4,4),float);b[1:,:-1]=eye(3)

In [639]: c=b.copy();c[0]=-(p/p[0])[1:];eig(c)[0]
Out[639]: 
array([-7.40849430+0.j        ,  5.77969794+0.j        ,
       -0.18560182+3.48995646j, -0.18560182-3.48995646j])

In [640]: roots(p)
Out[640]: 
array([-7.40849430+0.j        ,  5.77969794+0.j        ,
       -0.18560182+3.48995646j, -0.18560182-3.48995646j])

In [641]: %timeit roots(p)
1000 loops, best of 3: 365 µs per loop

In [642]: %timeit c=b.copy();c[0]=-(p/p[0])[1:];eig(c)
10000 loops, best of 3: 181 µs per loop

答案 2 :(得分:1)

如果提前知道多项式系数,可以通过在roots中给出计算向量来加速(给定Numpy&gt; = 1.10左右):

import numpy as np

def roots_vec(p):
    p = np.atleast_1d(p)
    n = p.shape[-1]
    A = np.zeros(p.shape[:1] + (n-1, n-1), float)
    A[...,1:,:-1] = np.eye(n-2)
    A[...,0,:] = -p[...,1:]/p[...,None,0]
    return np.linalg.eigvals(A)

def roots_loop(p):
    r = []
    for pp in p:
        r.append(np.roots(pp))
    return r

p = np.random.rand(2000, 4)  # 2000 polynomials of 4th order

assert np.allclose(roots_vec(p), roots_loop(p))

In [35]: %timeit roots_vec(p)
100 loops, best of 3: 4.49 ms per loop

In [36]: %timeit roots_loop(p)
10 loops, best of 3: 81.9 ms per loop

它可能无法超越分析解决方案+ Numba,但允许更高的订单。