From c8264761a3069ca869ed9dbb474591c8743c75a4 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 05:10:10 +0300 Subject: [PATCH 01/20] `quantile_newton()`: extract `df()` --- src/quantilealgs.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 8aa9f1b89..0cf41064f 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -48,13 +48,14 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - x = xs + (p - cdf(d, xs)) / pdf(d, xs) + f(x) = (p - cdf(d, x)) / pdf(d, x) + x = xs + f(xs) T = typeof(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) + x = x0 + f(x0) end return x elseif p == 0 From 672e46b9cec8a209d0fcaecd2f26f2442bf87024 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 05:37:44 +0300 Subject: [PATCH 02/20] `cquantile_newton()`: extract `df()` --- src/quantilealgs.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 0cf41064f..a1235df1f 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -68,13 +68,14 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real= end function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - x = xs + (ccdf(d, xs)-p) / pdf(d, xs) + f(x) = (ccdf(d, x)-p) / pdf(d, x) + x = xs + f(xs) T = typeof(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) + x = x0 + f(x0) end return x elseif p == 1 From fc9077839af61492ec2c32bf6e99b712c69d6696 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 05:38:16 +0300 Subject: [PATCH 03/20] `quantile_newton()`: extract `newton()` --- src/quantilealgs.jl | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index a1235df1f..79bdc4be7 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -47,17 +47,24 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # Distribution, with Application to the Inverse Gaussian Distribution # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf +function newton(f, xs::T=mode(d), tol::Real=1e-12) where {T} + x = xs + f(xs) + @assert typeof(x) === T + x0 = T(xs) + while abs(x-x0) > max(abs(x),abs(x0)) * tol + x0 = x + x = x0 + f(x0) + end + return x +end + function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) f(x) = (p - cdf(d, x)) / pdf(d, x) + # FIXME: can this be expressed via `promote_type()`? Test coverage missing. x = xs + f(xs) T = typeof(x) if 0 < p < 1 - x0 = T(xs) - while abs(x-x0) > max(abs(x),abs(x0)) * tol - x0 = x - x = x0 + f(x0) - end - return x + return newton(f, T(xs), tol) elseif p == 0 return T(minimum(d)) elseif p == 1 From 330f8fa23b62a8c6015b3515867fa47ac25ebd33 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 05:38:33 +0300 Subject: [PATCH 04/20] `cquantile_newton()`: refactor using `newton()` --- src/quantilealgs.jl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 79bdc4be7..60e5034c5 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -76,15 +76,11 @@ end function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) f(x) = (ccdf(d, x)-p) / pdf(d, x) + # FIXME: can this be expressed via `promote_type()`? Test coverage missing. x = xs + f(xs) T = typeof(x) if 0 < p < 1 - x0 = T(xs) - while abs(x-x0) > max(abs(x),abs(x0)) * tol - x0 = x - x = x0 + f(x0) - end - return x + return newton(f, T(xs), tol) elseif p == 1 return T(minimum(d)) elseif p == 0 From 1f9b7a3fb3a54a0c2f6f545ca5fa9aa64069d0ae Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 06:20:51 +0300 Subject: [PATCH 05/20] `invlogcdf_newton()`/`invlogccdf_newton()`: use strict inequality This is what the `quantile_newton()`/`cquantile_newton()` does, because otherwise they were able to end up in an endless loops, when the initial point and the mode are the same, see #666. I'm not sure this is needed here, but the next change is going to refactor them to use general `newton()`, which would make this change anyway, so unless we need the current behaviour, let's do this change explicitly. --- src/quantilealgs.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 60e5034c5..b1d1de349 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -96,13 +96,13 @@ function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Rea 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 + 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 + 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 @@ -123,13 +123,13 @@ function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Re 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 + 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 + 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 From 59a28310deee3d520a9cebe34cbf3dc3734a08cd Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 06:49:46 +0300 Subject: [PATCH 06/20] `invlogcdf_newton()`: extract `df(x)` --- src/quantilealgs.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index b1d1de349..d46ba013e 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -92,19 +92,21 @@ end function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) + f_a(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) + f_b(x) = exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) 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))) + x = x0 + f_a(x0) 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))) + x = x0 + f_a(x0) end else - x = x0 + exp(lp - logpdf(d,x0) + log1mexp(min(logcdf(d,x0)-lp,0))) + x = x0 + f_b(x0) 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))) + x = x0 + f_b(x0) end end return x From 2590ac0e654f65a387094797c5f662cba4402815 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 06:56:31 +0300 Subject: [PATCH 07/20] `invlogccdf_newton()`: extract `df(x)` --- src/quantilealgs.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index d46ba013e..e7995b074 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -121,19 +121,21 @@ end function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) + f_a(x) = exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) + f_b(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) 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))) + x = x0 + f_a(x0) 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))) + x = x0 + f_a(x0) end else - x = x0 - exp(lp - logpdf(d,x0) + log1mexp(min(logccdf(d,x0)-lp,0))) + x = x0 + f_b(x0) 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))) + x = x0 + f_b(x0) end end return x From 4e7f76c5f0b038f4ea259f582028acc4ebd54ed3 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 07:25:36 +0300 Subject: [PATCH 08/20] `invlogcdf_newton()`: refactor using `newton()` --- src/quantilealgs.jl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index e7995b074..59c294f4f 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -97,17 +97,9 @@ function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Rea if -Inf < lp < 0 x0 = T(xs) if lp < logcdf(d,x0) - x = x0 + f_a(x0) - while abs(x-x0) > max(abs(x),abs(x0)) * tol - x0 = x - x = x0 + f_a(x0) - end + return newton(f_a, T(xs), tol) else - x = x0 + f_b(x0) - while abs(x-x0) > max(abs(x),abs(x0))*tol - x0 = x - x = x0 + f_b(x0) - end + return newton(f_b, T(xs), tol) end return x elseif lp == -Inf From a319a7adc4f05fec2b14c5138db6ead0d16169b7 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 12 Sep 2024 07:25:43 +0300 Subject: [PATCH 09/20] `invlogccdf_newton()`: refactor using `newton()` --- src/quantilealgs.jl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 59c294f4f..b987b39ae 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -118,17 +118,9 @@ function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Re if -Inf < lp < 0 x0 = T(xs) if lp < logccdf(d,x0) - x = x0 + f_a(x0) - while abs(x-x0) > max(abs(x),abs(x0)) * tol - x0 = x - x = x0 + f_a(x0) - end + return newton(f_a, T(xs), tol) else - x = x0 + f_b(x0) - while abs(x-x0) > max(abs(x),abs(x0)) * tol - x0 = x - x = x0 + f_b(x0) - end + return newton(f_b, T(xs), tol) end return x elseif lp == -Inf From 4624b41f1493a4ebe802a6406398f6d6353570e0 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 02:43:02 +0300 Subject: [PATCH 10/20] `newton()`: more closely match text-book method, which performs subtraction, --- src/quantilealgs.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index b987b39ae..465a58281 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -48,20 +48,20 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf function newton(f, xs::T=mode(d), tol::Real=1e-12) where {T} - x = xs + f(xs) + x = xs - f(xs) @assert typeof(x) === T x0 = T(xs) while abs(x-x0) > max(abs(x),abs(x0)) * tol x0 = x - x = x0 + f(x0) + x = x0 - f(x0) end return x end function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - f(x) = (p - cdf(d, x)) / pdf(d, x) + f(x) = -((p - cdf(d, x)) / pdf(d, x)) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. - x = xs + f(xs) + x = xs - f(xs) T = typeof(x) if 0 < p < 1 return newton(f, T(xs), tol) @@ -75,9 +75,9 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real= end function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - f(x) = (ccdf(d, x)-p) / pdf(d, x) + f(x) = -((ccdf(d, x)-p) / pdf(d, x)) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. - x = xs + f(xs) + x = xs - f(xs) T = typeof(x) if 0 < p < 1 return newton(f, T(xs), tol) @@ -92,8 +92,8 @@ end function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) - f_a(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) - f_b(x) = exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) + f_a(x) = exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) + f_b(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) if -Inf < lp < 0 x0 = T(xs) if lp < logcdf(d,x0) @@ -113,8 +113,8 @@ end function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) - f_a(x) = exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) - f_b(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) + f_a(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) + f_b(x) = exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) if -Inf < lp < 0 x0 = T(xs) if lp < logccdf(d,x0) From ab895307a97b0760e2917552b89aa344f302572b Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 02:49:15 +0300 Subject: [PATCH 11/20] `newton()`: `f()` is more of a function to calculate the actual delta --- src/quantilealgs.jl | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 465a58281..e039a6247 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -47,24 +47,24 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # Distribution, with Application to the Inverse Gaussian Distribution # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf -function newton(f, xs::T=mode(d), tol::Real=1e-12) where {T} - x = xs - f(xs) +function newton(Δ, xs::T=mode(d), tol::Real=1e-12) where {T} + x = xs - Δ(xs) @assert typeof(x) === T x0 = T(xs) while abs(x-x0) > max(abs(x),abs(x0)) * tol x0 = x - x = x0 - f(x0) + x = x0 - Δ(x0) end return x end function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - f(x) = -((p - cdf(d, x)) / pdf(d, x)) + Δ(x) = -((p - cdf(d, x)) / pdf(d, x)) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. - x = xs - f(xs) + x = xs - Δ(xs) T = typeof(x) if 0 < p < 1 - return newton(f, T(xs), tol) + return newton(Δ, T(xs), tol) elseif p == 0 return T(minimum(d)) elseif p == 1 @@ -75,12 +75,12 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real= end function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - f(x) = -((ccdf(d, x)-p) / pdf(d, x)) + Δ(x) = -((ccdf(d, x)-p) / pdf(d, x)) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. - x = xs - f(xs) + x = xs - Δ(xs) T = typeof(x) if 0 < p < 1 - return newton(f, T(xs), tol) + return newton(Δ, T(xs), tol) elseif p == 1 return T(minimum(d)) elseif p == 0 @@ -92,14 +92,14 @@ end function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) - f_a(x) = exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) - f_b(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) + Δ_ver0(x) = exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) + Δ_ver1(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) if -Inf < lp < 0 x0 = T(xs) if lp < logcdf(d,x0) - return newton(f_a, T(xs), tol) + return newton(Δ_ver0, T(xs), tol) else - return newton(f_b, T(xs), tol) + return newton(Δ_ver1, T(xs), tol) end return x elseif lp == -Inf @@ -113,14 +113,14 @@ end function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) - f_a(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) - f_b(x) = exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) + Δ_ver0(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) + Δ_ver1(x) = exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) if -Inf < lp < 0 x0 = T(xs) if lp < logccdf(d,x0) - return newton(f_a, T(xs), tol) + return newton(Δ_ver0, T(xs), tol) else - return newton(f_b, T(xs), tol) + return newton(Δ_ver1, T(xs), tol) end return x elseif lp == -Inf From 0f93fa14c0443dcad214c3c5ed262bd8cf85d85f Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 02:52:51 +0300 Subject: [PATCH 12/20] `[c]quantile_newton()`: explicitly pass function and derivative into the `newton()` --- src/quantilealgs.jl | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index e039a6247..362609356 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -47,7 +47,7 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # Distribution, with Application to the Inverse Gaussian Distribution # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf -function newton(Δ, xs::T=mode(d), tol::Real=1e-12) where {T} +function newton_impl(Δ, xs::T=mode(d), tol::Real=1e-12) where {T} x = xs - Δ(xs) @assert typeof(x) === T x0 = T(xs) @@ -58,13 +58,20 @@ function newton(Δ, xs::T=mode(d), tol::Real=1e-12) where {T} return x end +function newton((f,df), xs::T=mode(d), tol::Real=1e-12) where {T} + Δ(x) = f(x)/df(x) + return newton_impl(Δ, xs, tol) +end + function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - Δ(x) = -((p - cdf(d, x)) / pdf(d, x)) + f(x) = -(p - cdf(d, x)) + df(x) = pdf(d, x) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. + Δ(x) = f(x)/df(x) x = xs - Δ(xs) T = typeof(x) if 0 < p < 1 - return newton(Δ, T(xs), tol) + return newton((f, df), T(xs), tol) elseif p == 0 return T(minimum(d)) elseif p == 1 @@ -75,12 +82,14 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real= end function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - Δ(x) = -((ccdf(d, x)-p) / pdf(d, x)) + f(x) = -(ccdf(d, x)-p) + df(x) = pdf(d, x) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. + Δ(x) = f(x)/df(x) x = xs - Δ(xs) T = typeof(x) if 0 < p < 1 - return newton(Δ, T(xs), tol) + return newton((f, df), T(xs), tol) elseif p == 1 return T(minimum(d)) elseif p == 0 @@ -97,9 +106,9 @@ function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Rea if -Inf < lp < 0 x0 = T(xs) if lp < logcdf(d,x0) - return newton(Δ_ver0, T(xs), tol) + return newton_impl(Δ_ver0, T(xs), tol) else - return newton(Δ_ver1, T(xs), tol) + return newton_impl(Δ_ver1, T(xs), tol) end return x elseif lp == -Inf @@ -118,9 +127,9 @@ function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Re if -Inf < lp < 0 x0 = T(xs) if lp < logccdf(d,x0) - return newton(Δ_ver0, T(xs), tol) + return newton_impl(Δ_ver0, T(xs), tol) else - return newton(Δ_ver1, T(xs), tol) + return newton_impl(Δ_ver1, T(xs), tol) end return x elseif lp == -Inf From e7203948c5e3a9f28fa523b8a25d02cf6bffaed0 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 18:42:55 +0300 Subject: [PATCH 13/20] `invlog[c]cdf_newton()`: explicitly pass function and derivative into the `newton() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These have `df(x)=1`, at least that is what the `df(x)` is if we solve `Δ(x)=f(x)/f'(x)` as an equation for `f'(x)`. --- src/quantilealgs.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 362609356..dc368298e 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -101,14 +101,15 @@ end function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) - Δ_ver0(x) = exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) - Δ_ver1(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) + f_ver0(x) = exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) + f_ver1(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) + df(x::T) where {T} = T(1) if -Inf < lp < 0 x0 = T(xs) if lp < logcdf(d,x0) - return newton_impl(Δ_ver0, T(xs), tol) + return newton((f_ver0,df), T(xs), tol) else - return newton_impl(Δ_ver1, T(xs), tol) + return newton((f_ver1,df), T(xs), tol) end return x elseif lp == -Inf @@ -122,14 +123,15 @@ end function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Real=mode(d), tol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) - Δ_ver0(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) - Δ_ver1(x) = exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) + f_ver0(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) + f_ver1(x) = exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) + df(x::T) where {T} = T(1) if -Inf < lp < 0 x0 = T(xs) if lp < logccdf(d,x0) - return newton_impl(Δ_ver0, T(xs), tol) + return newton((f_ver0,df), T(xs), tol) else - return newton_impl(Δ_ver1, T(xs), tol) + return newton((f_ver1,df), T(xs), tol) end return x elseif lp == -Inf From 8e13f42e202ab5ac5fbb987601fd2083c0706fa0 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 02:56:27 +0300 Subject: [PATCH 14/20] `[c]quantile_newton()`: sink negation into `f(x)` --- src/quantilealgs.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index dc368298e..543a16f72 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -64,7 +64,7 @@ function newton((f,df), xs::T=mode(d), tol::Real=1e-12) where {T} end function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - f(x) = -(p - cdf(d, x)) + f(x) = cdf(d, x) - p df(x) = pdf(d, x) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. Δ(x) = f(x)/df(x) @@ -82,7 +82,7 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real= end function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), tol::Real=1e-12) - f(x) = -(ccdf(d, x)-p) + f(x) = p - ccdf(d, x) df(x) = pdf(d, x) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. Δ(x) = f(x)/df(x) From cf55a373d6237b6a29b054aa2e1920f531a56161 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 03:21:52 +0300 Subject: [PATCH 15/20] `newton_impl()`: use `isapprox()` --- src/quantilealgs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 543a16f72..94608f575 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -51,7 +51,7 @@ function newton_impl(Δ, xs::T=mode(d), tol::Real=1e-12) where {T} x = xs - Δ(xs) @assert typeof(x) === T x0 = T(xs) - while abs(x-x0) > max(abs(x),abs(x0)) * tol + while !isapprox(x, x0, atol=0, rtol=tol) x0 = x x = x0 - Δ(x0) end From 96786f0b1a055647c8792abf7213d9d5d4c3010f Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 03:22:50 +0300 Subject: [PATCH 16/20] Be more specific that `tol` is really a `xrtol` --- src/quantilealgs.jl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index 94608f575..b6eb35cbd 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -47,23 +47,23 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # Distribution, with Application to the Inverse Gaussian Distribution # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf -function newton_impl(Δ, xs::T=mode(d), tol::Real=1e-12) where {T} +function newton_impl(Δ, xs::T=mode(d), xrtol::Real=1e-12) where {T} x = xs - Δ(xs) @assert typeof(x) === T x0 = T(xs) - while !isapprox(x, x0, atol=0, rtol=tol) + while !isapprox(x, x0, atol=0, rtol=xrtol) x0 = x x = x0 - Δ(x0) end return x end -function newton((f,df), xs::T=mode(d), tol::Real=1e-12) where {T} +function newton((f,df), xs::T=mode(d), xrtol::Real=1e-12) where {T} Δ(x) = f(x)/df(x) - return newton_impl(Δ, xs, tol) + return newton_impl(Δ, xs, xrtol) end -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), xrtol::Real=1e-12) f(x) = cdf(d, x) - p df(x) = pdf(d, x) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. @@ -71,7 +71,7 @@ function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real= x = xs - Δ(xs) T = typeof(x) if 0 < p < 1 - return newton((f, df), T(xs), tol) + return newton((f, df), T(xs), xrtol) elseif p == 0 return T(minimum(d)) elseif p == 1 @@ -81,7 +81,7 @@ 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), xrtol::Real=1e-12) f(x) = p - ccdf(d, x) df(x) = pdf(d, x) # FIXME: can this be expressed via `promote_type()`? Test coverage missing. @@ -89,7 +89,7 @@ function cquantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real x = xs - Δ(xs) T = typeof(x) if 0 < p < 1 - return newton((f, df), T(xs), tol) + return newton((f, df), T(xs), xrtol) elseif p == 1 return T(minimum(d)) elseif p == 0 @@ -99,7 +99,7 @@ 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), xrtol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) f_ver0(x) = exp(lp - logpdf(d,x) + logexpm1(max(logcdf(d,x)-lp,0))) f_ver1(x) = -exp(lp - logpdf(d,x) + log1mexp(min(logcdf(d,x)-lp,0))) @@ -107,9 +107,9 @@ function invlogcdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Rea if -Inf < lp < 0 x0 = T(xs) if lp < logcdf(d,x0) - return newton((f_ver0,df), T(xs), tol) + return newton((f_ver0,df), T(xs), xrtol) else - return newton((f_ver1,df), T(xs), tol) + return newton((f_ver1,df), T(xs), xrtol) end return x elseif lp == -Inf @@ -121,7 +121,7 @@ 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), xrtol::Real=1e-12) T = typeof(lp - logpdf(d,xs)) f_ver0(x) = -exp(lp - logpdf(d,x) + logexpm1(max(logccdf(d,x)-lp,0))) f_ver1(x) = exp(lp - logpdf(d,x) + log1mexp(min(logccdf(d,x)-lp,0))) @@ -129,9 +129,9 @@ function invlogccdf_newton(d::ContinuousUnivariateDistribution, lp::Real, xs::Re if -Inf < lp < 0 x0 = T(xs) if lp < logccdf(d,x0) - return newton((f_ver0,df), T(xs), tol) + return newton((f_ver0,df), T(xs), xrtol) else - return newton((f_ver1,df), T(xs), tol) + return newton((f_ver1,df), T(xs), xrtol) end return x elseif lp == -Inf From 2346d07f6fbf323d32833ffd7bd4a972f53009f1 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 13 Sep 2024 02:32:55 +0300 Subject: [PATCH 17/20] `newton()`: defer to `Roots.jl` implementation Note that we really do need `Roots.jl` 2.2.0-or-newer, because of https://github.com/JuliaMath/Roots.jl/pull/445 --- Project.toml | 2 ++ src/quantilealgs.jl | 16 +++------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Project.toml b/Project.toml index af12e4b7a..5483e8292 100644 --- a/Project.toml +++ b/Project.toml @@ -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" @@ -48,6 +49,7 @@ PDMats = "0.10, 0.11" Printf = "<0.0.1, 1" QuadGK = "2" Random = "<0.0.1, 1" +Roots = "2.2" SparseArrays = "<0.0.1, 1" SpecialFunctions = "1.2, 2" StableRNGs = "1" diff --git a/src/quantilealgs.jl b/src/quantilealgs.jl index b6eb35cbd..4733e3dc0 100644 --- a/src/quantilealgs.jl +++ b/src/quantilealgs.jl @@ -1,3 +1,5 @@ +using Roots + # Various algorithms for computing quantile function quantile_bisect(d::ContinuousUnivariateDistribution, p::Real, lx::T, rx::T) where {T<:Real} @@ -47,20 +49,8 @@ quantile_bisect(d::ContinuousUnivariateDistribution, p::Real) = # Distribution, with Application to the Inverse Gaussian Distribution # http://www.statsci.org/smyth/pubs/qinvgaussPreprint.pdf -function newton_impl(Δ, xs::T=mode(d), xrtol::Real=1e-12) where {T} - x = xs - Δ(xs) - @assert typeof(x) === T - x0 = T(xs) - while !isapprox(x, x0, atol=0, rtol=xrtol) - x0 = x - x = x0 - Δ(x0) - end - return x -end - function newton((f,df), xs::T=mode(d), xrtol::Real=1e-12) where {T} - Δ(x) = f(x)/df(x) - return newton_impl(Δ, xs, xrtol) + return find_zero((f,df), xs, Roots.Newton(), xatol=0, xrtol=xrtol, atol=0, rtol=eps(float(T)), maxiters=typemax(Int)) end function quantile_newton(d::ContinuousUnivariateDistribution, p::Real, xs::Real=mode(d), xrtol::Real=1e-12) From e548dc709a3a49ac39866aba8655c16e4d7dbb42 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 19 Sep 2024 23:22:09 +0300 Subject: [PATCH 18/20] `Roots.jl` require julia 1.6+ --- .github/workflows/CI.yml | 4 ++-- Project.toml | 2 +- test/aqua.jl | 3 --- test/testutils.jl | 6 ++---- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 886724bd2..eb6d22610 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: version: - - '1.3' + - '1.6' - '1' - pre os: @@ -36,7 +36,7 @@ jobs: with: version: ${{ matrix.version }} # ARM64 on macos-latest is neither supported by older Julia versions nor setup-julia - arch: ${{ matrix.os == 'macos-latest' && matrix.version != '1.3' && 'aarch64' || 'x64' }} + arch: ${{ matrix.os == 'macos-latest' && matrix.version >= '1.8' && 'aarch64' || 'x64' }} show-versioninfo: true - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 diff --git a/Project.toml b/Project.toml index 5483e8292..aab9155d1 100644 --- a/Project.toml +++ b/Project.toml @@ -59,7 +59,7 @@ StatsAPI = "1.6" StatsBase = "0.32, 0.33, 0.34" StatsFuns = "0.9.15, 1" Test = "<0.0.1, 1" -julia = "1.3" +julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/test/aqua.jl b/test/aqua.jl index 4dd8c0706..c984a3ef2 100644 --- a/test/aqua.jl +++ b/test/aqua.jl @@ -9,9 +9,6 @@ import Aqua Aqua.test_all( Distributions; ambiguities = false, - # On older Julia versions, installed dependencies are quite old - # Thus unbound type parameters show up that are fixed in newer versions - unbound_args = VERSION >= v"1.6", ) # Tests are not reliable on older Julia versions and # show ambiguities in loaded packages diff --git a/test/testutils.jl b/test/testutils.jl index 2859856a7..d9d24108d 100644 --- a/test/testutils.jl +++ b/test/testutils.jl @@ -50,10 +50,8 @@ function test_cgf(dist, ts) d(f) = Base.Fix1(ForwardDiff.derivative, f) κ₁ = d(Base.Fix1(cgf, dist))(0) @test κ₁ ≈ mean(dist) - if VERSION >= v"1.4" - κ₂ = d(d(Base.Fix1(cgf, dist)))(0) - @test κ₂ ≈ var(dist) - end + κ₂ = d(d(Base.Fix1(cgf, dist)))(0) + @test κ₂ ≈ var(dist) for t in ts val = @inferred cgf(dist, t) @test isfinite(val) From ef3d587e4f0da9524d494aca560055a0af6546b7 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 19 Sep 2024 20:18:07 +0300 Subject: [PATCH 19/20] Tests: disable Aqua tests before 1.9 - spurious failures --- test/aqua.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/aqua.jl b/test/aqua.jl index c984a3ef2..17b06f424 100644 --- a/test/aqua.jl +++ b/test/aqua.jl @@ -12,7 +12,7 @@ import Aqua ) # Tests are not reliable on older Julia versions and # show ambiguities in loaded packages - if VERSION >= v"1.6" + if VERSION >= v"1.9" Aqua.test_ambiguities(Distributions) end end From 9374853dbb2e4046d4e04457fc5b57bad1c0bac8 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 19 Sep 2024 23:23:51 +0300 Subject: [PATCH 20/20] CI does not pass with julia 1.6, so test 1.7 instead From the disscussion in the PR, it is not at all obvious to me what the proper solution here is. --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index eb6d22610..5d0505462 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: version: - - '1.6' + - '1.7' - '1' - pre os: