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
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