Skip to content

Root finding for degenerate polynomials #446

@HechtiDerLachs

Description

@HechtiDerLachs

Let me first say that I was very happy to find the capabilities of Arb within Nemo and Oscar!

I have one application in mind where I need to track roots of a univariate polynomial f(u) for varying coefficients a_i of f. It lies in the nature of my problem that the limit polynomial f -> f_1 has at least one multiple root. I learned from the Arb documentation that internally, the Durand-Kerner method is used. According to this article, it is possible to obtain reasonable convergence also in this degenerate case.

Playing around with the Arb methods, I found that the error bounds for the returned list of roots of f becomes significantly worse, once f approaches f_1 which has a multiple root. Usually, one would circumvent this problem by making f square free. But in my setting, it will be most convenient to have the input in inexact form, i.e. f_1 is not an exact polynomial with a multiple root, but rather an Arb-polynomial with an 'approximate multiple root'. Therefore, doing exact division beforehand is not a feasible way to go.

Question: Can we modify the implementation of the root-finding algorithm in such a way that it returns a list of roots with higher precision in the degenerate cases? It already returns a list of roots, making no claims about their multiplicities. And this is fine for me; it's only that the precision really seems to go down.

The following is a sample code in julia using Oscar, derived from @JHanselman's work.

using Oscar

function recursive_continuation(f::acb_poly, g::acb_poly, r::Vector{acb})
  P = parent(f)
  P === parent(g) || error("polynomials must belong to the same parent ring")
  z = gens(P)[1]
  CC = coefficient_ring(P)
  prec = precision(CC)
  n = degree(f)
  degree(g) == n || error("number of coefficients must be equal to the degree of the polynomial")
  length(r) == n || error("number of approximate roots must be equal to the degree of the polynomial")
  temp_vec = Nemo.acb_vec(n)
  temp_vec_res = Nemo.acb_vec(n)

  # The following is an error estimation from Neurohr's thesis.
  d = reduce(min, [abs(r[i] - r[j]) for (i, j) in filter(t-> t[1] != t[2], [a for a in Iterators.product((1:n), (1:n))])]) 
  W = [ f(r[i]) // prod([r[i] - r[j] for j in vcat((1:i - 1), i+1:n)];init = one(CC)) for i in (1:n)]
  w = reduce(max, map(t -> real(t)^2 + imag(t)^2, W))
  
  if w < d // (2*n)
    temp_vec = Nemo.acb_vec(copy(r)) # TODO: Why does fill! not work?
    dd = ccall((:acb_poly_find_roots, Nemo.libarb), Cint, (Ptr{Nemo.acb_struct}, Ref{acb_poly}, Ptr{Nemo.acb_struct}, Int, Int), temp_vec_res, g, temp_vec, 0, prec)

    dd == n || println("number of roots has dropped to $dd")
    result = similar(r)
    result .= Nemo.array(CC, temp_vec_res, n)
    
    Nemo.acb_vec_clear(temp_vec, n)
    Nemo.acb_vec_clear(temp_vec_res, n)
    
    return result
  else
    h = CC(1//2).*(f+g)
    return recursive_continuation(h, g, recursive_continuation(f, h, r))
  end
end

prec = 128
CC = ComplexField(prec)

d = 5
P, a = PolynomialRing(QQ, push!(["a$i" for i in 0:d-1], "z"))
z = pop!(a)
F = sum([a[i]*z^(i-1) for i in 1:d]) + z^d

v = [CC(i) for i in 1:d]

CCu, u = CC["u"]

f = evaluate(F, push!(CCu.(v), u))

r = roots(f)

w = CC.([20, -10, 5, 25, -3])
g = u^d + sum([w[i]*u^(i-1) for i in 1:d])
@show r2 = recursive_continuation(f, g, r) # This produces good results!
g = u^d - u^(d-1) 
@show recursive_continuation(f, g, r) # This produces worse results.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions