=================================
Finding real roots of polynomials
=================================

real roots
----------

This section deals with finding roots of polynomials in the field of
real numbers.

Without loss of generality, the coefficients :math:`a_i` of a polynomial

.. math:: p = a_nx^n+\ldots+a_0

can be considered to be rational numbers, as real-valued numbers are
truncated in practice, when doing calculations on a computer.

Assuming that the leading coefficient :math:`a_n=1`, the polynomial
:math:`p` can also be written as

.. math:: p = p_1^{n_1}\ldots p_m^{n_m}

where :math:`p_i` are the :math:`m` distinct irreducible monic factors
of the form :math:`p_i=x-x_i`, and :math:`n_i` are multiplicities of
the factors. Here the roots are :math:`x_i` and some of them may be
complex. However, complex roots of a polynomial with real coefficients
always come in conjugate pairs, so the corresponding irreducible
factors should be taken as :math:`p_i=x^2+c_ix+d_i`. In this case,
there will be less than :math:`m` irreducible factors, and all
coefficients will be real.

square free decomposition
-------------------------

To find roots, it is useful to first remove the multiplicities,
i.e. to convert the polynomial to one with multiplicity 1 for all
irreducible factors, i.e. find the polynomial :math:`p_1\ldots
p_m`. This is called the *square-free part* of the original polynomial
:math:`p`.

The square-free part of the polynomial :math:`p` can be easily found
using the polynomial GCD algorithm. The derivative of a polynomial
:math:`p` can be written as:

.. math:: p' = \sum_{i=1}^m p_1^{n_1}\ldots n_ip_i^{n_i-1}\frac{dp_i}{dx}\ldots p_m^{n_m}

The GCD of :math:`p` and :math:`p'` equals

.. math:: \gcd(p,p') = p_1^{n_1-1}\ldots p_m^{n_m-1}

So if we divide :math:`p` by :math:`\gcd(p,p')`, we get the square-free
part of the polynomial:

.. math:: \mathrm{SquareFree}(p) := \frac{p}{\gcd(p,p')} = p_1\ldots p_m.

In what follows we shall assume that all polynomials are square-free
with rational coefficients.  Given any polynomial, we can apply the
functions :func:`SquareFree` and :func:`Rationalize` and reduce it to this form.
The function :func:`Rationalize` converts all numbers in an expression to
rational numbers. The function :func:`SquareFree` returns the square-free
part of a polynomial. For example::

  In> Expand((x+1.5)^5)
  Out> x^5+7.5*x^4+22.5*x^3+33.75*x^2+25.3125*x +7.59375;
  In> SquareFree(Rationalize(%))
  Out> x/5+3/10;
  In> Simplify(%*5)
  Out> (2*x+3)/2;
  In> Expand(%)
  Out> x+3/2;

Sturm sequences
---------------

For a polynomial :math:`p(x)` of degree :math:`n`, the Sturm sequence
:math:`p_0, p_1,\dots,p_n` is defined by the following equations
(following :cite:`davenport1988:computer_algebra`):

.. math::
   :nowrap:

   \begin{eqnarray}
   p_0 & = & p(x) \\
   p_1 & = & p'(x) \\
   p_i & = & -(p_{i-2} \bmod p_{i-1}).
   \end{eqnarray}

The polynomial :math:`p` can be assumed to have no multiple factors, and
thus :math:`p` and :math:`p'` are relatively prime. The sequence of
polynomials in the Sturm sequence are (up to a minus sign) the
consecutive polynomials generated by Euclid's algorithm for the
calculation of a greatest common divisor for :math:`p` and :math:`p'`, so the
last polynomial :math:`p_n` will be a constant.

In yacas, the function :func:`SturmSequence` returns the Sturm sequence
of :math:`p`, assuming :math:`p` is a univariate polynomial in :math:`x`,
:math:`p = p(x)`.

variations in Sturm sequences
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Given a Sturm sequence :math:`S` of a polynomial :math:`p`,
the *variation* in the Sturm sequence :math:`V(S,y)` is the number of
sign changes in the sequence :math:`p_0,p_1,\ldots,p_n`,
evaluated at point :math:`y`, and disregarding zeroes in the sequence.

Sturm's theorem states that if :math:`a` and :math:`b` are two real numbers
which are not roots of :math:`p`, and :math:`a < b`, then the number of roots
between :math:`a` and :math:`b` is :math:`V(S,a) - V(S,b)`. A proof can be
found in Knuth, <I>The Art of Computer Programming, Volume 2, Seminumerical
Algorithms</I>.

For :math:`a` and :math:`b`, the values :math:`-\infty` and :math:`\infty` can
also be used. In these cases, :math:`V(S,\infty)` is the number of sign
changes between the leading coefficients of the elements of the Sturm
sequence, and :math:`V(S,-\infty)` the same, but with a minus sign for
the leading coefficients for which the degree is odd.

Number of real roots
^^^^^^^^^^^^^^^^^^^^

Thus, the number of real roots of a polynomial is :math:`V(S,-\infty) -
V(S,\infty)`. The function :func:`NumRealRoots` returns the number of
real roots of :math:`p`.

The function :func:`SturmVariations` returns the number of sign changes
between the elements in the Sturm sequence :math:`S`, at point :math:`x = y`::

  In> p:=x^2-1
  Out> x^2-1;
  In> S:=SturmSequence(p)
  Out> {x^2-1,2*x,1};
  In> SturmVariations(S,-Infinity)-SturmVariations(S,Infinity)
  Out> 2;
  In> NumRealRoots(p)
  Out> 2;
  In> p:=x^2+1
  Out> x^2+1;
  In> S:=SturmSequence(p)
  Out> {x^2+1,2*x,-1};
  In> SturmVariations(S,-Infinity)-SturmVariations(S,Infinity)
  Out> 0;
  In> NumRealRoots(p)
  Out> 0;


Finding bounds on real roots
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Armed with the variations in the Sturm sequence given in the previous
section, we can now find the number of real roots in a range
:math:`(a,b)`, for :math:`a < b`. We can thus bound all the roots by
subdividing ranges until there is only one root in each range.  To be
able to start this process, we first need some upper bounds of the
roots, or an interval that contains all roots. Davenport gives limits
on the roots of a polynomial given the coefficients of the polynomial,
as

.. math:: |a| \le \max\left(\left|\frac{a_{n-1}}{a_n}\right|,\left|\frac{a_{n-2}}{a_{n}}\right|^{\frac{1}{2}},\ldots,\left|\frac{a_{0}}{a_{n}}\right|^{\frac{1}{n}}\right)

for all real roots :math:`a` of :math:`p`. This gives the upper bound on the
absolute value of the roots of the polynomial in question.  If :math:`p(0)\ne0`
the minimum bound can be obtained also by considering the
upper bound of :math:`p(\frac{1}{x})x^n`, and taking :math:`\frac{1}{bound}`.

We thus know that given

.. math:: a_{max} = \mathrm{MaximumBound}(p)

and

.. math:: a_{min} = \mathrm{MinimumBound}(p)

for all roots :math:`a` of polynomial, either

.. math:: -a_{max}\le a\le -a_{min}

or

.. math:: a_{min}\le a\le a_{max}

Now we can start the search for the bounds on all roots. The search
starts with initial upper and lower bounds on ranges, subdividing
ranges until a range contains only one root, and adding that range to
the resulting list of bounds. If, when dividing a range, the middle of
the range lands on a root, care must be taken, because the bounds
should not be on a root themselves. This can be solved by observing
that if :math:`c` is a root, :math:`p` contains a factor :math:`x-c`, and thus
taking :math:`p(x+c)` results in a polynomial with all the roots shifted
by a constant :math:`-c`, and the root :math:`c` moved to zero, e.g. :math:`p(x+c)`
contains a factor :math:`x`. Thus a new ranges to the left and right of
:math:`c` can be determined by first calculating the minimum bound :math:`M`
of :math:`\frac{p(x+c)}{x}`. When the original range was :math:`(a,b)`, and
:math:`c = \frac{a+b}{2}` is a root, the new ranges should become
:math:`(a,c-M)` and :math:`(c+M,b)`.

In yacas, :func:`MimimumBound` returns the lower bound described above,
and :func:`MaximumBound` returns the upper bound on the roots in :math:`p`.
These bounds are returned as rational numbers.  :func:`BoundRealRoots`
returns a list with sublists with the bounds on the roots of a
polynomial::

  In> p:=(x+20)*(x+10)
  Out> (x+20)*(x+10);
  In> MinimumBound(p)
  Out> 10/3;
  In> MaximumBound(p)
  Out> 60;
  In> BoundRealRoots(p)
  Out> {{-95/3,-35/2},{-35/2,-10/3}};
  In> N(%)
  Out> {{-31.6666666666,-17.5}, {-17.5,-3.3333333333}};

It should be noted that since all calculations are done with rational
numbers, the algorithm for bounding the roots is very robust. This is
important, as the roots can be very unstable for small variations in
the coefficients of the polynomial in question (see Davenport).

Finding real roots given the bounds on the roots
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Given the bounds on the real roots as determined in the previous
section, two methods for finding roots are available: the secant
method or the Newton method, where the function is locally
approximated by a line, and extrapolated to find a new estimate for a
root. This method converges quickly when "sufficiently" near a root,
but can easily fail otherwise.  The secant method can easily send the
search to infinity.

The bisection method is more robust, but slower. It works by taking
the middle of the range, and checking signs of the polynomial to
select the half-range where the root is.  As there is only one root in
the range :math:`(a,b)`, in general it will be true that :math:`p(a)p(b) < 0`,
which is assumed by this method.

Yacas finds the roots by first trying the secant method, starting in
the middle of the range, :math:`c = \frac{a+b}{2}`. If this fails the
bisection method is tried.

The function call to find the real roots of a polynomial :math:`p` in
variable :math:`x` is :func:`FindRealRoots`, for example::

  In> p:=Expand((x+3.1)*(x-6.23))
  Out> x^2-3.13*x-19.313;
  In> FindRealRoots(p)
  Out> {-3.1,6.23};
  In> p:=Expand((x+3.1)^3*(x-6.23))
  Out> x^4+3.07*x^3-29.109*x^2-149.8199\
  In> *x-185.59793;
  In> p:=SquareFree(Rationalize( \
  In> Expand((x+3.1)^3*(x-6.23))))
  Out> (-160000*x^2+500800*x+3090080)/2611467;
  In> FindRealRoots(p)
  Out> {-3.1,6.23};
