Skip to content

Commit 089d03b

Browse files
authored
Ngcd cleanup (#430)
* refactor `ngcd` code * clean up doc strings * formatting and minor adjustments * use `muladd` where possible * version bump
1 parent 1881c44 commit 089d03b

7 files changed

+598
-380
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.1.4"
5+
version = "3.1.5"
66

77
[deps]
88
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

src/Polynomials.jl

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ include("polynomials/pi_n_polynomial.jl")
2323
include("polynomials/factored_polynomial.jl")
2424
include("polynomials/ngcd.jl")
2525
include("polynomials/multroot.jl")
26-
2726
include("polynomials/ChebyshevT.jl")
2827

2928
# Rational functions

src/polynomials/multroot.jl

+67-42
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ multiplicity structure:
5555
5656
* `θ`: the singular value threshold, set to `1e-8`. This is used by
5757
`Polynomials.ngcd` to assess if a matrix is rank deficient by
58-
comparing the smallest singular value to `θ ⋅ ||p||₂`.
58+
comparing the smallest singular value to `θ ⋅ ‖p‖₂`.
5959
6060
* `ρ`: the initial residual tolerance, set to `1e-10`. This is passed
61-
to `Polynomials.ngcd`, the GCD finding algorithm as a relative tolerance.
61+
to `Polynomials.ngcd`, the GCD finding algorithm as a measure for
62+
the absolute tolerance multiplied by `‖p‖₂`.
6263
6364
* `ϕ`: A scale factor, set to `100`. As the `ngcd` algorithm is called
6465
recursively, this allows the residual tolerance to scale up to match
@@ -69,11 +70,11 @@ Returns a named tuple with
6970
* `values`: the identified roots
7071
* `multiplicities`: the corresponding multiplicities
7172
* `κ`: the estimated condition number
72-
* `ϵ`: the backward error, `||p̃ - p||_w`.
73+
* `ϵ`: the backward error, `p̃ - p_w`.
7374
7475
If the wrong multiplicity structure is identified in step 1, then
7576
typically either `κ` or `ϵ` will be large. The estimated forward
76-
error, `||z̃ - z||₂`, is bounded up to higher order terms by `κ ⋅ ϵ`.
77+
error, `z̃ - z₂`, is bounded up to higher order terms by `κ ⋅ ϵ`.
7778
This will be displayed if `verbose=true` is specified.
7879
7980
For polynomials of degree 20 or higher, it is often the case the `l`
@@ -100,62 +101,85 @@ function multroot(p::Polynomials.StandardBasisPolynomial{T}; verbose=false,
100101

101102
end
102103

103-
# The multiplicity structure, `l`, gives rise to a pejorative manifold `Πₗ = {Gₗ(z) | z∈ Cᵐ}`.
104-
function pejorative_manifold(p::Polynomials.StandardBasisPolynomial{T};
105-
ρ = 1e-10, # initial residual tolerance
106-
θ = 1e-8, # zero singular-value threshold
107-
ϕ = 100) where {T}
104+
# The multiplicity structure, `l`, gives rise to a pejorative manifold
105+
# `Πₗ = {Gₗ(z) | z ∈ Cᵐ}`. This first finds the approximate roots, then
106+
# 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}
112+
113+
S = float(T)
114+
u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p)))
115+
116+
nu₂ = norm(u, 2)
117+
θ′, ρ′ = θ * nu₂, ρ * nu₂
118+
119+
u, v, w, ρⱼ, κ = Polynomials.ngcd(u, derivative(u);
120+
satol = θ′, srtol = zero(real(T)),
121+
atol = ρ′, rtol = zero(real(T))
122+
)
123+
ρⱼ /= nu₂
108124

109-
zT = zero(float(real(T)))
110-
u, v, w, θ′, κ = Polynomials.ngcd(p, derivative(p),
111-
atol=ρ*norm(p), satol = θ*norm(p),
112-
rtol = zT, srtol = zT)
113125
zs = roots(v)
126+
ls = pejorative_manifold_multiplicities(u,
127+
zs, ρⱼ,
128+
θ, ρ, ϕ)
129+
zs, ls
130+
end
131+
132+
133+
134+
function pejorative_manifold_multiplicities(u::Polynomials.PnPolynomial{T},
135+
zs, ρⱼ,
136+
θ, ρ, ϕ) where {T}
114137
nrts = length(zs)
115138
ls = ones(Int, nrts)
116139

117140
while !Polynomials.isconstant(u)
118141

119-
normp = 1 + norm(u, 2)
120-
ρ′ = max*normp, ϕ * θ′) # paper uses just latter
121-
u, v, w, θ′, κ = Polynomials.ngcd(u, derivative(u),
122-
atol=ρ′, satol = θ*normp,
123-
rtol = zT, srtol = zT,
124-
minⱼ = degree(u) - nrts # truncate search
142+
nu₂ = norm(u,2)
143+
θ = θ * nu₂
144+
ρ = max(ρ, ϕ * ρⱼ)
145+
ρ′ = ρ * nu₂
146+
u, v, w, ρⱼ, κ = Polynomials.ngcd(u, derivative(u),
147+
satol = θ, srtol = zero(real(T)),
148+
atol = ρ′, rtol = zero(real(T)),
149+
minⱼ = degree(u) - nrts
125150
)
126-
127-
rts = roots(v)
128-
nrts = length(rts)
129-
130-
for z in rts
131-
tmp, ind = findmin(abs.(zs .- z))
132-
ls[ind] = ls[ind] + 1
151+
ρⱼ /= nu₂
152+
nrts = degree(v)
153+
for z roots(v)
154+
_, ind = findmin(abs.(zs .- z))
155+
ls[ind] += 1
133156
end
134157

135158
end
136159

137-
zs, ls # estimate for roots, multiplicities
160+
ls
138161

139162
end
140163

164+
141165
"""
142166
pejorative_root(p, zs, ls; kwargs...)
143167
144168
Find a *pejorative* *root* for `p` given multiplicity structure `ls` and initial guess `zs`.
145169
146170
The pejorative manifold for a multplicity structure `l` is denoted `{Gₗ(z) | z ∈ Cᵐ}`. A pejorative
147-
root is a least squares minimizer of `F(z) = W ⋅ [Gₗ(z) - a]`. Here `a ~ (p_{n-1}, p_{n-2}, …, p_1, p_0) / p_n` and `W` are weights given by `min(1, 1/|aᵢ|)`. When `l` is the mathematically correct structure, then `F` will be `0` for a pejorative root. If `l` is not correct, then the backward error `||p̃ - p||_w` is typically large, where `p̃ = Π (x-z̃ᵢ)ˡⁱ`.
171+
root is a least squares minimizer of `F(z) = W ⋅ [Gₗ(z) - a]`. Here `a ~ (p_{n-1}, p_{n-2}, …, p_1, p_0) / p_n` and `W` are weights given by `min(1, 1/|aᵢ|)`. When `l` is the mathematically correct structure, then `F` will be `0` for a pejorative root. If `l` is not correct, then the backward error `p̃ - p_w` is typically large, where `p̃ = Π (x-z̃ᵢ)ˡⁱ`.
148172
149173
This follows Algorithm 1 of [Zeng](https://www.ams.org/journals/mcom/2005-74-250/S0025-5718-04-01692-8/S0025-5718-04-01692-8.pdf)
150174
"""
151175
function pejorative_root(p::Polynomials.StandardBasisPolynomial,
152-
zs::Vector{S}, ls::Vector{Int}; kwargs...) where {S}
176+
zs::Vector{S}, ls; kwargs...) where {S}
153177
ps = (reverse coeffs)(p)
154178
pejorative_root(ps, zs, ls; kwargs...)
155179
end
156180

157181
# p is [1, a_{n-1}, a_{n-2}, ..., a_1, a_0]
158-
function pejorative_root(p, zs::Vector{S}, ls::Vector{Int};
182+
function pejorative_root(p, zs::Vector{S}, ls;
159183
τ = eps(float(real(S)))^(2/3),
160184
maxsteps = 100, # linear or quadratic
161185
verbose=false
@@ -170,7 +194,7 @@ function pejorative_root(p, zs::Vector{S}, ls::Vector{Int};
170194
a = p[2:end]./p[1] # a ~ (p[n-1], p[n-2], ..., p[0])/p[n]
171195
W = Diagonal([min(1, 1/abs(aᵢ)) for aᵢ in a])
172196
J = zeros(S, m, n)
173-
G = zeros(S, 1 + m)
197+
G = zeros(S, 1 + m)
174198
Δₖ = zeros(S, n)
175199
zₖs = copy(zs) # updated
176200

@@ -205,10 +229,10 @@ function pejorative_root(p, zs::Vector{S}, ls::Vector{Int};
205229
if cvg
206230
return zₖs
207231
else
208-
@info ("""
232+
@info """
209233
The multiplicity count may be in error: the initial guess for the roots failed
210234
to converge to a pejorative root.
211-
""")
235+
"""
212236
return(zₖs)
213237
end
214238

@@ -220,13 +244,11 @@ function evalG!(G, zs::Vector{T}, ls::Vector) where {T}
220244
G .= zero(T)
221245
G[1] = one(T)
222246

223-
ctr = 1
224247
for (z,l) in zip(zs, ls)
225248
for _ in 1:l
226-
for j in length(G)-1:-1:1#ctr
249+
for j in length(G)-1:-1:1
227250
G[1 + j] -= z * G[j]
228251
end
229-
ctr += 1
230252
end
231253
end
232254

@@ -241,6 +263,7 @@ function evalJ!(J, zs::Vector{T}, ls::Vector) where {T}
241263
n, m = sum(ls), length(zs)
242264

243265
evalG!(view(J, 1:1+n-m, 1), zs, ls .- 1)
266+
244267
for (j′, lⱼ) in enumerate(reverse(ls))
245268
for i in 1+n-m:-1:1
246269
J[i, m - j′ + 1] = -lⱼ * J[i, 1]
@@ -260,19 +283,21 @@ end
260283

261284
# Defn (3.5) condition number of z with respect to the multiplicity l and weight W
262285
cond_zl(p::AbstractPolynomial, zs::Vector{S}, ls) where {S} = cond_zl(reverse(coeffs(p)), zs, ls)
286+
263287
function cond_zl(p, zs::Vector{S}, ls) where {S}
264288
J = zeros(S, sum(ls), length(zs))
265-
W = diagm(0 => [min(1, 1/abs(aᵢ)) for aᵢ in p[2:end]])
266289
evalJ!(J, zs, ls)
267-
F = qr(W*J)
268-
_, σ, _ = Polynomials.NGCD.smallest_singular_value(F.R)
290+
W = diagm(0 => [min(1, 1/abs(aᵢ)) for aᵢ in p[2:end]])
291+
292+
σ = Polynomials.NGCD.smallest_singular_value(W*J)
269293
1 / σ
270294
end
271295

272296
backward_error(p::AbstractPolynomial, zs::Vector{S}, ls) where {S} =
273297
backward_error(reverse(coeffs(p)), zs, ls)
298+
274299
function backward_error(p, z̃s::Vector{S}, ls) where {S}
275-
# || ã - a ||w
300+
# ã - a w
276301
G = zeros(S, 1 + sum(ls))
277302
evalG!(G, z̃s, ls)
278303
a = p[2:end]./p[1]
@@ -288,8 +313,8 @@ end
288313
function show_stats(κ, ϵ)
289314
println("")
290315
println("Condition number κ(z; l) = ", κ)
291-
println("Backward error ||ã - a||w = ", ϵ)
292-
println("Estimated forward root error ||z̃ - z||₂ = ", κ * ϵ)
316+
println("Backward error ã - aw = ", ϵ)
317+
println("Estimated forward root error z̃ - z₂ = ", κ * ϵ)
293318
println("")
294319
end
295320

0 commit comments

Comments
 (0)