# # # Nim's Runtime Library # (c) Copyright 2013 Robert Persson # # See the file "copying.txt", included in this # distribution, for details about the copyright. # import math import strutils import numeric type Poly* = object cofs:seq[float] {.deprecated: [TPoly: Poly].} proc degree*(p:Poly):int= ## Returns the degree of the polynomial, ## that is the number of coefficients-1 return p.cofs.len-1 proc eval*(p:Poly,x:float):float= ## Evaluates a polynomial function value for `x` ## quickly using Horners method var n=p.degree result=p.cofs[n] dec n while n>=0: result = result*x+p.cofs[n] dec n proc `[]` *(p:Poly;idx:int):float= ## Gets a coefficient of the polynomial. ## p[2] will returns the quadric term, p[3] the cubic etc. ## Out of bounds index will return 0.0. if idx<0 or idx>p.degree: return 0.0 return p.cofs[idx] proc `[]=` *(p:var Poly;idx:int,v:float)= ## Sets an coefficient of the polynomial by index. ## p[2] set the quadric term, p[3] the cubic etc. ## If index is out of range for the coefficients, ## the polynomial grows to the smallest needed degree. assert(idx>=0) if idx>p.degree: #polynomial must grow var oldlen=p.cofs.len p.cofs.setLen(idx+1) for q in oldlen.. =0: yield p[i] dec i proc clean*(p:var Poly;zerotol=0.0)= ## Removes leading zero coefficients of the polynomial. ## An optional tolerance can be given for what's considered zero. var n=p.degree var relen=false while n>0 and abs(p[n])<=zerotol: # >0 => keep at least one coefficient dec n relen=true if relen: p.cofs.setLen(n+1) proc `$` *(p:Poly):string = ## Gets a somewhat reasonable string representation of the polynomial ## The format should be compatible with most online function plotters, ## for example directly in google search result="" var first=true #might skip + sign if first coefficient for idx in countdown(p.degree,0): let a=p[idx] if a==0.0: continue if a>= 0.0 and not first: result.add('+') first=false if a!=1.0 or idx==0: result.add(formatFloat(a,ffDefault,0)) if idx>=2: result.add("x^" & $idx) elif idx==1: result.add("x") if result=="": result="0" proc derivative*(p: Poly): Poly= ## Returns a new polynomial, which is the derivative of `p` newSeq[float](result.cofs,p.degree) for idx in 0..high(result.cofs): result.cofs[idx]=p.cofs[idx+1]*float(idx+1) proc diff*(p:Poly,x:float):float= ## Evaluates the differentiation of a polynomial with ## respect to `x` quickly using a modifed Horners method var n=p.degree result=p[n]*float(n) dec n while n>=1: result = result*x+p[n]*float(n) dec n proc integral*(p:Poly):Poly= ## Returns a new polynomial which is the indefinite ## integral of `p`. The constant term is set to 0.0 newSeq(result.cofs,p.cofs.len+1) result.cofs[0]=0.0 #constant arbitrary term, use 0.0 for i in 1..high(result.cofs): result.cofs[i]=p.cofs[i-1]/float(i) proc integrate*(p:Poly;xmin,xmax:float):float= ## Computes the definite integral of `p` between `xmin` and `xmax` ## quickly using a modified version of Horners method var n=p.degree s1=p[n]/float(n+1) s2=s1 fac:float dec n while n>=0: fac=p[n]/float(n+1) s1 = s1*xmin+fac s2 = s2*xmax+fac dec n result=s2*xmax-s1*xmin proc initPoly*(cofs:varargs[float]):Poly= ## Initializes a polynomial with given coefficients. ## The most significant coefficient is first, so to create x^2-2x+3: ## intiPoly(1.0,-2.0,3.0) if len(cofs)<=0: result.cofs= @[0.0] #need at least one coefficient else: # reverse order of coefficients so indexing matches degree of # coefficient... result.cofs= @[] for idx in countdown(cofs.len-1,0): result.cofs.add(cofs[idx]) result.clean #remove leading zero terms proc divMod*(p,d:Poly;q,r:var Poly)= ## Divides `p` with `d`, and stores the quotinent in `q` and ## the remainder in `d` var pdeg=p.degree ddeg=d.degree power=p.degree-d.degree ratio:float r.cofs = p.cofs #initial remainder=numerator if power<0: #denominator is larger than numerator q.cofs= @ [0.0] #quotinent is 0.0 return # keep remainder as numerator q.cofs=newSeq[float](power+1) for i in countdown(pdeg,ddeg): ratio=r.cofs[i]/d.cofs[ddeg] q.cofs[i-ddeg]=ratio r.cofs[i]=0.0 for j in countup(0,=res[high(res)]+mergetol: #dont add equal roots. res.add(br.rootx) else: #this might be a 'touching' case, check function value against #zero tolerance if abs(br.rooty)<=zerotol: if res.len==0 or br.rootx>=res[high(res)]+mergetol: #dont add equal roots. res.add(br.rootx) proc roots*(p:Poly,tol=1.0e-9,zerotol=1.0e-6,mergetol=1.0e-12,maxiter=1000):seq[float]= ## Computes the real roots of the polynomial `p` ## `tol` is the tolerance used to break searching for each root when reached. ## `zerotol` is the tolerance, which is 'close enough' to zero to be considered a root ## and is used to find roots for curves that only 'touch' the x-axis. ## `mergetol` is the tolerance, of which two x-values are considered beeing the same root. ## `maxiter` can be used to limit the number of iterations for each root. ## Returns a (possibly empty) sorted sequence with the solutions. var deg=p.degree if deg<=0: #constant only => no roots return @[] elif p.degree==1: #linear var linrt= -p.cofs[0]/p.cofs[1] if linrt==Inf or linrt==NegInf: return @[] #constant only => no roots return @[linrt] elif p.degree==2: return solveQuadric(p.cofs[2],p.cofs[1],p.cofs[0],zerotol) else: # degree >=3 , find min/max points of polynomial with recursive # derivative and do a numerical search for root between each min/max var range=p.getRangeForRoots() var minmax=p.derivative.roots(tol,zerotol,mergetol) result= @[] if minmax!=nil: #ie. we have minimas/maximas in this function for x in minmax.items: addRoot(p,result,range.xmin,x,tol,zerotol,mergetol,maxiter) range.xmin=x addRoot(p,result,range.xmin,range.xmax,tol,zerotol,mergetol,maxiter)