Newton’s Method, Deflation and Multiplicity¶
The instructions in the code cells in this chapter require the following functions to be imported:
from phcpy.solutions import make_solution, strsol2dict
from phcpy.deflation import double_newton_step
from phcpy.deflation import double_double_newton_step
from phcpy.deflation import quad_double_newton_step
from phcpy.deflation import double_deflate
from phcpy.deflation import double_multiplicity
Newton’s method¶
Let us start with a simple system of two polynomials in two unknowns.
pols = ['x^2 + 4*y^2 - 4;', '2*y^2 - x;']
At a regular solution, Newton’s method doubles the accuracy in each step. For the example, we start at a solution where the coordinates are given with only three decimal places.
sols = [make_solution(['x', 'y'], [1.23, -0.786])]
print(sols[0])
Here is then the solution with three decimal places of accuracy:
t : 0.000000000000000E+00 0.000000000000000E+00
m : 1
the solution for t :
x : 1.230000000000000E+00 0.0
y : -7.860000000000000E-01 0.0
== err : 0.000E+00 = rco : 1.000E+00 = res : 0.000E+00 =
The function double_newton_step
expects a list of solution strings
as its second argument.
sols = double_newton_step(pols, sols)
print(sols[0])
The outcome of one step with Newton’s method in double precision is
t : 0.00000000000000E+00 0.00000000000000E+00
m : 0
the solution for t :
x : 1.23607623318386E+00 0.00000000000000E+00
y : -7.86154018188250E-01 0.00000000000000E+00
== err : 6.230E-03 = rco : 1.998E-01 = res : 3.706E-05 =
Observe the values for err
(forward error),
rco
(estimated inverse of the condition number), and
res
(the residual or backward error).
The multiplicity field m
turned 0
because the default
tolerance was not reached and the solution could not be counted
as a proper solution. Let us reset the multiplicity,
making a new solution with the values from the sols[0]
.
The string representation of the solution is converted
into a dictionary first:
sold = strsol2dict(sols[0])
sold
which yields
{'t': 0j,
'm': 0,
'err': 0.00623,
'rco': 0.1998,
'res': 3.706e-05,
'x': (1.23607623318386+0j),
'y': (-0.78615401818825+0j)}
and then we make a new solution:
sol = make_solution(['x', 'y'], [sold['x'], sold['y']])
print(sol)
As input for the next Newton step we will use
t : 0.000000000000000E+00 0.000000000000000E+00
m : 1
the solution for t :
x : 1.236076233183860E+00 0.000000000000000E+00
y : -7.861540181882500E-01 0.000000000000000E+00
== err : 0.000E+00 = rco : 1.000E+00 = res : 0.000E+00 =
Calling double_newton_step
for the second time
sols = [sol]
sols = double_newton_step(pols, sols)
print(sols[0])
gives
t : 0.00000000000000E+00 0.00000000000000E+00
m : 0
the solution for t :
x : 1.23606797751503E+00 0.00000000000000E+00
y : -7.86151377766704E-01 0.00000000000000E+00
== err : 1.090E-05 = rco : 1.998E-01 = res : 1.100E-10 =
Observe that the value of res
dropped from magnitude 1.0e-5
down to 1.0e-10
, corresponding to the well conditioning of the root.
However, the multiplicy field is still zero because the estimate
for the forward error is still too high.
The third application of the Newton step
sold = strsol2dict(sols[0])
sol = make_solution(['x', 'y'], [sold['x'], sold['y']])
sols = [sol]
sols = double_newton_step(pols, sols)
print(sols[0])
then yields the solution
t : 0.00000000000000E+00 0.00000000000000E+00
m : 1
the solution for t :
x : 1.23606797749979E+00 0.00000000000000E+00
y : -7.86151377757423E-01 0.00000000000000E+00
== err : 2.452E-11 = rco : 1.998E-01 = res : 4.441E-16 =
The value for the residual res
is very close to machine precision
and the solution is considered a proper regular solution.
We can double the precision to double double, and continue with the previous approximate solution:
sols = double_double_newton_step(pols, sols)
print(sols[0])
what is printed is below
t : 0.00000000000000000000000000000000E+00 0.00000000000000000000000000000000E+00
m : 1
the solution for t :
x : 1.23606797749978969640917366873130E+00 0.00000000000000000000000000000000E+00
y : -7.86151377757423286069558585843026E-01 0.00000000000000000000000000000000E+00
== err : 3.036E-16 = rco : 1.998E-01 = res : 3.944E-31 =
and doing this once more
sols = double_double_newton_step(pols, sols)\n",
print(sols[0])"
shows
t : 0.00000000000000000000000000000000E+00 0.00000000000000000000000000000000E+00
m : 1
the solution for t :
x : 1.23606797749978969640917366873128E+00 0.00000000000000000000000000000000E+00
y : -7.86151377757423286069558585842966E-01 0.00000000000000000000000000000000E+00
== err : 6.272E-32 = rco : 1.998E-01 = res : 0.000E+00 =
and then on to quad double precision:
sols = quad_double_newton_step(pols, sols)
print(sols[0])
which then gives the solution:
t : 0.0000000000000000000000000000000000000000000000000000000000000000E+00 0.0000000000000000000000000000000000000000000000000000000000000000E+00
m : 1
the solution for t :
x : 1.2360679774997896964091736687312762354406183596115257242708972454E+00 0.0000000000000000000000000000000000000000000000000000000000000000E+00
y : -7.8615137775742328606955858584295892952312205783772323766490197015E-01 0.0000000000000000000000000000000000000000000000000000000000000000E+00
== err : 7.070E-33 = rco : 1.998E-01 = res : 2.101E-64 =
deflation¶
At an isolated singular solution, deflation is a method to restore the quadratic convergence of Newton’s method.
pols = ['(29/16)*x^3 - 2*x*y;', 'x^2 - y;']
Obviously, (0, 0)
is a root of the pols
but even if we start
very close to (0, 0)
, Newton’s method converges extremely slowly.
sols = [make_solution(['x', 'y'],[1.0e-6, 1.0e-6])]
print(sols[0])
The initial solution is then
t : 0.000000000000000E+00 0.000000000000000E+00
m : 1
the solution for t :
x : 1.000000000000000E-06 0.0
y : 1.000000000000000E-06 0.0
== err : 0.000E+00 = rco : 1.000E+00 = res : 0.000E+00 =
Running one step with Newton’s method:
sols = double_newton_step(pols, sols)
print(sols[0])
gives
t : 0.00000000000000E+00 0.00000000000000E+00
m : 0
the solution for t :
x : 9.99999906191101E-07 0.00000000000000E+00
y : 9.99999812409806E-13 0.00000000000000E+00
== err : 1.000E-06 = rco : 5.625E-13 = res : 1.875E-19 =
Observe the tiny value for rco
, the estimate of the inverse
of the condition number.
The next step with Newton’s method
sols = double_newton_step(pols, sols)
print(sols[0])
shows
t : 0.00000000000000E+00 0.00000000000000E+00
m : 0
the solution for t :\n",
x : 6.66666604160106E-07 0.00000000000000E+00
y : 3.33333270859482E-13 0.00000000000000E+00
== err : 3.333E-07 = rco : 2.778E-14 = res : 1.111E-13 =
which does not brings us much closer to (0, 0)
.
Now we apply deflation in double precison:
solsd = double_deflate(pols, sols)
print(solsd[0])
which then yields
t : 0.00000000000000E+00 0.00000000000000E+00
m : 1
the solution for t :
x : 9.46532112069346E-24 4.09228221015004E-24
y : 1.02357542351685E-24 -2.03442589046821E-24
== err : 5.292E-12 = rco : 5.608E-03 = res : 1.885E-15 =
Deflation also works on systems with more equations than unknowns.
pols = ['x^2;', 'x*y;', 'y^2;']
Once again, (0, 0)
is the obvious root of pols
.
sols = [make_solution(['x', 'y'], [1.0e-6, 1.0e-6])]
print(sols[0]
So, we start at
t : 0.000000000000000E+00 0.000000000000000E+00
m : 1
the solution for t :
x : 1.000000000000000E-06 0.0
y : 1.000000000000000E-06 0.0
== err : 0.000E+00 = rco : 1.000E+00 = res : 0.000E+00 =
and apply deflation
sols = double_deflate(pols, sols, tolrnk=1.0e-8)
print(sols[0])
which shows
t : 0.00000000000000E+00 0.00000000000000E+00
m : 1
the solution for t :
x : 1.25000000000000E-07 0.00000000000000E+00
y : 1.25000000000000E-07 0.00000000000000E+00
== err : 1.250E-07 = rco : 8.165E-01 = res : 1.562E-14 =
This is not an improvement, because the tolerance on the numerical
rank, given by tolrnk=1.0e-8
is too severe.
If we relax this tolerance to 1.0e-4
,
sols = double_deflate(pols, sols, tolrnk=1.0e-4)
print(sols[0])
then we obtain
t : 0.00000000000000E+00 0.00000000000000E+00
m : 1
the solution for t :
x : -5.87747175411144E-39 -5.14278778484751E-39
y : 2.93873587705572E-39 -1.83670992315982E-40
== err : 2.757E-23 = rco : 4.082E-01 = res : 1.984E-38 =
and the coordinates of this numerical approximation are well below the double precision.
multiplicity structure¶
The multiplicity can be computed locally starting at the solution. Consider the following example:
pols = [ 'x**2+y-3;', 'x+0.125*y**2-1.5;']
The solution (1, 2)
is a multiple root of pols
.
Let us prepare the input:
sol = make_solution(['x', 'y'], [1, 2])
print(sol)
so, we work with
t : 0.000000000000000E+00 0.000000000000000E+00
m : 1
the solution for t :
x : 1.000000000000000E+00 0.0
y : 2.000000000000000E+00 0.0
== err : 0.000E+00 = rco : 1.000E+00 = res : 0.000E+00 =
as input to double_multiplicity
multiplicity, hilbert_function = double_multiplicity(pols, sol)
The value of multiplicity
equals 3
which is confirmed by the content of hilbert_function
:
[1, 1, 1, 0, 0, 0]
Thus, the multiplicity of (1, 2)
as a root of pols
equals three.