Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Distributions"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
authors = ["JuliaStats"]
version = "0.25.127"
version = "0.25.128"

[deps]
AliasTables = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
Expand All @@ -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 = "3"
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
95 changes: 30 additions & 65 deletions src/quantilealgs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,11 @@ function quantile_bisect(d::ContinuousUnivariateDistribution, p::Real, lx::T, rx
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
end
return (lx + rx)/2
# Rely on Roots' default tolerances for the bracketing solver. Unlike the previous
# hand-rolled bisection, ITP uses a relative `xrtol = eps`, so it converges even for
# brackets far from zero where an absolute tolerance is below the floating-point spacing
# (#1611).
return find_zero(x -> cdf(d, x) - p, (lx, rx), ITP())
end

function quantile_bisect(d::ContinuousUnivariateDistribution, p::Real, lx::Real, rx::Real)
Expand All @@ -47,16 +31,16 @@ 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))
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
# Newton's method with an ITP bracketing fallback: Roots switches to ITP whenever the
# iteration brackets the root (sign change of `F`), which resolves the oscillation/stalling
# observed for extreme quantiles (#2061, #1898). Roots' default tolerances are used.
return find_zero((F, f), x, Newton(), ITP())
elseif p == 0
return T(minimum(d))
elseif p == 1
Expand All @@ -66,16 +50,13 @@ 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))
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
return find_zero((F, f), x, Newton(), ITP())
elseif p == 1
return T(minimum(d))
elseif p == 0
Expand All @@ -85,24 +66,16 @@ 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))
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)))
return find_zero((F, f), x, Newton(), ITP())
elseif lp == -Inf
return T(minimum(d))
elseif lp == 0
Expand All @@ -112,24 +85,16 @@ 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))
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)))
return find_zero((F, f), x, Newton(), ITP())
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
24 changes: 24 additions & 0 deletions test/mixture.jl
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,28 @@ end
end
end
end

# issue #1611: bracket of adjacent floating point numbers far from zero
@testset "adjacent floating point bracket (#1611)" begin
d = MixtureModel(
[
Uniform{Float64}(-0.0001, 0.0001),
LogNormal{Float64}(11.174347445936371, 1.6086247197750911),
],
[0.8832, 0.1168],
)
p = 0.99
x = quantile(d, p)
@test isfinite(x)
@test cdf(d, x) ≈ p
end

# issue #1807: high quantile of a mixture of exponentials with very different scales
@testset "high quantile of exponential mixture (#1807)" begin
d = MixtureModel(Exponential, [10_000, 1_000_000], [0.5, 0.5])
p = 0.999
x = quantile(d, p)
@test isfinite(x)
@test cdf(d, x) ≈ p
end
end
40 changes: 40 additions & 0 deletions test/quantile_newton.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,43 @@ 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)

# issue #1571
@testset "InverseGaussian quantile convergence (#1571)" begin
d = InverseGaussian(1.0, 0.25)
p = 0.999996
x = quantile(d, p)
@test isfinite(x)
@test cdf(d, x) ≈ p
end

# issue #2061
@testset "InverseGaussian quantile convergence (#2061)" begin
d = InverseGaussian(2.8853900817779268)
p = 0.9999996485182184
x = quantile(d, p)
@test isfinite(x)
@test cdf(d, x) ≈ p
end

# issue #1898: large-σ InverseGaussian, `p` far in the tail. `2 * cdf(Normal(), 5) - 1` is the
# value `erf(5 / sqrt(2))` from the original report.
@testset "InverseGaussian quantile convergence (#1898)" begin
d = InverseGaussian(1.187997687788096, 60.467382225458564)
p = 2 * cdf(Normal(), 5) - 1
x = quantile(d, p)
@test isfinite(x)
@test cdf(d, x) ≈ p
end

# the Roots defaults are type-aware: `Float32` must converge (and preserve the element type)
# without the absolute tolerance floor that the hand-rolled loops needed to avoid an infinite loop.
@testset "Float32 quantile round-trip" begin
d = InverseGaussian(2.0f0, 3.0f0)
for p in (0.1f0, 0.5f0, 0.9f0, 0.999f0)
x = quantile(d, p)
@test x isa Float32
@test isfinite(x)
@test cdf(d, x) ≈ p
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 cdf(td, x) ≈ p
end
end
Loading