MathGroup Archive 2001

[Date Index] [Thread Index] [Author Index]

Search the Archive

Re: roots

  • To: mathgroup at smc.vnet.net
  • Subject: [mg29595] Re: [mg29380] roots
  • From: Daniel Lichtblau <danl at wolfram.com>
  • Date: Wed, 27 Jun 2001 05:12:29 -0400 (EDT)
  • References: <200106160647.CAA07743@smc.vnet.net>
  • Sender: owner-wri-mathgroup at wolfram.com

maarten.vanderburgt at icos.be wrote:
> 
> hallo,
> 
> What is the easiest solution to finding the 13 (real) roots of 0.05 x +
> Cos[x] == 0.
> I can be easily seen that there are 13 roots by plotting:
> Plot[{0.05 x,-Cos[x]},{x,-30,30}]
> but this is not sufficient for finding accurate numerical values.
> 
> FindRoot only gives one value and it is hard to predict which root it will find with a specified start value:
> 
> In[83]:= FindRoot[0.05 x  +Cos[x] == 0,{x,0}]
> 
> Out[83]= {x -> -4.96317}
> 
> There are three roots closer to 0 then x == -4.96317
> 
> An NSolve only works for polynomials.
> 
> Is there no simple way to find all roots of such an equation, eventually within a specified range.
> 
> thanks for your help
> 
> Maarten van der Burgt
> Leuven

Simplicity is in the eye of the beholder. If you have a plot you trust,
perhaps best is to use FindRoot on what appear to the eye to be close
approximations, and then verify visually that you got all the roots.
Below I show a more general approach that does not rely on Plot to find
all the axis crossings. This method makes good use of the Cauchy
integral formula. We will look for all roots between -20 and 20. To do
so we will use contour integration in a narrow rectangle around this
segment, using a vertical offset of 1/10.

bound = 20;
epsilon = 10^(-1);
f[z_] := 1/20*z+Cos[z]
Off[NIntegrate::slwcon]

segmentIntegrate[func_,a_,b_,offset_,z_, f2_:1] := 
  Module[{t1,t2,t3,integrand=f2*D[func,z]/func},
    t1 = NIntegrate[
        Evaluate[(integrand/.z->z-I*offset)-
		  (integrand/.z->z+I*offset)], {z,a,b},MaxRecursion->10];
    t2= NIntegrate[Evaluate[integrand], {z,a+I*offset,a-I*offset},
        MaxRecursion->10];
    t3 = NIntegrate[Evaluate[integrand], {z,b-I*offset,b+I*offset},
        MaxRecursion->10];
    (t1+t2+t3)/(2*Pi*I)]

I grouped two terms together below rather than simply doing one path
integral. This is to achieve cancellation on terms that might be
problematic along long (say, infinite or semi-infinite) segments. For
more general paths (see remark (vii) below) one would not do this.

The above can be used to count roots on a segment. We use it for
interval-bisection in order to isolate roots.

isolateRoots[func_,lower_,upper_,z_,eps_] := 
  Module[{numroots,avg=(lower+upper)/2,leftroots},
    numroots = Round[segmentIntegrate[func,lower,upper,eps,z]];
    If[numroots==0, Return[{}]];
    If[numroots==1, Return[{Interval[{lower,upper}]}]];
    leftroots = isolateRoots[func,lower,avg,z,eps];
    If[Length[leftroots]==numroots,Return[leftroots]];
    Join[leftroots,isolateRoots[func,avg,upper,z,eps]]
    ]

We'll get intervals for the example at hand.

In[11]:= InputForm[Timing[rootintervals =
isolateRoots[f[z],-bound,bound,z,epsilon]]]

Out[11]//InputForm= 
{2.1999999999999997*Second, {Interval[{-20, -75/4}], 
  Interval[{-75/4, -35/2}], Interval[{-15, -25/2}], Interval[{-25/2,
-10}], 
  Interval[{-10, -5}], Interval[{-5, -5/2}], Interval[{-5/2, 0}], 
  Interval[{0, 5/2}], Interval[{5/2, 5}], Interval[{5, 10}], 
  Interval[{10, 25/2}], Interval[{25/2, 15}], Interval[{15, 20}]}}


Now that we have one root in each interval, we use CIF again but with
multiplier function z in order to evaluate the sum of all z-values for z
a root. This simply gives the roots themselves.

In[12]:= refineRoots[func_,intervals_,eps_,z_]:=
  Map[Re[segmentIntegrate[func,#[[1,1]],#[[1,2]],eps,z,z]]&, intervals]
         
In[13]:= Timing[rts=refineRoots[f[z],rootintervals,epsilon,z]]
Out[13]= {0.65 Second, {-19.1433, -18.4538, -13.4028, -11.6152,
-7.47114, 
  -4.96317, -1.49593, 1.65357, 4.48616, 8.28087, 10.446, 14.984,
16.324}}

We check that the residuals are suitably small.

In[14]:= Max[Abs[f[z] /. MapThread[{z -> #} &, {rts}]]] // InputForm
Out[14]//InputForm= 4.854923130181987*^-10


Let me point out some of the subtle issues involved in this method.

(i) It depends on numerical integration. I did no error checking but one
should check that the root-counting phase gets values that are "close"
to integers. 

(ii) Depending on the integrand, one may want to tune NIntegrate beyond
setting the MaxRecursion option to a nondefault value.

(iii) The function f[z] must be analytic in a neighborhood of the
segment of interest. It must have no zeroes off the segment but in that
neighborhood. This affects the choice of epsilon. On the other hand,
making it too small may make NIntegrate work quite hard (possibly
nondefault WorkingPrecision would be needed).

(iv) In particular, branch cuts or poles will cause trouble. Poles, for
example, will silently cancel with zeroes and thus mess up the root
count.

(v) The code I used above assumes that there are no multiple roots. A
way to handle thos would be to isolate to some tolerance (say,
10^6*$MachineEpsilon). If the root counting (isolation) step still
claims there is more than one root, call it a multiple root, do the
refinement computation as above, and divide that value by the
multiplicity computed in the isolation step.

(vi) The code above assumes there is no root at an endpoint of the
segment. It would be good to check endpoints explicitly before each
isolation step. Any time a root is found at an endpoint, isolate it
(accounting for multiplicity, if any, as per (v) above). Then move
inward a small amount (that is, take a neighborhood of that endpoint),
check that there are no extra roots in this neighborhood of the endpoint
not accounted for by that endpoint, and take for a new endpoint a value
midway between the original endpoint and the boundary of the small
neighborhood. In this way we overlap with a small region that we know
has no roots, but still miss the endpoint that was a root.

(vii) These subtleties notwithstanding, the method is quite nice. For
one, it generalizes to arbitrary regions provided one has a way to
subdivide and to specify the boundaries for the contour integrations.

(viii) Moreover it may be extended to higher dimensions (n equations in
n variables). I am not conversant with the details, but will note that
the theoretical groundwork was done by L. Aizenberg and colleages
regarding multivariate residues. Practical issues in implementation (and
some theory) were addressed in recent work by P. Kravanja. I believe he
considers primarily polydisk regions but the same ideas will work for
e.g. real regions in C^n. The main drawback so far as I am aware is that
it is a challenge to do sufficiently fast and accurate quadrature as
dimension goes up. So a multivariate extension may not be practical at
this time beyond low dimensions. All the same, it provides an
interesting approach to e.g. the "real solutions" problem wherein one
has a polynomial system with many solutions but is only interested in
the relatively small number of real ones. In this case complex solvers
are generally not of much use, and local root-finders only help when
reasonable starting points are known to all desired actual solutions.

I would like to make some general remarks of an editorial-page nature.
While it may not be a universally held opinion, one sometimes hears in
this group that Mathematica is not terribly well suited for computations
that are primarily numeric in nature. Indeed, a few months back the
question arose as to what might be an ideal language in which to write a
"foolproof" (so far as one can obtain) code to find all roots of a
univariate function in a given interval; exactly the problem again at
hand today. My opinion, which I back with the code above, is that
Mathematica is an excellent candidate for this task. Moreover I note
that the code is essentially all numeric. Yes, you can instead use C,
load QUADPACK for the numeric integrations, and so forth (provided you
don't need bignum capabilities). But I think the simplicity of the
Mathematica approach, relative ease of
development/extension/maintainance (and in particular the easy ability
to influence the quadrature steps with options) argue well that
Mathematica is a fine choice for the job.


Daniel Lichtblau
Wolfram Research


  • References:
    • roots
      • From: maarten.vanderburgt@icos.be
  • Prev by Date: Re: Unappropiate context in a package
  • Next by Date: Problems with NonlinearFit
  • Previous by thread: roots
  • Next by thread: Re: roots