Skip to content

Track Roots 3 find_zero convergence failures in quantile paths #375

Description

@lrnv

[writen by chatgpt]

Summary

Recent full test runs fail with Roots.ConvergenceFailed("Algorithm failed to converge") from quantile/root-finding paths that used to pass.

This started showing up after dependency updates around Roots/Optim. It may be a Roots 3 behavior/tolerance change, or it may expose that our quantile calls rely on overly strict/default convergence behavior.

Observed failures

From the attached test output:

  1. BC2Copula sampling through ExtremeDist.quantile:
Roots.ConvergenceFailed("Algorithm failed to converge")
Stacktrace:
  Roots.find_zero(..., Roots.Brent())
  quantile @ src/UnivariateDistribution/ExtremeDist.jl:42
  rand @ src/UnivariateDistribution/ExtremeDist.jl:47
  _rand!(::StableRNGs.LehmerRNG, ::BC2Copula{Float64}, ::Matrix{Float64})
      @ src/ExtremeValueCopula.jl:80

Examples in the test output include:

  • BC2Copula{Float64}(a = 0.5, b = 0.3)
  • BC2Copula{Float64}(a = 0.5, b = 0.5)
  • BC2Copula{Float64}(a = 0.5516353577049822, b = 0.33689370624999193)
  • BC2Copula{Float64}(a = 0.7, b = 0.3)
  • BC2Copula{Float64}(a = 0.6, b = 0.8)

The relevant code currently does:

Roots.find_zero(x -> Distributions.cdf(d, x) - p,
                (eps(), 1.0 - eps()), Roots.Brent())
  1. Bernstein inverse Rosenblatt through Distributions mixture quantile:
Roots.ConvergenceFailed("Algorithm failed to converge")
Stacktrace:
  Roots.find_zero(...)
  quantile_bisect @ Distributions/src/quantilealgs.jl:18
  quantile @ Distributions/src/mixtures/mixturemodel.jl:449
  inverse_rosenblatt(::BernsteinCopula{2}, ...)
      @ src/Conditioning.jl:340

The failing test is the generic roundtrip:

rosenblatt  inverse_rosenblatt = Id

Version trail

Current local manifest / failing run:

  • Julia: 1.12.1
  • Copulas: 0.1.35
  • Roots: 3.0.2
  • Optim: 2.2.1
  • Distributions: 0.25.129

Recent compat history from this repo:

  • Before ad9988b3 / PR #354, Project.toml had Roots = "1, 2".
  • After ad9988b3 / PR #354, Project.toml has Roots = "1, 2, 3".
  • Before a75b08cf / PR #355, Project.toml had Optim = "1.13.2".
  • After a75b08cf / PR #355, Project.toml has Optim = "2".

Manifest.toml is not tracked historically in this repo, so I could not recover the exact old resolved Roots version from git. Installed local Roots versions include:

  • 2.2.10 installed 2025-09-02
  • 3.0.0 installed 2026-05-11
  • 3.0.2 installed 2026-07-10

Given the old compat was Roots = "1, 2", the previous resolver target was presumably a Roots 2.x release, likely 2.2.10 in this local environment.

Initial hypothesis

This may be due to Roots 3 returning NaN / throwing ConvergenceFailed more readily when the bracketing solver reaches a plateau or a very tight tolerance near floating-point resolution. The two failure paths both rely on library quantile/root-finding defaults:

  • our explicit Roots.find_zero(..., Roots.Brent()) in ExtremeDist.quantile;
  • Distributions' quantile_bisect, which now delegates to find_zero(..., ITP()) and relies on Roots defaults.

Things to investigate

  • Reproduce with Roots 3.0.2 versus Roots 2.2.10.
  • Check whether ExtremeDist.quantile should use a more robust local bisection/ITP call with explicit tolerances or endpoint guards.
  • Check whether the Bernstein failures are upstream in Distributions/Roots defaults or whether our inverse_rosenblatt passes probabilities close enough to a flat/atomic mixture boundary that we should clamp or handle endpoints.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions