Ejemplo n.º 1
0
def _laguerre_ ( x , f , d1 = None , d2 = None ) :
    """ Root finding for Bernstein polnomials using Laguerre's method
    - https://en.wikipedia.org/wiki/Laguerre%27s_method
    """
    fn   = f.norm() / 10 
    n    = f.degree()
    
    xmin = f.xmin ()
    xmax = f.xmax ()
    
    l    = xmax - xmin
    
    if not d1 : d1 =  f.derivative()
    if not d2 : d2 = d1.derivative()
    
    if  not f.xmin() <= x <= f.xmax() :
        x = 0.5 * ( xmin + xmax ) 

    if 1 ==  f.degree() :
        
        p0 = f [ 0 ]
        p1 = f [ 1 ]
        
        s0 = signum ( p0 )
        s1 = signum ( p1 )
        
        return ( p1 * xmin - p0 * xmax ) / ( p1 - p0) , 
        

    ## the convergency is cubic, therefore 16 is *very* larger number of iterations 
    for i in  range(16) : 
        
        if  not xmin <= x <= xmax :
            ## something goes wrong: multiple root? go to derivative
            break 

        vx = f.evaluate(x)
        if iszero ( vx ) or isequal ( fn +  vx , fn ) : return x,
        
        G =         d1 ( x ) / vx
        H = G * G - d2 ( x ) / vx
        
        d = math.sqrt ( ( n - 1 ) * ( n * H - G * G ) )
        if G < 0 : d = -d

        a = n / ( G + d ) 

        x -= a

        if isequal ( a + x , x ) : return x,  
        
    ## look for derivative
    r =  _laguerre_ ( x , d1 , d2 , d2.derivative() )
    if r : r = r[:1] + r
    return r
Ejemplo n.º 2
0
def solve (  bp  , C = 0 , split = 2 ) :
    """Solve   equation  B(x) = C, where B(x) is  Bernstein polynomial, and
    C is  e.g.  constant or another polynomial
    >>> bernstein = ...
    >>> roots = bernstein.solve ()
    >>> roots = solve ( bernstein , C = 0 ) ## ditto
    """
    
    ## construct the equation  b'(x)=0:
    bp = bp.bernstein() 
    if C : bp = bp - C
    
    ## 1) zero-degree polynomial
    if   0 == bp.degree() :

        if iszero ( bp[0] ) : return bp.xmin(),
        return () 
    
    ## 2) linear polynomial
    elif 1 == bp.degree() :

        x0 = bp.xmin()
        x1 = bp.xmax()
        
        p0 = bp[0]
        p1 = bp[1]

        s0 = signum ( p0 )
        s1 = signum ( p1 )

        bn = bp.norm() 
        if  iszero ( p0 ) or isequal ( p0 + bn , bn ) : s0 = 0
        if  iszero ( p1 ) or isequal ( p1 + bn , bn ) : s1 = 0
        
        if   s0 ==     0 : return x0,  ## 
        elif s1 ==     0 : return x1,  ##
        elif s0 * s1 > 0 : return ()   ## no roots
        #
        return ( p1 * x0 - p0 * x1 ) / ( p1 - p0) , 

    ## make a copy & scale is needed 
    bp  = _scale_ ( bp + 0 ) 

    ##  norm of polynomial 
    bn = bp.norm()

    ## check number of roots
    nc = bp.sign_changes()
    if not nc : return ()      ## no roots !   RETURN

    ## treat separetely roots at the left and right edges
    roots = []
    while 1 <= bp.degree() and isequal ( bp[0] +  bn , bn ) :
        bp   -= bp[0]
        bp    = bp.deflate_left() 
        bp    = _scale_ ( bp ) 
        bn    = bp.norm ()
        roots.append ( bp.xmin() )        
    if roots : return tuple(roots) + bp.solve ( split = split ) 
    
    roots = []
    while 1 <= bp.degree() and isequal ( bp[-1] +  bn , bn ) :
        bp -= bp[-1]
        bp  = bp.deflate_right() 
        bp  = _scale_ ( bp ) 
        bn  = bp.norm () 
        roots.append ( bp.xmax() )        
    if roots : return bp.solve ( split = split ) + tuple(roots) 

    ## check again number of roots
    nc = bp.sign_changes()
    if not nc : return ()      ## no roots !   RETURN
    
    # =========================================================================
    ## there are  many roots in the interval 
    # =========================================================================


    if 1 < nc :

        xmin = bp.xmin ()
        xmax = bp.xmax ()
        
        lcp  = bp.left_line_hull()
        if ( not lcp is None ) and xmin <= lcp <= xmax : xmin = max ( xmin , lcp )
        
        rcp  = bp.right_line_hull()
        if ( not rcp is None ) and xmin <= rcp <= xmax : xmax = min ( xmax , rcp )

        # =====================================================================
        ## Two strategies for isolation of roots:
        #  - use control polygon 
        #  - use the derivative 
        #  For both cases, the zeros of contol-polygon and/or derivatives
        #  are tested to be the roots of  polynomial..
        #  If they corresponds to the roots, polinomial is properly deflated and
        #  deflated roots are collected
        #  Remaining points are used to (recursively) split interval into smaller
        #  intervals with presumably smaller number of roots 
        
        #
        if 0 < split :

            cps = bp.crossing_points()
            splits = [ xmin ]
            for xp in cps :
                if xmin < xp < xmax : splits.append ( xp )
            splits.append ( xmax ) 

            split -= 1
            
        else :
            
            ## use the roots of derivative 
            dd = bp.derivative()
            rd = dd.solve ( split = split )
            if not rd :
                ## use bisection 
                nrd = xmin, 0.5 * ( xmin + xmax ), xmax
            else :
                ## use roots of derivative 
                nrd = [ xmin , xmax ] 
                for  r in  rd :
                    if xmin < r < xmax :
                        found = False 
                        for  rr in nrd :
                            if isequal ( r , rr ) :
                                found = True
                                break 
                        if not found : nrd.append ( r )
                nrd.sort()                
            splits =  list(nrd)
            
        ## use simple bisection
        if 2 >= len ( splits ) :
            if xmin < xmax : splits =     xmin   , 0.5*(   xmin  +   xmax  ) ,    xmax
            else           : splits =  bp.xmin() , 0.5*(bp.xmin()+bp.xmax()) , bp.xmax()
            splits = list(splits) 
        
        roots = []
        for s in splits :
            
            bv  = bp.evaluate ( s ) 
            bn = bp.norm() 
            while 1 <= bp.degree() and isequal ( bv + bn , bn ) :
                bp -= bv 
                bp  = bp.deflate  ( s  )
                bp  = _scale_     ( bp ) 
                bn  = bp.norm     (    )
                bv  = bp.evaluate ( s  ) 
                roots.append  ( s )
                for q in splits :
                    if  isequal ( q , s ) :
                        splits.remove ( q )
                
        if roots :
            roots += list ( bp.solve ( split =  ( split - 1 ) ) )
            roots.sort()
            return tuple(roots)

        if 2 == len(splits) :
            if isequal ( bp.xmin() , splits[0] ) and isequal ( bp.xmax() , splits[1] ) :
                xmn    = splits[0]
                xmx    = splits[1]
                splits = [ xmn , 0.5*(xmn+xmx) , xmx ] 
                
        ns = len(splits)
        for i in range(1,ns) :
            xl = splits[i-1]
            xr = splits[i  ]            
            bb = _scale_ ( Bernstein ( bp , xl , xr ) )
            roots += bb.solve ( split = ( split - 1 ) )

        roots.sort()
        return  tuple ( roots )   ##  RETURN


    # =========================================================================
    ## there is exactly 1 root here
    # =========================================================================
    
    l0 = ( bp.xmax() -  bp.xmin() ) / ( bp.degree() + 1 )

    ## trivial case 
    if 1 == bp.degree() :        
        y0 =  bp[ 0]
        y1 =  bp[-1]
        x  = ( y1 * bp.xmin() -  y0 * bp.xmax() ) / ( y1 - y0)
        return x,
    
    ## make several iterations (not to much) for better isolation of root
    for i in range ( bp.degree() + 1 ) : 

        xmin = bp.xmin ()
        xmax = bp.xmax ()
        bn   = bp.norm ()

        lcp = bp.left_line_hull  ()
        if not lcp is None :
            if   isequal ( lcp , xmin ) : return  lcp,                      ## RETURN
            elif lcp <= xmax or isequal ( lcp , xmax ) : 
                bv =  bp.evaluate ( lcp )
                if  iszero ( bv ) or isequal ( bv + bn , bn ) : return lcp, ## RETURN
            xmin = max ( xmin , lcp )
                
        rcp = bp.right_line_hull () 
        if not rcp is None :
            if   isequal ( lcp , xmax ) : return  rcp,                      ## RETURN
            elif lcp >= xmin or isequal ( lcp , xmin ) : 
                bv =  bp.evaluate ( rcp )
                if  iszero ( bv ) or isequal ( bv + bn , bn ) : return rcp, ## RETURN
            xmax = min ( xmax , rcp )

        ## 
        if isequal ( xmin , xmax ) : return 0.5*(xmin+xmax),               ## RETURN

        if xmin >= xmax : break                             ## BREAK
        
        ## avoid too iterations - it decreased the precision 
        if 10 * ( xmax - xmin )  < l0  : break              ## BREAK  
        
        s0  =  signum ( bp[ 0] )
        s1  =  signum ( bp[-1] )
        
        smn =  signum ( bp.evaluate ( xmin ) )
        smx =  signum ( bp.evaluate ( xmax ) )

        ##  we have lost the root ?
        if ( s0 * s1 ) * ( smn * smx ) < 0 :  break           ## BREAK 

        ## refine the polynomial: 
        _bp = Bernstein ( bp , xmin , xmax )
        _bp = _scale_ ( _bp )
        _nc = _bp.sign_changes()

        ## we have lost the root 
        if not _nc : break                                   ## BREAK 

        bp = _bp 
        bn = bp.norm()

    # =========================================================================
    ## start the of root-polishing machinery
    # ========================================================================= 

    f = bp 
    d1 = f .derivative ()
    d2 = d1.derivative ()
    
    cps = bp.crossing_points()
    if cps : x = cps[0]
    else   : x = 0.5*(bp.xmin()+bp.xmax()) 

    ##  use Laguerre's method to refine the isolated root 
    l = _laguerre_ ( x , f , d1 , d2 )

    return l