diff --git a/docs/src/extends.md b/docs/src/extends.md index 0c87b8bfb..25eae4ecd 100644 --- a/docs/src/extends.md +++ b/docs/src/extends.md @@ -4,11 +4,8 @@ Whereas this package already provides a large collection of common distributions Generally, you don't have to implement every API method listed in the documentation. This package provides a series of generic functions that turn a small number of internal methods into user-end API methods. What you need to do is to implement this small set of internal methods for your distributions. -By default, `Discrete` sampleables have the support of type `Int` while `Continuous` sampleables have the support of type `Float64`. If this assumption does not hold for your new distribution or sampler, or its `ValueSupport` is neither `Discrete` nor `Continuous`, you should implement the `eltype` method in addition to the other methods listed below. - **Note:** The methods that need to be implemented are different for distributions of different variate forms. - ## Create a Sampler Unlike full-fledged distributions, a sampler, in general, only provides limited functionalities, mainly to support sampling. @@ -18,60 +15,48 @@ Unlike full-fledged distributions, a sampler, in general, only provides limited To implement a univariate sampler, one can define a subtype (say `Spl`) of `Sampleable{Univariate,S}` (where `S` can be `Discrete` or `Continuous`), and provide a `rand` method, as ```julia -function rand(rng::AbstractRNG, s::Spl) +function Base.rand(rng::AbstractRNG, s::Spl) # ... generate a single sample from s end ``` -The package already implements a vectorized version of `rand!` and `rand` that repeatedly calls the scalar version to generate multiple samples; as wells as a one arg version that uses the default random number generator. - -### Multivariate Sampler +The package already implements vectorized versions `rand!(rng::AbstractRNG, s::Spl, dims::Int...)` and `rand(rng::AbstractRNG, s::Spl, dims::Int...)` that repeatedly call the scalar version to generate multiple samples. +Additionally, the package implements versions of these functions without the `rng::AbstractRNG` argument that use the default random number generator. -To implement a multivariate sampler, one can define a subtype of `Sampleable{Multivariate,S}`, and provide both `length` and `_rand!` methods, as +If there is a more efficient method to generate multiple samples, one should provide the following method ```julia -Base.length(s::Spl) = ... # return the length of each sample - -function _rand!(rng::AbstractRNG, s::Spl, x::AbstractVector{T}) where T<:Real - # ... generate a single vector sample to x +function Random.rand!(rng::AbstractRNG, s::Spl, x::AbstractArray{<:Real}) + # ... generate multiple samples from s in x end ``` -This function can assume that the dimension of `x` is correct, and doesn't need to perform dimension checking. +### Multivariate Sampler -The package implements both `rand` and `rand!` as follows (which you don't need to implement in general): +To implement a multivariate sampler, one can define a subtype of `Sampleable{Multivariate,S}`, and provide `length`, `rand`, and `rand!` methods, as ```julia -function _rand!(rng::AbstractRNG, s::Sampleable{Multivariate}, A::DenseMatrix) - for i = 1:size(A,2) - _rand!(rng, s, view(A,:,i)) - end - return A -end +Base.length(s::Spl) = ... # return the length of each sample -function rand!(rng::AbstractRNG, s::Sampleable{Multivariate}, A::AbstractVector) - length(A) == length(s) || - throw(DimensionMismatch("Output size inconsistent with sample length.")) - _rand!(rng, s, A) +function Base.rand(rng::AbstractRNG, s::Spl) + # ... generate a single vector sample from s end -function rand!(rng::AbstractRNG, s::Sampleable{Multivariate}, A::DenseMatrix) - size(A,1) == length(s) || - throw(DimensionMismatch("Output size inconsistent with sample length.")) - _rand!(rng, s, A) +@inline function Random.rand!(rng::AbstractRNG, s::Spl, x::AbstractVector{<:Real}) + # `@inline` + `@boundscheck` allows users to skip bound checks by calling `@inbounds rand!(...)` + # Ref https://docs.julialang.org/en/v1/devdocs/boundscheck/#Eliding-bounds-checks + @boundscheck # ... check size (and possibly indices) of `x` + # ... generate a single vector sample from s in x end - -rand(rng::AbstractRNG, s::Sampleable{Multivariate,S}) where {S<:ValueSupport} = - _rand!(rng, s, Vector{eltype(S)}(length(s))) - -rand(rng::AbstractRNG, s::Sampleable{Multivariate,S}, n::Int) where {S<:ValueSupport} = - _rand!(rng, s, Matrix{eltype(S)}(length(s), n)) ``` If there is a more efficient method to generate multiple vector samples in a batch, one should provide the following method ```julia -function _rand!(rng::AbstractRNG, s::Spl, A::DenseMatrix{T}) where T<:Real +@inline function Random.rand!(rng::AbstractRNG, s::Spl, A::AbstractMatrix{<:Real}) + # `@inline` + `@boundscheck` allows users to skip bound checks by calling `@inbounds rand!(...)` + # Ref https://docs.julialang.org/en/v1/devdocs/boundscheck/#Eliding-bounds-checks + @boundscheck # ... check size (and possibly indices) of `x` # ... generate multiple vector samples in batch end ``` @@ -80,17 +65,22 @@ Remember that each *column* of A is a sample. ### Matrix-variate Sampler -To implement a multivariate sampler, one can define a subtype of `Sampleable{Multivariate,S}`, and provide both `size` and `_rand!` methods, as +To implement a multivariate sampler, one can define a subtype of `Sampleable{Multivariate,S}`, and provide `size`, `rand`, and `rand!` methods, as ```julia Base.size(s::Spl) = ... # the size of each matrix sample -function _rand!(rng::AbstractRNG, s::Spl, x::DenseMatrix{T}) where T<:Real - # ... generate a single matrix sample to x +function Base.rand(rng::AbstractRNG, s::Spl) + # ... generate a single matrix sample from s end -``` -Note that you can assume `x` has correct dimensions in `_rand!` and don't have to perform dimension checking, the generic `rand` and `rand!` will do dimension checking and array allocation for you. +@inline function Random.rand!(rng::AbstractRNG, s::Spl, x::AbstractMatrix{<:Real}) + # `@inline` + `@boundscheck` allows users to skip bound checks by calling `@inbounds rand!(...)` + # Ref https://docs.julialang.org/en/v1/devdocs/boundscheck/#Eliding-bounds-checks + @boundscheck # ... check size (and possibly indices) of `x` + # ... generate a single matrix sample from s in x +end +``` ## Create a Distribution @@ -106,7 +96,7 @@ A univariate distribution type should be defined as a subtype of `DiscreteUnivar The following methods need to be implemented for each univariate distribution type: -- [`rand(::AbstractRNG, d::UnivariateDistribution)`](@ref) +- [`Base.rand(::AbstractRNG, d::UnivariateDistribution)`](@ref) - [`sampler(d::Distribution)`](@ref) - [`logpdf(d::UnivariateDistribution, x::Real)`](@ref) - [`cdf(d::UnivariateDistribution, x::Real)`](@ref) @@ -138,8 +128,8 @@ The following methods need to be implemented for each multivariate distribution - [`length(d::MultivariateDistribution)`](@ref) - [`sampler(d::Distribution)`](@ref) -- [`eltype(d::Distribution)`](@ref) -- [`Distributions._rand!(::AbstractRNG, d::MultivariateDistribution, x::AbstractArray)`](@ref) +- [`Base.rand(::AbstractRNG, d::MultivariateDistribution)`](@ref) +- [`Random.rand!(::AbstractRNG, d::MultivariateDistribution, x::AbstractVector{<:Real})`](@ref) - [`Distributions._logpdf(d::MultivariateDistribution, x::AbstractArray)`](@ref) Note that if there exist faster methods for batch evaluation, one should override `_logpdf!` and `_pdf!`. @@ -161,6 +151,7 @@ A matrix-variate distribution type should be defined as a subtype of `DiscreteMa The following methods need to be implemented for each matrix-variate distribution type: - [`size(d::MatrixDistribution)`](@ref) -- [`Distributions._rand!(rng::AbstractRNG, d::MatrixDistribution, A::AbstractMatrix)`](@ref) +- [`Base.rand(rng::AbstractRNG, d::MatrixDistribution)`](@ref) +- [`Random.rand!(rng::AbstractRNG, d::MatrixDistribution, A::AbstractMatrix{<:Real})`](@ref) - [`sampler(d::MatrixDistribution)`](@ref) - [`Distributions._logpdf(d::MatrixDistribution, x::AbstractArray)`](@ref) diff --git a/docs/src/multivariate.md b/docs/src/multivariate.md index c4e7c1764..ab4b2cdbb 100644 --- a/docs/src/multivariate.md +++ b/docs/src/multivariate.md @@ -18,7 +18,6 @@ The methods listed below are implemented for each multivariate distribution, whi ```@docs length(::MultivariateDistribution) size(::MultivariateDistribution) -eltype(::Type{MultivariateDistribution}) mean(::MultivariateDistribution) var(::MultivariateDistribution) cov(::MultivariateDistribution) diff --git a/docs/src/types.md b/docs/src/types.md index f0ed23836..eb77d9c47 100644 --- a/docs/src/types.md +++ b/docs/src/types.md @@ -57,7 +57,6 @@ The basic functionalities that a sampleable object provides are to *retrieve inf length(::Sampleable) size(::Sampleable) nsamples(::Type{Sampleable}, ::Any) -eltype(::Type{Sampleable}) rand(::AbstractRNG, ::Sampleable) rand!(::AbstractRNG, ::Sampleable, ::AbstractArray) ``` diff --git a/src/censored.jl b/src/censored.jl index 5b8cfac5e..4c1451445 100644 --- a/src/censored.jl +++ b/src/censored.jl @@ -112,8 +112,6 @@ function partype(d::Censored{<:UnivariateDistribution,<:ValueSupport,T}) where { return promote_type(partype(d.uncensored), T) end -Base.eltype(::Type{<:Censored{D,S,T}}) where {D,S,T} = promote_type(T, eltype(D)) - #### Range and Support isupperbounded(d::LeftCensored) = isupperbounded(d.uncensored) diff --git a/src/cholesky/lkjcholesky.jl b/src/cholesky/lkjcholesky.jl index 7556620f6..33d9546fa 100644 --- a/src/cholesky/lkjcholesky.jl +++ b/src/cholesky/lkjcholesky.jl @@ -82,8 +82,6 @@ end # Properties # ----------------------------------------------------------------------------- -Base.eltype(::Type{LKJCholesky{T}}) where {T} = T - function Base.size(d::LKJCholesky) p = d.d return (p, p) diff --git a/src/common.jl b/src/common.jl index 31a218e19..802c60918 100644 --- a/src/common.jl +++ b/src/common.jl @@ -94,14 +94,35 @@ Base.size(s::Sampleable{Univariate}) = () Base.size(s::Sampleable{Multivariate}) = (length(s),) """ - eltype(::Type{Sampleable}) - -The default element type of a sample. This is the type of elements of the samples generated -by the `rand` method. However, one can provide an array of different element types to -store the samples using `rand!`. -""" -Base.eltype(::Type{<:Sampleable{F,Discrete}}) where {F} = Int -Base.eltype(::Type{<:Sampleable{F,Continuous}}) where {F} = Float64 + eltype(::Type{S}) where {S<:Distributions.Sampleable} + +The default element type of a sample from a sampler of type `S`. + +This is the type of elements of the samples generated by the `rand` method. +However, one can provide an array of different element types to store the samples using `rand!`. + +!!! warn + This method is deprecated and will be removed in an upcoming breaking release. + +""" +function Base.eltype(::Type{S}) where {VF<:VariateForm,VS<:Union{Discrete,Continuous},S<:Sampleable{VF,VS}} + Base.depwarn("`eltype(::Type{<:Distributions.Sampleable})` is deprecated and will be removed", :eltype) + T = Base.promote_op(rand, S) + if T === Union{} + # `T` can be `Union{}` if + # - `rand(::S)` is not defined and/or + # - `rand(::S)` calls `eltype(::S)` or `eltype(::Type{S})` but they are not specialized and fall back to the generic definition here + # (the latter case happens e.g. for non-univariate samplers that only implement `_rand!` and rely on the generic fallback for `rand`) + # In all these cases we return 1) `Int` for `Discrete` samplers and 2) `Float64` for `Continuous` samplers + if VS === Discrete + return Int + elseif VS === Continuous + return Float64 + end + else + return eltype(T) + end +end """ nsamples(s::Sampleable) diff --git a/src/genericrand.jl b/src/genericrand.jl index 6b3b213f1..7ec10f12f 100644 --- a/src/genericrand.jl +++ b/src/genericrand.jl @@ -30,35 +30,23 @@ function rand(rng::AbstractRNG, s::Sampleable{<:ArrayLikeVariate}) end # multiple samples -function rand(rng::AbstractRNG, s::Sampleable{Univariate}, dims::Dims) - out = Array{eltype(s)}(undef, dims) - return @inbounds rand!(rng, sampler(s), out) +# we use function barriers since for some distributions `sampler(s)` is not type-stable: +# https://github.com/JuliaStats/Distributions.jl/pull/1281 +function rand(rng::AbstractRNG, s::Sampleable{<:ArrayLikeVariate}, dims::Dims) + return _rand(rng, sampler(s), dims) end -function rand( - rng::AbstractRNG, s::Sampleable{<:ArrayLikeVariate}, dims::Dims, -) - sz = size(s) - ax = map(Base.OneTo, dims) - out = [Array{eltype(s)}(undef, sz) for _ in Iterators.product(ax...)] - return @inbounds rand!(rng, sampler(s), out, false) +function _rand(rng::AbstractRNG, s::Sampleable{<:ArrayLikeVariate}, dims::Dims) + r = rand(rng, s) + out = Array{typeof(r)}(undef, dims) + out[1] = r + rand!(rng, s, @view(out[2:end])) + return out end -# these are workarounds for sampleables that incorrectly base `eltype` on the parameters +# this is a workaround for sampleables that incorrectly base `eltype` on the parameters function rand(rng::AbstractRNG, s::Sampleable{<:ArrayLikeVariate,Continuous}) return @inbounds rand!(rng, sampler(s), Array{float(eltype(s))}(undef, size(s))) end -function rand(rng::AbstractRNG, s::Sampleable{Univariate,Continuous}, dims::Dims) - out = Array{float(eltype(s))}(undef, dims) - return @inbounds rand!(rng, sampler(s), out) -end -function rand( - rng::AbstractRNG, s::Sampleable{<:ArrayLikeVariate,Continuous}, dims::Dims, -) - sz = size(s) - ax = map(Base.OneTo, dims) - out = [Array{float(eltype(s))}(undef, sz) for _ in Iterators.product(ax...)] - return @inbounds rand!(rng, sampler(s), out, false) -end """ rand!([rng::AbstractRNG,] s::Sampleable, A::AbstractArray) diff --git a/src/multivariate/dirichlet.jl b/src/multivariate/dirichlet.jl index b24980ec9..b5b8e0187 100644 --- a/src/multivariate/dirichlet.jl +++ b/src/multivariate/dirichlet.jl @@ -50,8 +50,6 @@ end length(d::DirichletCanon) = length(d.alpha) -Base.eltype(::Type{<:Dirichlet{T}}) where {T} = T - #### Conversions convert(::Type{Dirichlet{T}}, cf::DirichletCanon) where {T<:Real} = Dirichlet(convert(AbstractVector{T}, cf.alpha)) @@ -154,6 +152,15 @@ end # sampling +function rand(rng::AbstractRNG, d::Union{Dirichlet,DirichletCanon}) + x = map(αi -> rand(rng, Gamma(αi)), d.alpha) + return lmul!(inv(sum(x)), x) +end +function rand(rng::AbstractRNG, d::Dirichlet{<:Real,<:FillArrays.AbstractFill{<:Real}}) + x = rand(rng, Gamma(FillArrays.getindex_value(d.alpha)), length(d)) + return lmul!(inv(sum(x)), x) +end + function _rand!(rng::AbstractRNG, d::Union{Dirichlet,DirichletCanon}, x::AbstractVector{<:Real}) diff --git a/src/multivariate/dirichletmultinomial.jl b/src/multivariate/dirichletmultinomial.jl index eb15990cb..b8eb51991 100644 --- a/src/multivariate/dirichletmultinomial.jl +++ b/src/multivariate/dirichletmultinomial.jl @@ -97,6 +97,8 @@ end # Sampling +rand(rng::AbstractRNG, d::DirichletMultinomial) = + multinom_rand(rng, ntrials(d), rand(rng, Dirichlet(d.α))) _rand!(rng::AbstractRNG, d::DirichletMultinomial, x::AbstractVector{<:Real}) = multinom_rand!(rng, ntrials(d), rand(rng, Dirichlet(d.α)), x) diff --git a/src/multivariate/jointorderstatistics.jl b/src/multivariate/jointorderstatistics.jl index 1fbed0d1b..16da76576 100644 --- a/src/multivariate/jointorderstatistics.jl +++ b/src/multivariate/jointorderstatistics.jl @@ -88,8 +88,6 @@ maximum(d::JointOrderStatistics) = Fill(maximum(d.dist), length(d)) params(d::JointOrderStatistics) = tuple(params(d.dist)..., d.n, d.ranks) partype(d::JointOrderStatistics) = partype(d.dist) -Base.eltype(::Type{<:JointOrderStatistics{D}}) where {D} = Base.eltype(D) -Base.eltype(d::JointOrderStatistics) = eltype(d.dist) function logpdf(d::JointOrderStatistics, x::AbstractVector{<:Real}) n = d.n @@ -125,6 +123,27 @@ function _marginalize_range(dist, i, j, xᵢ, xⱼ, T) return k * T(logdiffcdf(dist, xⱼ, xᵢ)) - loggamma(T(k + 1)) end +function rand(rng::AbstractRNG, d::JointOrderStatistics) + n = d.n + if n == length(d.ranks) # ranks == 1:n + # direct method, slower than inversion method for large `n` and distributions with + # fast quantile function or that use inversion sampling + x = rand(rng, d.dist, n) + sort!(x) + else + # use exponential generation method with inversion, where for gaps in the ranks, we + # use the fact that the sum Y of k IID variables xₘ ~ Exp(1) is Y ~ Gamma(k, 1). + # Lurie, D., and H. O. Hartley. "Machine-generation of order statistics for Monte + # Carlo computations." The American Statistician 26.1 (1972): 26-27. + # this is slow if length(d.ranks) is close to n and quantile for d.dist is expensive, + # but this branch is probably taken when length(d.ranks) is small or much smaller than n. + xi = rand(rng, d.dist) # this is only used to obtain the type of samples from `d.dist` + x = Vector{typeof(xi)}(undef, length(d.ranks)) + _rand!(rng, d, x) + end + return x +end + function _rand!(rng::AbstractRNG, d::JointOrderStatistics, x::AbstractVector{<:Real}) n = d.n if n == length(d.ranks) # ranks == 1:n diff --git a/src/multivariate/multinomial.jl b/src/multivariate/multinomial.jl index c4db44b85..76d4776e0 100644 --- a/src/multivariate/multinomial.jl +++ b/src/multivariate/multinomial.jl @@ -165,6 +165,7 @@ end # Sampling # if only a single sample is requested, no alias table is created +rand(rng::AbstractRNG, d::Multinomial) = multinom_rand(rng, ntrials(d), probs(d)) _rand!(rng::AbstractRNG, d::Multinomial, x::AbstractVector{<:Real}) = multinom_rand!(rng, ntrials(d), probs(d), x) diff --git a/src/multivariate/mvlogitnormal.jl b/src/multivariate/mvlogitnormal.jl index 0d60ddf65..26af12730 100644 --- a/src/multivariate/mvlogitnormal.jl +++ b/src/multivariate/mvlogitnormal.jl @@ -52,8 +52,6 @@ canonform(d::MvLogitNormal{<:MvNormal}) = MvLogitNormal(canonform(d.normal)) # Properties length(d::MvLogitNormal) = length(d.normal) + 1 -Base.eltype(::Type{<:MvLogitNormal{D}}) where {D} = eltype(D) -Base.eltype(d::MvLogitNormal) = eltype(d.normal) params(d::MvLogitNormal) = params(d.normal) @inline partype(d::MvLogitNormal) = partype(d.normal) @@ -88,6 +86,19 @@ kldivergence(p::MvLogitNormal, q::MvLogitNormal) = kldivergence(p.normal, q.norm # Sampling +function rand(rng::AbstractRNG, d::MvLogitNormal) + x = rand(rng, d.normal) + push!(x, zero(eltype(x))) + StatsFuns.softmax!(x) + return x +end +function rand(rng::AbstractRNG, d::MvLogitNormal, n::Int) + r = rand(rng, d.normal, n) + x = vcat(r, zeros(eltype(r), 1, n)) + StatsFuns.softmax!(x; dims=1) + return x +end + function _rand!(rng::AbstractRNG, d::MvLogitNormal, x::AbstractVecOrMat{<:Real}) y = @views _drop1(x) rand!(rng, d.normal, y) diff --git a/src/multivariate/mvlognormal.jl b/src/multivariate/mvlognormal.jl index 1eecd38c2..738a93dff 100644 --- a/src/multivariate/mvlognormal.jl +++ b/src/multivariate/mvlognormal.jl @@ -176,8 +176,6 @@ MvLogNormal(μ::AbstractVector,s::Real) = MvLogNormal(MvNormal(μ,s)) MvLogNormal(σ::AbstractVector) = MvLogNormal(MvNormal(σ)) MvLogNormal(d::Int,s::Real) = MvLogNormal(MvNormal(d,s)) -Base.eltype(::Type{<:MvLogNormal{T}}) where {T} = T - ### Conversion function convert(::Type{MvLogNormal{T}}, d::MvLogNormal) where T<:Real MvLogNormal(convert(MvNormal{T}, d.normal)) @@ -232,6 +230,17 @@ var(d::MvLogNormal) = diag(cov(d)) entropy(d::MvLogNormal) = length(d)*(1+log2π)/2 + logdetcov(d.normal)/2 + sum(mean(d.normal)) #See https://en.wikipedia.org/wiki/Log-normal_distribution +function rand(rng::AbstractRNG, d::MvLogNormal) + x = rand(rng, d.normal) + map!(exp, x, x) + return x +end +function rand(rng::AbstractRNG, d::MvLogNormal, n::Int) + xs = rand(rng, d.normal, n) + map!(exp, xs, xs) + return xs +end + function _rand!(rng::AbstractRNG, d::MvLogNormal, x::AbstractVecOrMat{<:Real}) _rand!(rng, d.normal, x) map!(exp, x, x) diff --git a/src/multivariate/mvnormal.jl b/src/multivariate/mvnormal.jl index 6126c1d8f..6abdd2643 100644 --- a/src/multivariate/mvnormal.jl +++ b/src/multivariate/mvnormal.jl @@ -223,8 +223,6 @@ Base.@deprecate MvNormal(μ::AbstractVector{<:Real}, σ::Real) MvNormal(μ, σ^2 Base.@deprecate MvNormal(σ::AbstractVector{<:Real}) MvNormal(LinearAlgebra.Diagonal(map(abs2, σ))) Base.@deprecate MvNormal(d::Int, σ::Real) MvNormal(LinearAlgebra.Diagonal(FillArrays.Fill(σ^2, d))) -Base.eltype(::Type{<:MvNormal{T}}) where {T} = T - ### Conversion function convert(::Type{MvNormal{T}}, d::MvNormal) where T<:Real MvNormal(convert(AbstractArray{T}, d.μ), convert(AbstractArray{T}, d.Σ)) @@ -273,6 +271,17 @@ gradlogpdf(d::MvNormal, x::AbstractVector{<:Real}) = -(d.Σ \ (x .- d.μ)) # Sampling (for GenericMvNormal) +function rand(rng::AbstractRNG, d::MvNormal) + x = unwhiten!(d.Σ, randn(rng, float(partype(d)), length(d))) + x .+= d.μ + return x +end +function rand(rng::AbstractRNG, d::MvNormal, n::Int) + x = unwhiten!(d.Σ, randn(rng, float(partype(d)), length(d), n)) + x .+= d.μ + return x +end + function _rand!(rng::AbstractRNG, d::MvNormal, x::VecOrMat) unwhiten!(d.Σ, randn!(rng, x)) x .+= d.μ diff --git a/src/multivariate/mvnormalcanon.jl b/src/multivariate/mvnormalcanon.jl index 587b20ba5..6217f2d07 100644 --- a/src/multivariate/mvnormalcanon.jl +++ b/src/multivariate/mvnormalcanon.jl @@ -154,7 +154,6 @@ length(d::MvNormalCanon) = length(d.μ) mean(d::MvNormalCanon) = convert(Vector{eltype(d.μ)}, d.μ) params(d::MvNormalCanon) = (d.μ, d.h, d.J) @inline partype(d::MvNormalCanon{T}) where {T<:Real} = T -Base.eltype(::Type{<:MvNormalCanon{T}}) where {T} = T var(d::MvNormalCanon) = diag(inv(d.J)) cov(d::MvNormalCanon) = Matrix(inv(d.J)) @@ -177,6 +176,17 @@ if isdefined(PDMats, :PDSparseMat) unwhiten_winv!(J::PDSparseMat, x::AbstractVecOrMat) = x[:] = J.chol.PtL' \ x end +function rand(rng::AbstractRNG, d::MvNormalCanon) + x = unwhiten_winv!(d.J, randn(rng, float(partype(d)), length(d))) + x .+= d.μ + return x +end +function rand(rng::AbstractRNG, d::MvNormalCanon, n::Int) + x = unwhiten_winv!(d.J, randn(rng, float(partype(d)), length(d), n)) + x .+= d.μ + return x +end + function _rand!(rng::AbstractRNG, d::MvNormalCanon, x::AbstractVector) unwhiten_winv!(d.J, randn!(rng, x)) x .+= d.μ diff --git a/src/multivariate/mvtdist.jl b/src/multivariate/mvtdist.jl index 9076c364b..b8ed80c03 100644 --- a/src/multivariate/mvtdist.jl +++ b/src/multivariate/mvtdist.jl @@ -101,7 +101,6 @@ logdet_cov(d::GenericMvTDist) = d.df>2 ? logdet((d.df/(d.df-2))*d.Σ) : NaN params(d::GenericMvTDist) = (d.df, d.μ, d.Σ) @inline partype(d::GenericMvTDist{T}) where {T} = T -Base.eltype(::Type{<:GenericMvTDist{T}}) where {T} = T # For entropy calculations see "Multivariate t Distributions and their Applications", S. Kotz & S. Nadarajah function entropy(d::GenericMvTDist) @@ -155,6 +154,21 @@ function gradlogpdf(d::GenericMvTDist, x::AbstractVector{<:Real}) end # Sampling (for GenericMvTDist) +function rand(rng::AbstractRNG, d::GenericMvTDist) + chisqd = Chisq{partype(d)}(d.df) + y = sqrt(rand(rng, chisqd) / d.df) + x = unwhiten!(d.Σ, randn(rng, typeof(y), length(d))) + x .= x ./ y .+ d.μ + x +end +function rand(rng::AbstractRNG, d::GenericMvTDist, n::Int) + chisqd = Chisq{partype(d)}(d.df) + y = rand(rng, chisqd, n) + x = unwhiten!(d.Σ, randn(rng, eltype(y), length(d), n)) + x .= x ./ sqrt.(y' ./ d.df) .+ d.μ + x +end + function _rand!(rng::AbstractRNG, d::GenericMvTDist, x::AbstractVector{<:Real}) chisqd = Chisq{partype(d)}(d.df) y = sqrt(rand(rng, chisqd) / d.df) diff --git a/src/multivariate/product.jl b/src/multivariate/product.jl index 3bb0f0d9e..439c6ccb7 100644 --- a/src/multivariate/product.jl +++ b/src/multivariate/product.jl @@ -31,10 +31,6 @@ function Product(v::V) where {S<:ValueSupport,T<:UnivariateDistribution{S},V<:Ab end length(d::Product) = length(d.v) -function Base.eltype(::Type{<:Product{S,T}}) where {S<:ValueSupport, - T<:UnivariateDistribution{S}} - return eltype(T) -end _rand!(rng::AbstractRNG, d::Product, x::AbstractVector{<:Real}) = map!(Base.Fix1(rand, rng), x, d.v) diff --git a/src/multivariates.jl b/src/multivariates.jl index 56d91233c..6d6a45678 100644 --- a/src/multivariates.jl +++ b/src/multivariates.jl @@ -18,10 +18,20 @@ size(d::MultivariateDistribution) # multiple multivariate, must allocate matrix # TODO: inconsistency with other `ArrayLikeVariate`s and `rand(s, (n,))` - maybe remove? -rand(rng::AbstractRNG, s::Sampleable{Multivariate}, n::Int) = - @inbounds rand!(rng, sampler(s), Matrix{eltype(s)}(undef, length(s), n)) -rand(rng::AbstractRNG, s::Sampleable{Multivariate,Continuous}, n::Int) = - @inbounds rand!(rng, sampler(s), Matrix{float(eltype(s))}(undef, length(s), n)) +function rand(rng::AbstractRNG, s::Sampleable{Multivariate}, n::Int) + return _rand(rng, sampler(s), n) +end +function _rand(rng, s::Sampleable{Multivariate}, n::Int) + r = rand(rng, s) + out = Matrix{eltype(r)}(undef, length(r), n) + if n > 0 + copyto!(out, r) + if n > 1 + rand!(rng, s, @view(out[:, 2:n])) + end + end + return out +end ## domain diff --git a/src/product.jl b/src/product.jl index d6f7aaa6b..40638010b 100644 --- a/src/product.jl +++ b/src/product.jl @@ -70,10 +70,6 @@ const ArrayOfUnivariateDistribution{N,D,S<:ValueSupport,T} = ProductDistribution const FillArrayOfUnivariateDistribution{N,D<:Fill{<:Any,N},S<:ValueSupport,T} = ProductDistribution{N,0,D,S,T} ## General definitions -function Base.eltype(::Type{<:ProductDistribution{<:Any,<:Any,<:Any,<:ValueSupport,T}}) where {T} - return T -end - size(d::ProductDistribution) = d.size mean(d::ProductDistribution) = reshape(mapreduce(vec ∘ mean, vcat, d.dists), size(d)) diff --git a/src/reshaped.jl b/src/reshaped.jl index 23f35fb46..69431b6dd 100644 --- a/src/reshaped.jl +++ b/src/reshaped.jl @@ -24,7 +24,6 @@ function _reshape_check_dims(dist::Distribution{<:ArrayLikeVariate}, dims::Dims) end Base.size(d::ReshapedDistribution) = d.dims -Base.eltype(::Type{ReshapedDistribution{<:Any,<:ValueSupport,D}}) where {D} = eltype(D) partype(d::ReshapedDistribution) = partype(d.dist) params(d::ReshapedDistribution) = (d.dist, d.dims) diff --git a/src/samplers/multinomial.jl b/src/samplers/multinomial.jl index 7ce8b8973..c09c92ace 100644 --- a/src/samplers/multinomial.jl +++ b/src/samplers/multinomial.jl @@ -1,3 +1,6 @@ +function multinom_rand(rng::AbstractRNG, n::Int, p::AbstractVector{<:Real}) + return multinom_rand!(rng, n, p, Vector{Int}(undef, length(p))) +end function multinom_rand!(rng::AbstractRNG, n::Int, p::AbstractVector{<:Real}, x::AbstractVector{<:Real}) k = length(p) @@ -49,6 +52,9 @@ function MultinomialSampler(n::Int, prob::Vector{<:Real}) return MultinomialSampler(n, prob, AliasTable(prob)) end +function rand(rng::AbstractRNG, s::MultinomialSampler) + return _rand!(rng, s, Vector{Int}(undef, length(s.prob))) +end function _rand!(rng::AbstractRNG, s::MultinomialSampler, x::AbstractVector{<:Real}) n = s.n diff --git a/src/truncate.jl b/src/truncate.jl index 48d62b015..18c8716f9 100644 --- a/src/truncate.jl +++ b/src/truncate.jl @@ -126,9 +126,6 @@ end params(d::Truncated) = tuple(params(d.untruncated)..., d.lower, d.upper) partype(d::Truncated{<:UnivariateDistribution,<:ValueSupport,T}) where {T<:Real} = promote_type(partype(d.untruncated), T) -Base.eltype(::Type{<:Truncated{D}}) where {D<:UnivariateDistribution} = eltype(D) -Base.eltype(d::Truncated) = eltype(d.untruncated) - ### range and support islowerbounded(d::RightTruncated) = islowerbounded(d.untruncated) diff --git a/src/univariate/continuous/beta.jl b/src/univariate/continuous/beta.jl index fd2420d81..d1a84613e 100644 --- a/src/univariate/continuous/beta.jl +++ b/src/univariate/continuous/beta.jl @@ -138,15 +138,16 @@ struct BetaSampler{T<:Real, S1 <: Sampleable{Univariate,Continuous}, s2::S2 end -function sampler(d::Beta{T}) where T - (α, β) = params(d) - if (α ≤ 1.0) && (β ≤ 1.0) +function sampler(d::Beta) + α, β = params(d) + if α ≤ 1 && β ≤ 1 return BetaSampler(false, inv(α), inv(β), - sampler(Uniform()), sampler(Uniform())) + sampler(Uniform(zero(α), oneunit(α))), + sampler(Uniform(zero(β), oneunit(β)))) else return BetaSampler(true, inv(α), inv(β), - sampler(Gamma(α, one(T))), - sampler(Gamma(β, one(T)))) + sampler(Gamma(α, oneunit(α))), + sampler(Gamma(β, oneunit(β)))) end end @@ -160,11 +161,11 @@ function rand(rng::AbstractRNG, s::BetaSampler) iα = s.iα iβ = s.iβ while true - u = rand(rng) # the Uniform sampler just calls rand() - v = rand(rng) + u = rand(rng, s.s1) # the Uniform sampler just calls rand() + v = rand(rng, s.s2) x = u^iα y = v^iβ - if x + y ≤ one(x) + if x + y ≤ 1 if (x + y > 0) return x / (x + y) else @@ -180,16 +181,20 @@ function rand(rng::AbstractRNG, s::BetaSampler) end end -function rand(rng::AbstractRNG, d::Beta{T}) where T - (α, β) = params(d) - if (α ≤ 1.0) && (β ≤ 1.0) +function rand(rng::AbstractRNG, d::Beta) + α, β = params(d) + if α ≤ 1 && β ≤ 1 + iα = inv(α) + iβ = inv(β) + Tu = typeof(float(iα)) + Tv = typeof(float(iβ)) while true - u = rand(rng) - v = rand(rng) - x = u^inv(α) - y = v^inv(β) - if x + y ≤ one(x) - if (x + y > 0) + u = rand(rng, Tu) + v = rand(rng, Tv) + x = u^iα + y = v^iβ + if x + y ≤ 1 + if x + y > 0 return x / (x + y) else logX = log(u) / α @@ -202,8 +207,8 @@ function rand(rng::AbstractRNG, d::Beta{T}) where T end end else - g1 = rand(rng, Gamma(α, one(T))) - g2 = rand(rng, Gamma(β, one(T))) + g1 = rand(rng, Gamma(α, oneunit(α))) + g2 = rand(rng, Gamma(β, oneunit(β))) return g1 / (g1 + g2) end end diff --git a/src/univariate/continuous/gumbel.jl b/src/univariate/continuous/gumbel.jl index 54090967f..f9f5bc5d7 100644 --- a/src/univariate/continuous/gumbel.jl +++ b/src/univariate/continuous/gumbel.jl @@ -41,8 +41,6 @@ Gumbel(μ::Real=0.0) = Gumbel(μ, one(μ); check_args=false) const DoubleExponential = Gumbel -Base.eltype(::Type{Gumbel{T}}) where {T} = T - #### Conversions convert(::Type{Gumbel{T}}, μ::S, θ::S) where {T <: Real, S <: Real} = Gumbel(T(μ), T(θ)) @@ -56,8 +54,8 @@ scale(d::Gumbel) = d.θ params(d::Gumbel) = (d.μ, d.θ) partype(::Gumbel{T}) where {T} = T -function Base.rand(rng::Random.AbstractRNG, d::Gumbel) - return d.μ - d.θ * log(randexp(rng, float(eltype(d)))) +function Base.rand(rng::Random.AbstractRNG, d::Gumbel{T}) where {T} + return d.μ - d.θ * log(randexp(rng, float(T))) end function _rand!(rng::Random.AbstractRNG, d::Gumbel, x::AbstractArray{<:Real}) randexp!(rng, x) diff --git a/src/univariate/continuous/normal.jl b/src/univariate/continuous/normal.jl index ef2b77bb5..bc67ea210 100644 --- a/src/univariate/continuous/normal.jl +++ b/src/univariate/continuous/normal.jl @@ -60,8 +60,6 @@ params(d::Normal) = (d.μ, d.σ) location(d::Normal) = d.μ scale(d::Normal) = d.σ -Base.eltype(::Type{Normal{T}}) where {T} = T - #### Statistics mean(d::Normal) = d.μ diff --git a/src/univariate/continuous/uniform.jl b/src/univariate/continuous/uniform.jl index d4beca7d7..3fd4c507d 100644 --- a/src/univariate/continuous/uniform.jl +++ b/src/univariate/continuous/uniform.jl @@ -151,7 +151,7 @@ Base.:*(c::Real, d::Uniform) = Uniform(minmax(c * d.a, c * d.b)...) #### Sampling -rand(rng::AbstractRNG, d::Uniform) = d.a + (d.b - d.a) * rand(rng) +rand(rng::AbstractRNG, d::Uniform{T}) where {T} = d.a + (d.b - d.a) * rand(rng, float(T)) _rand!(rng::AbstractRNG, d::Uniform, A::AbstractArray{<:Real}) = A .= Base.Fix1(quantile, d).(rand!(rng, A)) diff --git a/src/univariate/discrete/bernoulli.jl b/src/univariate/discrete/bernoulli.jl index c1dee968c..fbfdab97d 100644 --- a/src/univariate/discrete/bernoulli.jl +++ b/src/univariate/discrete/bernoulli.jl @@ -40,8 +40,6 @@ Bernoulli() = Bernoulli{Float64}(0.5) @distr_support Bernoulli false true -Base.eltype(::Type{<:Bernoulli}) = Bool - #### Conversions convert(::Type{Bernoulli{T}}, p::Real) where {T<:Real} = Bernoulli(T(p)) Base.convert(::Type{Bernoulli{T}}, d::Bernoulli) where {T<:Real} = Bernoulli{T}(T(d.p)) diff --git a/src/univariate/discrete/bernoullilogit.jl b/src/univariate/discrete/bernoullilogit.jl index f82059fed..ca5233ea4 100644 --- a/src/univariate/discrete/bernoullilogit.jl +++ b/src/univariate/discrete/bernoullilogit.jl @@ -24,8 +24,6 @@ BernoulliLogit() = BernoulliLogit(0.0) @distr_support BernoulliLogit false true -Base.eltype(::Type{<:BernoulliLogit}) = Bool - #### Conversions Base.convert(::Type{BernoulliLogit{T}}, d::BernoulliLogit) where {T<:Real} = BernoulliLogit{T}(T(d.logitp)) Base.convert(::Type{BernoulliLogit{T}}, d::BernoulliLogit{T}) where {T<:Real} = d diff --git a/src/univariate/discrete/dirac.jl b/src/univariate/discrete/dirac.jl index 94d082b0f..01b49bfb1 100644 --- a/src/univariate/discrete/dirac.jl +++ b/src/univariate/discrete/dirac.jl @@ -22,8 +22,6 @@ struct Dirac{T} <: DiscreteUnivariateDistribution value::T end -Base.eltype(::Type{Dirac{T}}) where {T} = T - insupport(d::Dirac, x::Real) = x == d.value minimum(d::Dirac) = d.value maximum(d::Dirac) = d.value diff --git a/src/univariate/discrete/discretenonparametric.jl b/src/univariate/discrete/discretenonparametric.jl index 8e1eefab6..1c3e04718 100644 --- a/src/univariate/discrete/discretenonparametric.jl +++ b/src/univariate/discrete/discretenonparametric.jl @@ -39,8 +39,6 @@ DiscreteNonParametric(vs::AbstractVector{T}, ps::AbstractVector{P}; check_args:: T<:Real,P<:Real} = DiscreteNonParametric{T,P,typeof(vs),typeof(ps)}(vs, ps; check_args=check_args) -Base.eltype(::Type{<:DiscreteNonParametric{T}}) where T = T - # Conversion convert(::Type{DiscreteNonParametric{T,P,Ts,Ps}}, d::DiscreteNonParametric) where {T,P,Ts,Ps} = DiscreteNonParametric{T,P,Ts,Ps}(convert(Ts, support(d)), convert(Ps, probs(d)), check_args=false) diff --git a/src/univariate/locationscale.jl b/src/univariate/locationscale.jl index 97e47d6f7..7e4eda771 100644 --- a/src/univariate/locationscale.jl +++ b/src/univariate/locationscale.jl @@ -71,8 +71,6 @@ end const ContinuousAffineDistribution{T<:Real,D<:ContinuousUnivariateDistribution} = AffineDistribution{T,Continuous,D} const DiscreteAffineDistribution{T<:Real,D<:DiscreteUnivariateDistribution} = AffineDistribution{T,Discrete,D} -Base.eltype(::Type{<:AffineDistribution{T}}) where T = T - minimum(d::AffineDistribution) = d.σ > 0 ? d.μ + d.σ * minimum(d.ρ) : d.μ + d.σ * maximum(d.ρ) maximum(d::AffineDistribution) = diff --git a/src/univariate/orderstatistic.jl b/src/univariate/orderstatistic.jl index 1a7055ef9..4a9012fb5 100644 --- a/src/univariate/orderstatistic.jl +++ b/src/univariate/orderstatistic.jl @@ -58,8 +58,6 @@ insupport(d::OrderStatistic, x::Real) = insupport(d.dist, x) params(d::OrderStatistic) = tuple(params(d.dist)..., d.n, d.rank) partype(d::OrderStatistic) = partype(d.dist) -Base.eltype(::Type{<:OrderStatistic{D}}) where {D} = Base.eltype(D) -Base.eltype(d::OrderStatistic) = eltype(d.dist) # distribution of the ith order statistic from an IID uniform distribution, with CDF Uᵢₙ(x) function _uniform_orderstatistic(d::OrderStatistic) @@ -102,7 +100,6 @@ end function rand(rng::AbstractRNG, d::OrderStatistic) # inverse transform sampling. Since quantile function is Qₓ(Uᵢₙ⁻¹(p)), we draw a random # variable from Uᵢₙ and pass it through the quantile function of `d.dist` - T = eltype(d.dist) b = _uniform_orderstatistic(d) - return T(quantile(d.dist, rand(rng, b))) + return quantile(d.dist, float(partype(d.dist))(rand(rng, b))) end diff --git a/src/univariates.jl b/src/univariates.jl index b60e5a294..68daebb0c 100644 --- a/src/univariates.jl +++ b/src/univariates.jl @@ -154,7 +154,9 @@ end Generate a scalar sample from `d`. The general fallback is `quantile(d, rand())`. """ -rand(rng::AbstractRNG, d::UnivariateDistribution) = quantile(d, rand(rng)) +function rand(rng::AbstractRNG, d::UnivariateDistribution) + return quantile(d, rand(rng, float(partype(d)))) +end ## statistics diff --git a/test/multivariate/dirichlet.jl b/test/multivariate/dirichlet.jl index 78de162dc..99ee4893a 100644 --- a/test/multivariate/dirichlet.jl +++ b/test/multivariate/dirichlet.jl @@ -18,7 +18,7 @@ rng = MersenneTwister(123) d = Dirichlet(3, T(2)) @test length(d) == 3 - @test eltype(d) === T + @test eltype(d) === float(T) @test d.alpha == [2, 2, 2] @test d.alpha0 == 6 @@ -53,7 +53,7 @@ rng = MersenneTwister(123) v = [2, 1, 3] d = Dirichlet(T.(v)) - @test eltype(d) === T + @test eltype(d) === float(T) @test Dirichlet([2, 1, 3]).alpha == d.alpha @test length(d) == length(v) diff --git a/test/runtests.jl b/test/runtests.jl index cd3afe17f..6bb4ffa1e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -72,6 +72,7 @@ const tests = [ "univariate/discrete/poisson", "univariate/discrete/soliton", "univariate/continuous/skewnormal", + "univariate/continuous/beta", "univariate/continuous/chi", "univariate/continuous/chisq", "univariate/continuous/erlang", @@ -129,8 +130,6 @@ const tests = [ # "samplers/vonmisesfisher", # "show", # "truncated/loguniform", - # "univariate/continuous/beta", - # "univariate/continuous/beta", # "univariate/continuous/betaprime", # "univariate/continuous/biweight", # "univariate/continuous/cosine", diff --git a/test/testutils.jl b/test/testutils.jl index 8acd37853..9af33584a 100644 --- a/test/testutils.jl +++ b/test/testutils.jl @@ -165,7 +165,12 @@ function test_samples(s::Sampleable{Univariate, Discrete}, # the sampleable samples3 = [rand(rng3, s) for _ in 1:n] samples4 = [rand(rng4, s) for _ in 1:n] end - @test length(samples) == n + T = typeof(rand(s)) + @test samples isa Vector{T} + @test samples2 isa Vector{T} + @test samples3 isa Vector{T} + @test samples4 isa Vector{T} + @test length(samples) == length(samples2) == length(samples3) == length(samples4) == n @test samples2 == samples @test samples3 == samples4 @@ -289,7 +294,12 @@ function test_samples(s::Sampleable{Univariate, Continuous}, # the sampleable samples3 = [rand(rng3, s) for _ in 1:n] samples4 = [rand(rng4, s) for _ in 1:n] end - @test length(samples) == n + T = typeof(rand(s)) + @test samples isa Vector{T} + @test samples2 isa Vector{T} + @test samples3 isa Vector{T} + @test samples4 isa Vector{T} + @test length(samples) == length(samples2) == length(samples3) == length(samples4) == n @test samples2 == samples @test samples3 == samples4 diff --git a/test/univariate/continuous/beta.jl b/test/univariate/continuous/beta.jl new file mode 100644 index 000000000..1010682a5 --- /dev/null +++ b/test/univariate/continuous/beta.jl @@ -0,0 +1,19 @@ +using Distributions +using Test + +@testset "beta.jl" begin + # issue #1907 + @testset "rand consistency" begin + for T in (Float32, Float64) + @test @inferred(rand(Beta(T(1), T(1)))) isa T + @test @inferred(rand(Beta(T(4//5), T(4//5)))) isa T + @test @inferred(rand(Beta(T(1), T(2)))) isa T + @test @inferred(rand(Beta(T(2), T(1)))) isa T + + @test @inferred(eltype(rand(Beta(T(1), T(1)), 2))) === T + @test @inferred(eltype(rand(Beta(T(4//5), T(4//5)), 2))) === T + @test @inferred(eltype(rand(Beta(T(1), T(2)), 2))) === T + @test @inferred(eltype(rand(Beta(T(2), T(1)), 2))) === T + end + end +end diff --git a/test/univariate/continuous/logistic.jl b/test/univariate/continuous/logistic.jl index 3eb0b8f2d..1cd0772fd 100644 --- a/test/univariate/continuous/logistic.jl +++ b/test/univariate/continuous/logistic.jl @@ -1,2 +1,15 @@ -test_cgf(Logistic(0, 1), (-0.99,0.99, 1f-2, -1f-2)) -test_cgf(Logistic(100,10), (-0.099,0.099, 1f-2, -1f-2)) +using Distributions +using Test + +@testset "Logistic" begin + test_cgf(Logistic(0, 1), (-0.99,0.99, 1f-2, -1f-2)) + test_cgf(Logistic(100,10), (-0.099,0.099, 1f-2, -1f-2)) + + # issue 1082 + @testset "rand consistency" begin + for T in (Float32, Float64, BigFloat) + @test @inferred(rand(Logistic(T(0), T(1)))) isa T + @test @inferred(rand(Logistic(T(0), T(1)), 5)) isa Vector{T} + end + end +end diff --git a/test/univariate/continuous/tdist.jl b/test/univariate/continuous/tdist.jl index 127b99243..652af9cbd 100644 --- a/test/univariate/continuous/tdist.jl +++ b/test/univariate/continuous/tdist.jl @@ -10,10 +10,10 @@ using Test @inferred(rand(TDist(big"1.0"))) end @inferred(rand(TDist(ForwardDiff.Dual(1.0)))) - end for T in (Float32, Float64) @test @inferred(rand(TDist(T(1)))) isa T + @test @inferred(rand(TDist(T(1)), 5)) isa Vector{T} end end diff --git a/test/univariate/continuous/uniform.jl b/test/univariate/continuous/uniform.jl index e3a5d729e..0d928d087 100644 --- a/test/univariate/continuous/uniform.jl +++ b/test/univariate/continuous/uniform.jl @@ -114,4 +114,12 @@ using Test end end end + + # issues 1252 and 1783 + @testset "rand consistency" begin + for T in (Float32, Float64, BigFloat) + @test @inferred(rand(Uniform(T(0), T(1)))) isa T + @test @inferred(rand(Uniform(T(0), T(1)), 5)) isa Vector{T} + end + end end