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
2 changes: 1 addition & 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.126"
version = "0.25.127"

[deps]
AliasTables = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
Expand Down
2 changes: 1 addition & 1 deletion src/censored.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct Censored{
lower::TL # lower bound
upper::TU # upper bound
function Censored(d0::UnivariateDistribution, lower::T, upper::T; check_args::Bool=true) where {T<:Real}
@check_args(Censored, lower ≤ upper)
@check_args(Censored, ((; lower, upper), lower ≤ upper, "lower bound must be ≤ upper bound"))
new{typeof(d0), value_support(typeof(d0)), T, T, T}(d0, lower, upper)
end
function Censored(d0::UnivariateDistribution, l::Nothing, u::Real; check_args::Bool=true)
Expand Down
2 changes: 1 addition & 1 deletion src/matrix/inversewishart.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ end

function InverseWishart(df::T, Ψ::AbstractPDMat{T}) where T<:Real
p = size(Ψ, 1)
df > p - 1 || throw(ArgumentError("df should be greater than dim - 1."))
df > p - 1 || throw(DomainError(df, "InverseWishart: df should be greater than dim - 1."))
logc0 = invwishart_logc0(df, Ψ)
R = Base.promote_eltype(T, logc0)
prom_Ψ = convert(AbstractArray{R}, Ψ)
Expand Down
2 changes: 1 addition & 1 deletion src/matrix/matrixbeta.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ end
# -----------------------------------------------------------------------------

function MatrixBeta(p::Int, n1::Real, n2::Real)
p > 0 || throw(ArgumentError("dim must be positive: got $(p)."))
p > 0 || throw(DomainError(p, "MatrixBeta: dim must be positive."))
logc0 = matrixbeta_logc0(p, n1, n2)
T = Base.promote_eltype(n1, n2, logc0)
Ip = ScalMat(p, one(T))
Expand Down
4 changes: 2 additions & 2 deletions src/matrix/matrixfdist.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ end

function MatrixFDist(n1::Real, n2::Real, B::AbstractPDMat)
p = size(B, 1)
n1 > p - 1 || throw(ArgumentError("first degrees of freedom must be larger than $(p - 1)"))
n2 > p - 1 || throw(ArgumentError("second degrees of freedom must be larger than $(p - 1)"))
n1 > p - 1 || throw(DomainError(n1, "MatrixFDist: first degrees of freedom must be larger than dim - 1."))
n2 > p - 1 || throw(DomainError(n2, "MatrixFDist: second degrees of freedom must be larger than dim - 1."))
logc0 = matrixfdist_logc0(n1, n2, B)
T = Base.promote_eltype(n1, n2, logc0, B)
prom_B = convert(AbstractArray{T}, B)
Expand Down
4 changes: 2 additions & 2 deletions src/matrix/matrixnormal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ end

function MatrixNormal(M::AbstractMatrix{T}, U::AbstractPDMat{T}, V::AbstractPDMat{T}) where T <: Real
n, p = size(M)
n == size(U, 1) || throw(ArgumentError("Number of rows of M must equal dim of U."))
p == size(V, 1) || throw(ArgumentError("Number of columns of M must equal dim of V."))
n == size(U, 1) || throw(DimensionMismatch("MatrixNormal: number of rows of M must equal dim of U."))
p == size(V, 1) || throw(DimensionMismatch("MatrixNormal: number of columns of M must equal dim of V."))
logc0 = matrixnormal_logc0(U, V)
R = Base.promote_eltype(T, logc0)
prom_M = convert(AbstractArray{R}, M)
Expand Down
6 changes: 3 additions & 3 deletions src/matrix/matrixtdist.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ end

function MatrixTDist(ν::T, M::AbstractMatrix{T}, Σ::AbstractPDMat{T}, Ω::AbstractPDMat{T}) where T <: Real
n, p = size(M)
0 < ν < Inf || throw(ArgumentError("degrees of freedom must be positive and finite."))
n == size(Σ, 1) || throw(ArgumentError("Number of rows of M must equal dim of Σ."))
p == size(Ω, 1) || throw(ArgumentError("Number of columns of M must equal dim of Ω."))
0 < ν < Inf || throw(DomainError(ν, "MatrixTDist: degrees of freedom must be positive and finite."))
n == size(Σ, 1) || throw(DimensionMismatch("MatrixTDist: number of rows of M must equal dim of Σ."))
p == size(Ω, 1) || throw(DimensionMismatch("MatrixTDist: number of columns of M must equal dim of Ω."))
logc0 = matrixtdist_logc0(Σ, Ω, ν)
R = Base.promote_eltype(T, logc0)
prom_M = convert(AbstractArray{R}, M)
Expand Down
4 changes: 2 additions & 2 deletions src/matrix/wishart.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ end
# -----------------------------------------------------------------------------

function Wishart(df::T, S::AbstractPDMat{T}) where T<:Real
df > 0 || throw(ArgumentError("df must be positive. got $(df)."))
df > 0 || throw(DomainError(df, "Wishart: df must be positive."))
p = size(S, 1)
singular = df <= p - 1
if singular
isinteger(df) || throw(
ArgumentError("df of a singular Wishart distribution must be an integer (got $df)")
DomainError(df, "Wishart: df of a singular Wishart distribution must be an integer.")
)
end
rnk::Integer = ifelse(singular, df, p)
Expand Down
4 changes: 2 additions & 2 deletions src/multivariate/dirichletmultinomial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ struct DirichletMultinomial{T <: Real,V <: AbstractVector{T}} <: DiscreteMultiva

function DirichletMultinomial{T}(n::Integer, α::V) where {T <: Real, V <: AbstractVector{T}}
α0 = sum(abs, α)
sum(α) == α0 || throw(ArgumentError("alpha must be a positive vector."))
n > 0 || throw(ArgumentError("n must be a positive integer."))
sum(α) == α0 || throw(DomainError(α, "DirichletMultinomial: alpha must be a positive vector."))
n > 0 || throw(DomainError(n, "DirichletMultinomial: n must be a positive integer."))
new{T,V}(Int(n), α, α0)
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/univariate/continuous/arcsine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct Arcsine{T<:Real} <: ContinuousUnivariateDistribution
end

function Arcsine(a::T, b::T; check_args::Bool=true) where {T <: Real}
@check_args Arcsine a < b
@check_args Arcsine ((; a, b), a < b, "a must be less than b")
return Arcsine{T}(a, b)
end

Expand Down
2 changes: 1 addition & 1 deletion src/univariate/continuous/loglogistic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct LogLogistic{T<:Real} <: ContinuousUnivariateDistribution
end

function LogLogistic(α::T, β::T; check_args::Bool=true) where {T <: Real}
check_args && @check_args(LogLogistic, α > zero(α) && β > zero(β))
@check_args LogLogistic (α, α > zero(α)) (β, β > zero(β))
return LogLogistic{T}(α, β)
end

Expand Down
2 changes: 1 addition & 1 deletion src/univariate/continuous/loguniform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct LogUniform{T<:Real} <: ContinuousUnivariateDistribution
end

function LogUniform(a::T, b::T; check_args::Bool=true) where {T <: Real}
@check_args LogUniform (0 < a < b)
@check_args LogUniform ((; a, b), 0 < a < b, "a and b must satisfy 0 < a < b")
LogUniform{T}(a, b)
end

Expand Down
2 changes: 1 addition & 1 deletion src/univariate/continuous/normal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ struct NormalKnownSigma <: IncompleteDistribution
σ::Float64

function NormalKnownSigma(σ::Float64)
σ > 0 || throw(ArgumentError("σ must be a positive value."))
σ > 0 || throw(DomainError(σ, "NormalKnownSigma: σ must be a positive value."))
new(σ)
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/univariate/continuous/triangular.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct TriangularDist{T<:Real} <: ContinuousUnivariateDistribution
end

function TriangularDist(a::T, b::T, c::T; check_args::Bool=true) where {T <: Real}
@check_args TriangularDist (a <= c <= b)
@check_args TriangularDist ((; a, b, c), a <= c <= b, "a <= c <= b must hold")
return TriangularDist{T}(a, b, c)
end

Expand Down
2 changes: 1 addition & 1 deletion src/univariate/continuous/uniform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct Uniform{T<:Real} <: ContinuousUnivariateDistribution
end

function Uniform(a::T, b::T; check_args::Bool=true) where {T <: Real}
@check_args Uniform (a < b)
@check_args Uniform ((; a, b), a < b, "a must be less than b")
return Uniform{T}(a, b)
end

Expand Down
8 changes: 6 additions & 2 deletions src/univariate/discrete/discretenonparametric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ struct DiscreteNonParametric{T<:Real,P<:Real,Ts<:AbstractVector{T},Ps<:AbstractV
function DiscreteNonParametric{T,P,Ts,Ps}(xs::Ts, ps::Ps; check_args::Bool=true) where {
T<:Real,P<:Real,Ts<:AbstractVector{T},Ps<:AbstractVector{P}}
check_args || return new{T,P,Ts,Ps}(xs, ps)
length(xs) == length(ps) ||
throw(DimensionMismatch("DiscreteNonParametric: length of support and probability vector must be equal."))
@check_args(
DiscreteNonParametric,
(length(xs) == length(ps), "length of support and probability vector must be equal"),
(ps, isprobvec(ps), "vector is not a probability vector"),
(xs, allunique(xs), "support must contain only unique elements"),
)
Expand Down Expand Up @@ -275,7 +276,10 @@ end
function suffstats(::Type{<:DiscreteNonParametric}, x::AbstractArray{T},
w::AbstractArray{W}; check_args::Bool=true) where {T<:Real,W<:Real}

@check_args DiscreteNonParametric (length(x) == length(w))
if check_args
length(x) == length(w) ||
throw(DimensionMismatch("DiscreteNonParametric: length(x) must equal length(w)."))
end

N = length(x)
N == 0 && return DiscreteNonParametricStats(T[], W[])
Expand Down
2 changes: 1 addition & 1 deletion src/univariate/discrete/discreteuniform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct DiscreteUniform <: DiscreteUnivariateDistribution
pv::Float64 # individual probabilities

function DiscreteUniform(a::Real, b::Real; check_args::Bool=true)
@check_args DiscreteUniform (a <= b)
@check_args DiscreteUniform ((; a, b), a <= b, "a must be ≤ b")
new(a, b, 1 / (b - a + 1))
end
DiscreteUniform(b::Real; check_args::Bool=true) = DiscreteUniform(0, b; check_args=check_args)
Expand Down
2 changes: 1 addition & 1 deletion src/univariate/discrete/hypergeometric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct Hypergeometric <: DiscreteUnivariateDistribution
Hypergeometric,
(ns, ns >= zero(ns)),
(nf, nf >= zero(nf)),
zero(n) <= n <= ns + nf,
((; n, ns, nf), zero(n) <= n <= ns + nf, "n must satisfy 0 ≤ n ≤ ns + nf"),
)
new(ns, nf, n)
end
Expand Down
4 changes: 2 additions & 2 deletions src/univariate/discrete/noncentralhypergeometric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct FisherNoncentralHypergeometric{T<:Real} <: NoncentralHypergeometric{T}
FisherNoncentralHypergeometric,
(ns, ns >= zero(ns)),
(nf, nf >= zero(nf)),
zero(n) < n < ns + nf,
((; n, ns, nf), zero(n) < n < ns + nf, "n must satisfy 0 < n < ns + nf"),
(ω, ω > zero(ω)),
)
new{T}(ns, nf, n, ω)
Expand Down Expand Up @@ -235,7 +235,7 @@ struct WalleniusNoncentralHypergeometric{T<:Real} <: NoncentralHypergeometric{T}
WalleniusNoncentralHypergeometric,
(ns, ns >= zero(ns)),
(nf, nf >= zero(nf)),
zero(n) < n < ns + nf,
((; n, ns, nf), zero(n) < n < ns + nf, "n must satisfy 0 < n < ns + nf"),
(ω, ω > zero(ω)),
)
new{T}(ns, nf, n, ω)
Expand Down
2 changes: 1 addition & 1 deletion src/univariate/orderstatistic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct OrderStatistic{D<:UnivariateDistribution,S<:ValueSupport} <:
function OrderStatistic(
dist::UnivariateDistribution, n::Int, rank::Int; check_args::Bool=true
)
@check_args(OrderStatistic, 1 ≤ rank ≤ n)
@check_args(OrderStatistic, ((; rank, n), 1 ≤ rank ≤ n, "rank must satisfy 1 ≤ rank ≤ n"))
return new{typeof(dist),value_support(typeof(dist))}(dist, n, rank)
end
end
Expand Down
46 changes: 23 additions & 23 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ end
D,
@setup(statements...),
(arg₁, cond₁, message₁),
(cond₂, message₂),
(arg₂, cond₂),
...,
)

Expand All @@ -21,14 +21,19 @@ the following Julia code:
Distributions.check_args(check_args) do
\$(statements...)
cond₁ || throw(DomainError(arg₁, \$(string(D, ": ", message₁))))
cond₂ || throw(ArgumentError(\$(string(D, ": ", message₂))))
cond₂ || throw(DomainError(arg₂, \$(string(D, ": the condition ", cond₂, " is not satisfied."))))
...
end
```

The `@setup` argument can be elided if no setup code is needed. Moreover, error messages
can be omitted. In this case the message `"the condition \$(cond) is not satisfied."` is
used.
Every check must provide the offending value(s) `argᵢ` so the resulting `DomainError`
carries the parameter that violated the domain on `err.val`. For relational checks
involving multiple parameters (e.g. `a < b`), pass a named tuple such as `(; a, b)` as
the value so downstream catchers can read the individual parameters by name.

The `@setup` argument can be elided if no setup code is needed. The third message
element can also be elided; in that case the message `"the condition \$(cond) is not
satisfied."` is used.
"""
macro check_args(D, setup_or_check, checks...)
# Extract setup statements
Expand All @@ -51,25 +56,15 @@ macro check_args(D, setup_or_check, checks...)
message = string(D, ": ", check.args[3])
return :(($(esc(cond))) || throw(DomainError($(esc(arg)), $message)))
elseif Meta.isexpr(check, :tuple, 2)
cond_or_message = check.args[2]
if cond_or_message isa String
# only condition and message specified
cond = check.args[1]
message = string(D, ": ", cond_or_message)
return :(($(esc(cond))) || throw(ArgumentError($message)))
else
# only argument and condition specified
arg = check.args[1]
push!(args_exprs, esc(arg))
cond = cond_or_message
message = string(D, ": the condition ", cond, " is not satisfied.")
return :(($(esc(cond))) || throw(DomainError($(esc(arg)), $message)))
end
else
# only condition specified
cond = check
# argument and condition specified; auto-generate message
arg = check.args[1]
push!(args_exprs, esc(arg))
cond = check.args[2]
message = string(D, ": the condition ", cond, " is not satisfied.")
return :(($(esc(cond))) || throw(ArgumentError($message)))
return :(($(esc(cond))) || throw(DomainError($(esc(arg)), $message)))
else
error("`@check_args` requires each check to include an offending value; " *
"use `(arg, cond)` or `(arg, cond, message)`. Got: `", check, "`.")
end
end

Expand All @@ -92,8 +87,13 @@ extensions may specialize this to `false` for number-like types whose comparison
boolean context — for example sparsity-detection tracers, whose primal-free comparisons (`σ ≥ 0`)
return a tracer rather than a `Bool`. When any checked argument is not checkable, `@check_args` skips
the checks so distribution constructors can still be traced.

A relational check over several parameters (e.g. `a < b`) names them with a tuple/named tuple such
as `(; a, b)`; recurse into it so the check is skipped whenever *any* contained value is not
checkable (e.g. a tracer).
=#
_arg_checkable(@nospecialize(_)) = true
_arg_checkable(x::Union{Tuple,NamedTuple}) = all(_arg_checkable, x)

"""
check_args(f, check::Bool)
Expand Down
6 changes: 3 additions & 3 deletions test/censored.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ end

@testset "censored" begin
d0 = Normal(0, 1)
@test_throws ArgumentError censored(d0, 1, -1)
@test_throws DomainError censored(d0, 1, -1)

# bound argument constructors
d = censored(d0, -1, 1.0)
Expand Down Expand Up @@ -92,8 +92,8 @@ end
@testset "Censored" begin
@testset "basic" begin
# check_args
@test_throws ArgumentError Censored(Normal(0, 1), 2, 1)
@test_throws ArgumentError Censored(Normal(0, 1), 2, 1; check_args=true)
@test_throws DomainError Censored(Normal(0, 1), 2, 1)
@test_throws DomainError Censored(Normal(0, 1), 2, 1; check_args=true)
Censored(Normal(0, 1), 2, 1; check_args=false)
Censored(Normal(0, 1), nothing, 1; check_args=true)
Censored(Normal(0, 1), 2, nothing; check_args=true)
Expand Down
Loading
Loading