Skip to content
Open
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,13 +1,14 @@
name = "Distributions"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.123"
authors = ["JuliaStats"]
version = "0.25.124"

[deps]
AliasTables = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
HypergeometricFunctions = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already an indirect dependency via StatsFuns

LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PDMats = "90014a1f-27ba-587c-ab20-58faa44d9150"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Expand Down Expand Up @@ -42,6 +43,7 @@ Distributed = "<0.0.1, 1"
FillArrays = "0.9, 0.10, 0.11, 0.12, 0.13, 1"
FiniteDifferences = "0.12"
ForwardDiff = "0.10, 1"
HypergeometricFunctions = "0.3.28"
JSON = "0.21, 1"
LinearAlgebra = "<0.0.1, 1"
OffsetArrays = "1"
Expand Down
1 change: 1 addition & 0 deletions src/Distributions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import StatsBase: kurtosis, skewness, entropy, mode, modes,
import PDMats: dim, PDMat, invquad

using SpecialFunctions
import HypergeometricFunctions
using Base.MathConstants: eulergamma

import AliasTables
Expand Down
46 changes: 35 additions & 11 deletions src/univariate/continuous/rician.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ partype(d::Rician{T}) where {T<:Real} = T

#### Statistics

# helper
_Lhalf(x) = exp(x/2) * ((1-x) * besseli(zero(x), -x/2) - x * besseli(oneunit(x), -x/2))
# Laguerre function L_{1/2}
_Lhalf(x::Real) = HypergeometricFunctions._₁F₁(-one(x)/2, one(x), x)

mean(d::Rician) = d.σ * sqrthalfπ * _Lhalf(-d.ν^2/(2 * d.σ^2))
var(d::Rician) = 2 * d.σ^2 + d.ν^2 - halfπ * d.σ^2 * _Lhalf(-d.ν^2/(2 * d.σ^2))^2
mean(d::Rician) = d.σ * sqrthalfπ * _Lhalf(-(d.ν / d.σ)^2/2)
var(d::Rician) = 2 * d.σ^2 + d.ν^2 - halfπ * d.σ^2 * _Lhalf(-(d.ν/d.σ)^2/2)^2

function mode(d::Rician)
m = mean(d)
Expand All @@ -91,7 +91,21 @@ function _minimize_gss(f, a, b; tol=1e-12)
(b + a) / 2
end

#### PDF/CDF/quantile delegated to NoncentralChisq
#### PDF

function pdf(d::Rician, x::Real)
ν, σ = params(d)
result = x / σ^2 * exp(-((x-ν)/σ)^2/2) * besselix(0, (x * ν)/ σ^2)
return x < 0 || isinf(x) ? zero(result) : result
end

function logpdf(d::Rician, x::Real)
ν, σ = params(d)
result = log(abs(x) / σ^2 * besselix(0, (x * ν)/ σ^2)) - ((x-ν)/σ)^2/2
return x < 0 || isinf(x) ? oftype(result, -Inf) : result
end

#### quantile/CDF delegated to NoncentralChisq

function quantile(d::Rician, x::Real)
ν, σ = params(d)
Expand All @@ -103,16 +117,14 @@ function cquantile(d::Rician, x::Real)
return sqrt(cquantile(NoncentralChisq(2, (ν / σ)^2), x)) * σ
end

function pdf(d::Rician, x::Real)
function invlogcdf(d::Rician, lx::Real)
ν, σ = params(d)
result = 2 * x / σ^2 * pdf(NoncentralChisq(2, (ν / σ)^2), (x / σ)^2)
return x < 0 || isinf(x) ? zero(result) : result
return sqrt(invlogcdf(NoncentralChisq(2, (ν / σ)^2), lx)) * σ
end

function logpdf(d::Rician, x::Real)
function invlogccdf(d::Rician, lx::Real)
ν, σ = params(d)
result = log(2 * abs(x) / σ^2) + logpdf(NoncentralChisq(2, (ν / σ)^2), (x / σ)^2)
return x < 0 || isinf(x) ? oftype(result, -Inf) : result
return sqrt(invlogccdf(NoncentralChisq(2, (ν / σ)^2), lx)) * σ
end

function cdf(d::Rician, x::Real)
Expand All @@ -127,6 +139,18 @@ function logcdf(d::Rician, x::Real)
return x < 0 ? oftype(result, -Inf) : result
end

function ccdf(d::Rician, x::Real)
ν, σ = params(d)
result = ccdf(NoncentralChisq(2, (ν / σ)^2), (x / σ)^2)
return x < 0 ? one(result) : result
end

function logccdf(d::Rician, x::Real)
ν, σ = params(d)
result = logccdf(NoncentralChisq(2, (ν / σ)^2), (x / σ)^2)
return x < 0 ? zero(result) : result
end

#### Sampling

function rand(rng::AbstractRNG, d::Rician)
Expand Down
57 changes: 56 additions & 1 deletion test/univariate/continuous/rician.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using StatsFuns

@testset "Rician" begin

d1 = Rician(0.0, 10.0)
Expand All @@ -9,6 +11,24 @@
@test eltype(d1) === Float64
@test rand(d1) isa Float64

# Reference: WolframAlpha
@test @inferred(mean(d1))::Float64 ≈ 5 * sqrt(2 * π)
@test @inferred(std(d1))::Float64 ≈ sqrt(200 - 50 * π)
@test @inferred(var(d1))::Float64 ≈ 200 - 50 * π
@test @inferred(mode(d1))::Float64 ≈ 10
ps = 0:0.1:1
@test all(splat(isapprox), zip(quantile.(d1, ps), @. sqrt(-200 * log1p(-ps))))
@test all(splat(isapprox), zip(cquantile.(d1, ps), @. sqrt(-200 * log(ps))))
@test all(splat(isapprox), zip(invlogcdf.(d1, log.(ps)), @. sqrt(-200 * log1p(-ps))))
@test all(splat(isapprox), zip(invlogccdf.(d1, log.(ps)), @. sqrt(-200 * log(ps))))
xs = 0:0.5:30 # 99th percentile is approx 30
@test all(splat(isapprox), zip(pdf.(d1, xs), map(x -> x / 100 * exp(-x^2/200), xs)))
@test all(splat(isapprox), zip(logpdf.(d1, xs), map(x -> log(x / 100) - x^2/200, xs)))
@test all(splat(isapprox), zip(cdf.(d1, xs), @. 1 - exp(-xs^2/200)))
@test all(splat(isapprox), zip(logcdf.(d1, xs), @. log1mexp(-xs^2/200)))
@test all(splat(isapprox), zip(ccdf.(d1, xs), @. exp(-xs^2/200)))
@test all(splat(isapprox), zip(logccdf.(d1, xs), @. -xs^2/200))
Comment on lines +29 to +30
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallbacks are too inaccurate, without dedicated overloads for ccdf and logccdf these tests fail.


d2 = Rayleigh(10.0)
@test mean(d1) ≈ mean(d2)
@test var(d1) ≈ var(d2)
Expand All @@ -23,7 +43,22 @@
x = Base.Fix1(quantile, d1).([0.25, 0.45, 0.60, 0.80, 0.90])
@test all(Base.Fix1(cdf, d1).(x) .≈ [0.25, 0.45, 0.60, 0.80, 0.90])

x = rand(Rician(5.0, 5.0), 100000)
# Reference: WolframAlpha
@test mean(d1) ≈ 15.4857246055
@test std(d1) ≈ 7.75837182934
@test var(d1) ≈ 60.1923334423
@test median(d1) ≈ 14.7547909179
@test all(xy -> isapprox(xy...; rtol=1e-5), zip(quantile.(d1, [0.1, 0.25, 0.5, 0.75, 0.9]), [5.86808, 9.62923, 14.7548, 20.5189, 26.0195]))

d1 = Rician(5.0, 5.0)
# Reference: WolframAlpha
@test mean(d1) ≈ 7.7428623027557
@test std(d1) ≈ 3.8791859146687
@test var(d1) ≈ 15.0480833605606
@test median(d1) ≈ 7.37739545894
@test all(xy -> isapprox(xy...; rtol=1e-5), zip(quantile.(d1, [0.1, 0.25, 0.5, 0.75, 0.9]), [2.93404, 4.81462, 7.3774, 10.2595, 13.0097]))

x = rand(d1, 100000)
d1 = fit(Rician, x)
@test d1 isa Rician{Float64}
@test params(d1)[1] ≈ 5.0 atol=0.2
Expand All @@ -37,6 +72,11 @@
@test partype(d1) === Float32
@test eltype(d1) === Float64
@test rand(d1) isa Float64
d1_f64 = Rician(10.0, 10.0)
@test @inferred(mean(d1))::Float32 ≈ Float32(mean(d1_f64))
@test @inferred(std(d1))::Float32 ≈ Float32(std(d1_f64))
@test @inferred(var(d1))::Float32 ≈ Float32(var(d1_f64))
@test @inferred(median(d1))::Float32 ≈ Float32(median(d1_f64))

d1 = Rician()
@test d1 isa Rician{Float64}
Expand Down Expand Up @@ -94,4 +134,19 @@
@inferred logcdf(d1, 1//2)
@inferred logcdf(d1, Inf)

# issue #2035
# reference values computed with WolframAlpha
d = Rician(1.0, 0.12)
@test mean(d) ≈ 1.00722650704
@test std(d) ≈ 0.119560710568
@test var(d) ≈ 0.0142947635114
@test median(d) ≈ 1.00719144719
@test pdf(d, 0.5) ≈ 0.000400758853946
@test pdf(d, 1) ≈ 3.33055235263
@test pdf(d, 1.5) ≈ 0.000692437774661
@test pdf(d, 2) ≈ 3.91711741719e-15
@test logpdf(d, 0.5) ≈ -7.82215067328
@test logpdf(d, 1) ≈ 1.2031381619
@test logpdf(d, 1.5) ≈ -7.27529218003
@test logpdf(d, 2) ≈ -33.1734203644
end
Loading