Skip to content

Commit fd31502

Browse files
authored
Brehard etal (#500)
* incorporate work of Brehard et. al. * use suggestions of AP * WIP * WIP * re-organize and streamline * doctest * doc fix * doc fix
1 parent ffb1d73 commit fd31502

File tree

4 files changed

+240
-33
lines changed

4 files changed

+240
-33
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "Polynomials"
22
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
33
license = "MIT"
44
author = "JuliaMath"
5-
version = "3.2.11"
5+
version = "3.2.12"
66

77
[deps]
88
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"

src/polynomials/multroot.jl

+235-31
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ using ..Polynomials
66
using LinearAlgebra
77

88
"""
9-
multroot(p; verbose=false, kwargs...)
9+
multroot(p; verbose=false, method=:direct, kwargs...)
1010
1111
Use `multroot` algorithm of
1212
[Zeng](https://www.ams.org/journals/mcom/2005-74-250/S0025-5718-04-01692-8/S0025-5718-04-01692-8.pdf)
1313
to identify roots of polynomials with suspected multiplicities over
1414
`Float64` values, typically.
1515
16+
* `p`: a standard basis polynomial
17+
* `method`: If `:direct` uses a method of Brehard, Poteaux, and Soudant to identify the multiplicity structure of the roots, if `:iterative` uses the Zeng method.
18+
19+
The keyword arguments can be used to adjust the floating-point tolerances.
20+
21+
Returns a named tuple with the identified roots (`values`), the corresponding multiplicities (`multiplicities`) and estimates for the condition number (`κ`) and the backward error (`‖p̃ - p‖_w`).
22+
23+
1624
Example:
1725
1826
```jldoctest
@@ -25,9 +33,9 @@ julia> roots(p)
2533
5-element Vector{ComplexF64}:
2634
0.999999677417768 + 0.0im
2735
1.0000003225831504 + 0.0im
28-
1.4141705716005981 + 0.0im
29-
1.4142350577588885 - 3.72273772278647e-5im
30-
1.4142350577588885 + 3.72273772278647e-5im
36+
1.4141705716005881 + 0.0im
37+
1.4142350577588914 - 3.722737728087131e-5im
38+
1.4142350577588914 + 3.722737728087131e-5im
3139
3240
julia> m = Polynomials.Multroot.multroot(p);
3341
@@ -37,15 +45,19 @@ Dict{Float64, Int64} with 2 entries:
3745
1.0 => 2
3846
```
3947
48+
## Extended help
49+
4050
The algorithm has two stages. First it uses `pejorative_manifold` to
4151
identify the number of distinct roots and their multiplicities. This
4252
uses the fact if `p=Π(x-zᵢ)ˡⁱ`, `u=gcd(p, p′)`, and `u⋅v=p` then
4353
`v=Π(x-zi)` is square free and contains the roots of `p` and `u` holds
44-
the multiplicity details, which are identified by recursive usage of
45-
`ncgd`, which identifies `u` and `v` above even if numeric
46-
uncertainties are present.
54+
the multiplicity details. The `:iterative` method of Zeng identifies
55+
the multiplicities by recursive usage of `ncgd`, which identifies `u`
56+
and `v` above even if numeric uncertainties are present. The, default,
57+
`:direct` method of Brehard, Poteaux, and Soudant uses division and
58+
does a better job for polynomials of larger degrees.
4759
48-
Second it uses `pejorative_root` to improve a set of initial guesses
60+
Second the algorithm uses `pejorative_root` to improve a set of initial guesses
4961
for the roots under the assumption the multiplicity structure is
5062
correct using a Newton iteration scheme.
5163
@@ -57,13 +69,14 @@ multiplicity structure:
5769
`Polynomials.ngcd` to assess if a matrix is rank deficient by
5870
comparing the smallest singular value to `θ ⋅ ‖p‖₂`.
5971
60-
* `ρ`: the initial residual tolerance, set to `1e-10`. This is passed
72+
* `ρ`: the initial residual tolerance, set to `1e-13`. This is passed
6173
to `Polynomials.ngcd`, the GCD finding algorithm as a measure for
62-
the absolute tolerance multiplied by `‖p‖₂`.
74+
the absolute tolerance multiplied by `‖p‖₂`. (For the `:iterative`
75+
method, a suggested value is `1e-10`.
6376
6477
* `ϕ`: A scale factor, set to `100`. As the `ngcd` algorithm is called
65-
recursively, this allows the residual tolerance to scale up to match
66-
increasing numeric errors.
78+
recursively for the `:iterative` method, this allows the residual tolerance
79+
to scale up to match increasing numeric errors.
6780
6881
Returns a named tuple with
6982
@@ -92,48 +105,82 @@ function multroot(p::Polynomials.StandardBasisPolynomial{T}; verbose=false,
92105
return (values=zeros(T,1), multiplicities=[nz-1], κ=NaN, ϵ=NaN)
93106
end
94107

108+
# Basic algorithm is two steps
95109
z, l = pejorative_manifold(p; kwargs...)
96110
= pejorative_root(p, z, l)
97111
κ, ϵ = stats(p, z̃, l)
98112

99113
verbose && show_stats(κ, ϵ)
114+
100115
(values = z̃, multiplicities = l, κ = κ, ϵ = ϵ)
101116

102117
end
103118

119+
104120
# The multiplicity structure, `l`, gives rise to a pejorative manifold
105121
# `Πₗ = {Gₗ(z) | z ∈ Cᵐ}`. This first finds the approximate roots, then
106122
# finds the multiplicity structure
107-
function pejorative_manifold(p::Polynomials.StandardBasisPolynomial{T,X};
108-
θ = 1e-8, # zero singular-value threshold
109-
ρ = 1e-10, # initial residual tolerance
110-
ϕ = 100 # residual growth factor
111-
) where {T,X}
123+
124+
# compute initial root approximations with multiplicities
125+
# without any information
126+
127+
# With method = :direct, use the direct method of Brehard, Poteaux, and Soudant
128+
# based on the cofactors v,w s.t. p = u*v and q = u*w
129+
130+
# With method = :iterative, use the iterative strategy of Zeng
131+
# to recover the multiplicities associated to the computed roots
132+
133+
# Better performing :direct method by Florent Bréhard, Adrien Poteaux, and Léo Soudant [Validated root enclosures for interval polynomials with multiplicities](preprint)
134+
135+
function pejorative_manifold(
136+
p::Polynomials.StandardBasisPolynomial{T,X};
137+
method = :direct,
138+
θ = 1e-8, # zero singular-value threshold
139+
ρ = 1e-13, # initial residual tolerance, was 1e-10
140+
ϕ = 100, # residual growth factor
141+
kwargs...
142+
) where {T,X}
112143

113144
S = float(T)
114145
u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p)))
115146

116147
nu₂ = norm(u, 2)
117-
θ′, ρ′ = θ * nu₂, ρ * nu₂
148+
θ2, ρ2 = θ * nu₂, ρ * nu₂
118149

119-
u, v, w, ρⱼ, κ = Polynomials.ngcd(u, derivative(u);
120-
satol = θ′, srtol = zero(real(T)),
121-
atol = ρ′, rtol = zero(real(T))
122-
)
150+
u, v, w, ρⱼ, κ = Polynomials.ngcd(
151+
u, derivative(u),
152+
satol = θ2, srtol = zero(real(T)),
153+
atol = ρ2, rtol = zero(real(T)))
123154
ρⱼ /= nu₂
124155

156+
# root approximations
125157
zs = roots(v)
126-
ls = pejorative_manifold_multiplicities(u,
127-
zs, ρⱼ,
128-
θ, ρ, ϕ)
129-
zs, ls
130-
end
131158

159+
# recover multiplicities
160+
ls = pejorative_manifold_multiplicities(Val(method),
161+
u, v, w,
162+
zs, nothing,
163+
ρⱼ, θ, ρ, ϕ;
164+
kwargs...)
165+
166+
167+
return zs, ls
168+
169+
end
132170

171+
## ------- Step 1, get multiplicities
172+
# recover the multiplicity of each root approximation
173+
# using the `:iterative` method of Zeng
174+
function pejorative_manifold_multiplicities(
175+
::Val{:iterative},
176+
u::Polynomials.PnPolynomial{T},
177+
v::Polynomials.PnPolynomial{T},
178+
w::Polynomials.PnPolynomial{T},
179+
zs,
180+
l::Any,
181+
ρⱼ,θ, ρ, ϕ;
182+
kwargs...) where {T}
133183

134-
function pejorative_manifold_multiplicities(u::Polynomials.PnPolynomial{T},
135-
zs, ρⱼ,
136-
θ, ρ, ϕ) where {T}
137184
nrts = length(zs)
138185
ls = ones(Int, nrts)
139186

@@ -161,6 +208,26 @@ function pejorative_manifold_multiplicities(u::Polynomials.PnPolynomial{T},
161208

162209
end
163210

211+
# Use `:direct` method of Bréhard, Poteaux, and Soudant
212+
# to recover the multiplicity of each approximate root
213+
# directly from the cofactors v, w s.t. p = u*v and q = u*w
214+
function pejorative_manifold_multiplicities(
215+
::Val{:direct},
216+
u::Polynomials.PnPolynomial{T},
217+
v::Polynomials.PnPolynomial{T},
218+
w::Polynomials.PnPolynomial{T},
219+
zs,
220+
args...;
221+
kwargs...) where {T}
222+
223+
dv = derivative(v)
224+
ls = w.(zs) ./ dv.(zs)
225+
ls = round.(Int, real.(ls))
226+
227+
return ls
228+
end
229+
230+
164231

165232
"""
166233
pejorative_root(p, zs, ls; kwargs...)
@@ -174,10 +241,11 @@ This follows Algorithm 1 of [Zeng](https://www.ams.org/journals/mcom/2005-74-250
174241
"""
175242
function pejorative_root(p::Polynomials.StandardBasisPolynomial,
176243
zs::Vector{S}, ls; kwargs...) where {S}
177-
ps = (reverse coeffs)(p)
244+
ps = reverse(coeffs(p))
178245
pejorative_root(ps, zs, ls; kwargs...)
179246
end
180247

248+
181249
# p is [1, a_{n-1}, a_{n-2}, ..., a_1, a_0]
182250
function pejorative_root(p, zs::Vector{S}, ls;
183251
τ = eps(float(real(S)))^(2/3),
@@ -318,4 +386,140 @@ function show_stats(κ, ϵ)
318386
println("")
319387
end
320388

389+
390+
## ---- Some alternatives
391+
# compute initial root approximations with multiplicities
392+
# when the number k of distinct roots is known
393+
# If method=direct and leastsquares=true, compute the cofactors v,w
394+
# using least-squares rather than Zeng's AGCD refinement strategy
395+
function pejorative_manifold(
396+
p::Polynomials.StandardBasisPolynomial{T,X},
397+
k::Int;
398+
method = :direct,
399+
leastsquares = false,
400+
roundmul = true,
401+
θ = 1e-8, # zero singular-value threshold
402+
ρ = 1e-10, # initial residual tolerance
403+
ϕ = 100, # residual growth factor
404+
) where {T,X}
405+
406+
error("Does this get called?")
407+
408+
S = float(T)
409+
u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p)))
410+
411+
nu₂ = norm(u, 2)
412+
413+
if method == :direct && leastsquares
414+
v,w = _ngcd(u,k)
415+
else
416+
u, v, w, ρⱼ, κ = Polynomials.ngcd(u, derivative(u), degree(u)-k)
417+
ρⱼ /= nu₂
418+
end
419+
420+
# root approximations
421+
zs = roots(v)
422+
423+
# recover multiplicities
424+
ls = pejorative_manifold_multiplicities(
425+
Val(method),
426+
u, v, w,
427+
zs, nothing,
428+
ρⱼ, θ, ρ, ϕ)
429+
430+
roundmul && (ls = Int.(round.(real.(ls))))
431+
return zs, ls
432+
433+
end
434+
435+
436+
# compute initial root approximations with multiplicities
437+
# when the multiplicity structure l is known
438+
# If method=direct and leastsquares=true, compute the cofactors v,w
439+
# using least-squares rather than Zeng's AGCD refinement strategy
440+
function pejorative_manifold(p::Polynomials.StandardBasisPolynomial{T,X},
441+
l::Vector{Int};
442+
method = :direct,
443+
leastsquares = false,
444+
roundmul = true,
445+
) where {T,X}
446+
447+
error("Does this one get called?")
448+
449+
450+
S = float(T)
451+
u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p)))
452+
453+
# number of distinct roots
454+
k = sum(l .> 0)
455+
456+
if method == :direct && leastsquares
457+
v, w = _ngcd(u, k)
458+
else
459+
u, v, w, _, _ = Polynomials.ngcd(u, derivative(u), degree(u)-k)
460+
end
461+
462+
# root approximations
463+
zs = roots(v)
464+
465+
# recover multiplicities
466+
ls = pejorative_manifold_multiplicities(Val(method), u,v, w, zs, l; leastsquares=leastsquares)
467+
roundmul && (ls = Int.(round.(real.(ls))))
468+
return zs, ls
469+
end
470+
471+
472+
# use least-squares rather than Zeng's AGCD refinement strategy
473+
function _ngcd(u, k)
474+
@show :_ngcd
475+
n = degree(u)
476+
Sy = Polynomials.NGCD.SylvesterMatrix(u, derivative(u), n-k)
477+
b = Sy[1:end-1,2*k+1] - n * Sy[1:end-1,k] # X^k*p' - n*X^{k-1}*p
478+
A = Sy[1:end-1,1:end .∉ [[k,2*k+1]]]
479+
x = zeros(S, 2*k-1)
480+
Polynomials.NGCD.qrsolve!(x, A, b)
481+
# w = n*X^{k-1} + ...
482+
w = Polynomials.PnPolynomial([x[1:k-1]; n])
483+
# v = X^k + ...
484+
v = Polynomials.PnPolynomial([-x[k:2*k-1]; 1])
485+
v, w
486+
end
487+
488+
489+
# recover the multiplicity of each root approximation
490+
# using the iterative method of Zeng
491+
# but knowing the total multiplicity structure,
492+
# hence the degree of the approximate GCD at each iteration is known
493+
# if roundmul=true, round the floating-point multiplicities
494+
# to the closest integer
495+
#function pejorative_manifold_iterative_multiplicities(
496+
function pejorative_manifold_multiplicities(
497+
::Val{:iterative},
498+
u::Polynomials.PnPolynomial{T},
499+
v::Polynomials.PnPolynomial{T},
500+
w::Polynomials.PnPolynomial{T},
501+
zs,
502+
l::Vector{Int},
503+
args...; kwargs...) where {T}
504+
505+
ls = ones(Int, length(zs))
506+
ll = copy(l)
507+
508+
while !Polynomials.isconstant(u)
509+
# remove 1 to all multiplicities
510+
ll .-= 1
511+
deleteat!(ll, ll .== 0)
512+
k = length(ll)
513+
u, v, w, ρⱼ, κ = Polynomials.ngcd(u, derivative(u), degree(u)-k)
514+
for z roots(v)
515+
_, ind = findmin(abs.(zs .- z))
516+
ls[ind] += 1
517+
end
518+
end
519+
520+
return ls
521+
end
522+
523+
## --------
524+
321525
end

src/polynomials/standard-basis.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T},
241241
end
242242

243243
"""
244-
gcd(p1::StandardBasisPolynomial, p2::StandardBasisPolynomial; method=:eculidean, kwargs...)
244+
gcd(p1::StandardBasisPolynomial, p2::StandardBasisPolynomial; method=:euclidean, kwargs...)
245245
246246
Find the greatest common divisor.
247247

0 commit comments

Comments
 (0)