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
79 changes: 35 additions & 44 deletions docs/src/extends.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
```
Expand All @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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!`.
Expand All @@ -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)
3 changes: 2 additions & 1 deletion docs/src/matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ var(::MatrixDistribution)
cov(::MatrixDistribution)
pdf(d::MatrixDistribution, x::AbstractMatrix{<:Real})
logpdf(d::MatrixDistribution, x::AbstractMatrix{<:Real})
Distributions._rand!(::AbstractRNG, ::MatrixDistribution, A::AbstractMatrix)
Base.rand(::AbstractRNG, ::MatrixDistribution)
Random.rand!(::AbstractRNG, ::MatrixDistribution, A::AbstractMatrix{<:Real})
```

## Distributions
Expand Down
5 changes: 2 additions & 3 deletions docs/src/multivariate.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
std(::MultivariateDistribution)
Expand All @@ -42,8 +41,8 @@ loglikelihood(::MultivariateDistribution, ::AbstractVector{<:Real})
### Sampling

```@docs
rand(rng::AbstractRNG, ::MultivariateDistribution)
rand!(rng::AbstractRNG, d::MultivariateDistribution, x::AbstractArray)
Base.rand(rng::AbstractRNG, ::MultivariateDistribution)
Random.rand!(rng::AbstractRNG, d::MultivariateDistribution, x::AbstractVector{<:Real})
```

**Note:** In addition to these common methods, each multivariate distribution has its special methods, as introduced below.
Expand Down
1 change: 0 additions & 1 deletion docs/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```
Expand Down
2 changes: 0 additions & 2 deletions src/censored.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading