Skip to content
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ PDMats = "90014a1f-27ba-587c-ab20-58faa44d9150"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsAPI = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
Expand Down Expand Up @@ -51,6 +52,7 @@ PDMats = "0.11.35"
Printf = "<0.0.1, 1"
QuadGK = "2"
Random = "<0.0.1, 1"
Roots = "2"
SparseArrays = "<0.0.1, 1"
SparseConnectivityTracer = "1"
SpecialFunctions = "1.2, 2"
Expand Down
1 change: 1 addition & 0 deletions src/Distributions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ using StatsBase, PDMats, StatsFuns, Statistics
using StatsFuns: logtwo, invsqrt2, invsqrt2π

import QuadGK: quadgk
using Roots: find_zero, ITP, Newton
import Base: size, length, convert, show, getindex, rand, vec, inv
import Base: sum, maximum, minimum, extrema, +, -, *, ==
import Base.Math: @horner
Expand Down
103 changes: 35 additions & 68 deletions src/quantilealgs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,20 @@
function quantile_bisect(d::ContinuousUnivariateDistribution, p::Real, lx::T, rx::T) where {T<:Real}
rx < lx && throw(ArgumentError("empty bracketing interval [$lx, $rx]"))

# In some special cases, e.g. #1501, rx == lx`
# If the distribution is degenerate the check below can fail, hence we skip it
if rx == lx
# Returns `lx` of the same type as `(lx + rx) / 2`
# For specific types such as `Float64` it is more performant than `oftype((lx + rx) / 2, lx)`
return middle(lx)
end

# base tolerance on types to support e.g. `Float32` (avoids an infinite loop)
# ≈ 3.7e-11 for Float64
# ≈ 2.4e-5 for Float32
tol = cbrt(eps(float(T)))^2
# find quantile using bisect algorithm
cl = cdf(d, lx)
cr = cdf(d, rx)
cl <= p <= cr ||
throw(ArgumentError("[$lx, $rx] is not a valid bracketing interval for `quantile(d, $p)`"))
while rx - lx > tol
m = (lx + rx)/2
c = cdf(d, m)
if p > c
cl = c
lx = m
else
cr = c
rx = m
end

# In some special cases, e.g. #1501, rx == lx`
# If the distribution is degenerate the check below can fail, hence we skip it
if lx == rx || rx - lx <= tol * max(abs(lx), abs(rx))
# Returns `lx` of the same type as `(lx + rx) / 2`
# For specific types such as `Float64` it is more performant than `oftype((lx + rx) / 2, lx)`
return middle(lx, rx)
end
return (lx + rx)/2

return find_zero(x -> cdf(d, x) - p, (lx, rx), ITP(); xatol=tol)
end

function quantile_bisect(d::ContinuousUnivariateDistribution, p::Real, lx::Real, rx::Real)
Expand All @@ -47,16 +32,14 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) =
# Distribution, with Application to the Inverse Gaussian Distribution
# http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf

function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12)
function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12, max_iters::Int=50)
x = xs + (p - cdf(d, xs)) / pdf(d, xs)
T = typeof(x)
F(x) = cdf(d, x) - p
f(x) = pdf(d, x)
if 0 < p < 1
x0 = T(xs)
while abs(x-x0) > max(abs(x),abs(x0)) * tol
x0 = x
x = x0 + (p - cdf(d, x0)) / pdf(d, x0)
end
return x
tol = max(tol, cbrt(eps(float(T)))^2)
return find_zero((F, f), x, Newton(), ITP(); xrtol=tol, maxiters=max_iters)
elseif p == 0
return T(minimum(d))
elseif p == 1
Expand All @@ -66,16 +49,14 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=
end
end

function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12)
function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12, max_iters::Int=50)
x = xs + (ccdf(d, xs)-p) / pdf(d, xs)
T = typeof(x)
F(x) = ccdf(d, x) - p
f(x) = -pdf(d, x)
if 0 < p < 1
x0 = T(xs)
while abs(x-x0) > max(abs(x),abs(x0)) * tol
x0 = x
x = x0 + (ccdf(d, x0)-p) / pdf(d, x0)
end
return x
tol = max(tol, cbrt(eps(float(T)))^2)
return find_zero((F, f), x, Newton(), ITP(); xrtol=tol, maxiters=max_iters)
elseif p == 1
return T(minimum(d))
elseif p == 0
Expand All @@ -85,24 +66,17 @@ function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real
end
end

function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12)
function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12, max_iters::Int=50)
T = typeof(lp - logpdf(d,xs))
F(x) = logcdf(d, x) - lp
f(x) = exp(logpdf(d, x) - logcdf(d, x))
if -Inf < lp < 0
x0 = T(xs)
if lp < logcdf(d,x0)
x = x0 - exp(lp - logpdf(d,x0) + logexpm1(max(logcdf(d,x0)-lp,0)))
while abs(x-x0) >= max(abs(x),abs(x0)) * tol
x0 = x
x = x0 - exp(lp - logpdf(d,x0) + logexpm1(max(logcdf(d,x0)-lp,0)))
end
else
x = x0 + exp(lp - logpdf(d,x0) + log1mexp(min(logcdf(d,x0)-lp,0)))
while abs(x-x0) >= max(abs(x),abs(x0))*tol
x0 = x
x = x0 + exp(lp - logpdf(d,x0) + log1mexp(min(logcdf(d,x0)-lp,0)))
end
end
return x
x = lp < logcdf(d,x0) ?
x0 - exp(lp - logpdf(d,x0) + logexpm1(max(logcdf(d,x0)-lp,0))) :
x0 + exp(lp - logpdf(d,x0) + log1mexp(min(logcdf(d,x0)-lp,0)))
tol = max(tol, cbrt(eps(float(T)))^2)
return find_zero((F, f), x, Newton(), ITP(); xrtol=tol, maxiters=max_iters)
elseif lp == -Inf
return T(minimum(d))
elseif lp == 0
Expand All @@ -112,24 +86,17 @@ function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Rea
end
end

function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12)
function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12, max_iters::Int=50)
T = typeof(lp - logpdf(d,xs))
F(x) = logccdf(d, x) - lp
f(x) = -exp(logpdf(d, x) - logccdf(d, x))
if -Inf < lp < 0
x0 = T(xs)
if lp < logccdf(d,x0)
x = x0 + exp(lp - logpdf(d,x0) + logexpm1(max(logccdf(d,x0)-lp,0)))
while abs(x-x0) >= max(abs(x),abs(x0)) * tol
x0 = x
x = x0 + exp(lp - logpdf(d,x0) + logexpm1(max(logccdf(d,x0)-lp,0)))
end
else
x = x0 - exp(lp - logpdf(d,x0) + log1mexp(min(logccdf(d,x0)-lp,0)))
while abs(x-x0) >= max(abs(x),abs(x0)) * tol
x0 = x
x = x0 - exp(lp - logpdf(d,x0) + log1mexp(min(logccdf(d,x0)-lp,0)))
end
end
return x
x = lp < logccdf(d,x0) ?
x0 + exp(lp - logpdf(d,x0) + logexpm1(max(logccdf(d,x0)-lp,0))) :
x0 - exp(lp - logpdf(d,x0) + log1mexp(min(logccdf(d,x0)-lp,0)))
tol = max(tol, cbrt(eps(float(T)))^2)
return find_zero((F, f), x, Newton(), ITP(); xrtol=tol, maxiters=max_iters)
elseif lp == -Inf
return T(maximum(d))
elseif lp == 0
Expand Down
9 changes: 1 addition & 8 deletions src/univariate/continuous/chernoff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,7 @@ function quantile(d::Chernoff, tau::Real)
# one good approximation of the quantiles can be computed using Normal(0.0, stdapprox) with stdapprox = 0.52
stdapprox = 0.52
dnorm = Normal(0.0, 1.0)
if tau < 0.001
return -newton(x -> tau - ChernoffComputations._cdfbar(x), ChernoffComputations._pdf, quantile(dnorm, 1.0 - tau)*stdapprox)

end
if tau > 0.999
return newton(x -> 1.0 - tau - ChernoffComputations._cdfbar(x), ChernoffComputations._pdf, quantile(dnorm, tau)*stdapprox)
end
return newton(x -> ChernoffComputations._cdf(x) - tau, ChernoffComputations._pdf, quantile(dnorm, tau)*stdapprox) # should consider replacing x-> construct for speed
return quantile_newton(d, tau, quantile(dnorm, tau)*stdapprox)
end

minimum(d::Chernoff) = -Inf
Expand Down
33 changes: 33 additions & 0 deletions test/mixture.jl
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,37 @@ end
end
end
end

# issues #1611, #1807 and #1869
@testset "quantile convergence regressions" begin
cases = (
(
"nearly equal component quantiles (#1869)",
MixtureModel([Normal(0, 1), Normal(eps(), 1)], [0.999, 0.001]),
0.001,
),
(
"adjacent floating point bracket (#1611)",
MixtureModel(
[
Uniform{Float64}(-0.0001, 0.0001),
LogNormal{Float64}(11.174347445936371, 1.6086247197750911),
],
[0.8832, 0.1168],
),
0.99,
),
(
"high quantile of exponential mixture (#1807)",
MixtureModel(Exponential, [10_000, 1_000_000], [0.5, 0.5]),
0.999,
),
)

@testset "$name" for (name, d, p) in cases
x = quantile(d, p)
@test isfinite(x)
@test isapprox(cdf(d, x), p; atol=1e-10)
end
end
end
14 changes: 14 additions & 0 deletions test/quantile_newton.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ using Test
d = Normal()
@test Distributions.quantile_newton(d, 0.5) == quantile(d, 0.5)
@test Distributions.cquantile_newton(d, 0.5) == cquantile(d, 0.5)

# issues #1571 and #2061
@testset "InverseGaussian quantile convergence" begin
cases = (
(InverseGaussian(1.0, 0.25), 0.999996),
(InverseGaussian(2.8853900817779268), 0.9999996485182184),
)

@testset "p=$p" for (d, p) in cases
x = quantile(d, p)
@test isfinite(x)
@test isapprox(cdf(d, x), p; atol=1e-10)
end
end
11 changes: 11 additions & 0 deletions test/univariate/continuous/chernoff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,15 @@
for i=1:size(pdftest, 1)
@test isapprox(pdf(d, pdftest[i, 1]), pdftest[i, 2] ; atol = 1e-6)
end

# issue #1999
@testset "truncated quantile for non-precomputed probability (#1999)" begin
td = truncated(Chernoff(); lower=0.1)
p = 0.75
x = quantile(td, p)

@test isfinite(x)
@test x >= 0.1
@test isapprox(cdf(td, x), p; atol=1e-10)
end
end
Loading