Skip to content

Commit

Permalink
Deprecate use of vectors + scalars of standard deviations in construc…
Browse files Browse the repository at this point in the history
…tors of multivariate normal distributions (#1362)

* Deprecate vectors + scalars of standard deviations

* Bump version
  • Loading branch information
devmotion authored Jul 15, 2021
1 parent f02fe35 commit 02f1da3
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 111 deletions.
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.10"
version = "0.25.11"

[deps]
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
Expand Down
11 changes: 5 additions & 6 deletions src/multivariate/mvlognormal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,16 @@ struct MvLogNormal{T<:Real,Cov<:AbstractPDMat,Mean<:AbstractVector} <: AbstractM
normal::MvNormal{T,Cov,Mean}
end

#Constructors mirror the ones for MvNormmal
MvLogNormal::AbstractVector::AbstractPDMat) = MvLogNormal(MvNormal(μ,Σ))
MvLogNormal::AbstractPDMat) = MvLogNormal(MvNormal(Zeros{eltype(Σ)}(dim(Σ)),Σ))
MvLogNormal::AbstractVector::Matrix) = MvLogNormal(MvNormal(μ,Σ))
# Constructors mirror the ones for MvNormmal
MvLogNormal::AbstractVector{<:Real}, Σ::AbstractMatrix{<:Real}) = MvLogNormal(MvNormal(μ, Σ))
MvLogNormal::AbstractMatrix{<:Real}) = MvLogNormal(MvNormal(Σ))

# Deprecated constructors
MvLogNormal::AbstractVector::Vector) = MvLogNormal(MvNormal(μ,σ))
MvLogNormal::AbstractVector,s::Real) = MvLogNormal(MvNormal(μ,s))
MvLogNormal::AbstractMatrix) = MvLogNormal(MvNormal(Σ))
MvLogNormal::AbstractVector) = MvLogNormal(MvNormal(σ))
MvLogNormal(d::Int,s::Real) = MvLogNormal(MvNormal(d,s))


Base.eltype(::Type{<:MvLogNormal{T}}) where {T} = T

### Conversion
Expand Down
65 changes: 24 additions & 41 deletions src/multivariate/mvnormal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -148,33 +148,9 @@ _pdf!(r::AbstractArray, d::AbstractMvNormal, x::AbstractMatrix) = exp!(_logpdf!(
MvNormal
Generally, users don't have to worry about these internal details.
We provide a common constructor `MvNormal`, which will construct a distribution of
appropriate type depending on the input arguments.
MvNormal(sig)
Construct a multivariate normal distribution with zero mean and covariance represented by `sig`.
MvNormal(mu, sig)
Construct a multivariate normal distribution with mean `mu` and covariance represented by `sig`.
MvNormal(d, sig)
Construct a multivariate normal distribution of dimension `d`, with zero mean, and an
isotropic covariance matrix corresponding `abs2(sig)*I`.
# Arguments
- `mu::Vector{T<:Real}`: The mean vector.
- `d::Real`: dimension of distribution.
- `sig`: The covariance, which can in of either of the following forms (with `T<:Real`):
1. subtype of `AbstractPDMat`,
2. symmetric matrix of type `Matrix{T}`,
3. vector of type `Vector{T}`: indicating a diagonal covariance as `diagm(abs2(sig))`,
4. real-valued number: indicating an isotropic covariance matrix corresponding `abs2(sig) * I`.
**Note:** The constructor will choose an appropriate covariance form internally, so that
special structure of the covariance can be exploited.
"""
struct MvNormal{T<:Real,Cov<:AbstractPDMat,Mean<:AbstractVector} <: AbstractMvNormal
μ::Mean
Expand All @@ -197,33 +173,40 @@ function MvNormal(μ::AbstractVector{T}, Σ::AbstractPDMat{T}) where {T<:Real}
MvNormal{T,typeof(Σ), typeof(μ)}(μ, Σ)
end

function MvNormal::AbstractVector{<:Real}, Σ::AbstractPDMat)
R = Base.promote_eltype(μ, Σ)
MvNormal(convert(AbstractArray{R}, μ), convert(AbstractArray{R}, Σ))
end

function MvNormal::AbstractVector, Σ::AbstractPDMat)
function MvNormal::AbstractVector{<:Real}, Σ::AbstractPDMat{<:Real})
R = Base.promote_eltype(μ, Σ)
MvNormal(convert(AbstractArray{R}, μ), convert(AbstractArray{R}, Σ))
end

# constructor with general covariance matrix
"""
MvNormal(μ::AbstractVector{<:Real}, Σ::AbstractMatrix{<:Real})
Construct a multivariate normal distribution with mean `μ` and covariance matrix `Σ`.
"""
MvNormal::AbstractVector{<:Real}, Σ::AbstractMatrix{<:Real}) = MvNormal(μ, PDMat(Σ))
MvNormal::AbstractVector{<:Real}, Σ::Diagonal{<:Real}) = MvNormal(μ, PDiagMat(diag(Σ)))
MvNormal::AbstractVector{<:Real}, Σ::Diagonal{<:Real}) = MvNormal(μ, PDiagMat(Σ.diag))
MvNormal::AbstractVector{<:Real}, Σ::UniformScaling{<:Real}) =
MvNormal(μ, ScalMat(length(μ), Σ.λ))

# constructor with vector of standard deviations
MvNormal::AbstractVector{<:Real}, σ::AbstractVector{<:Real}) = MvNormal(μ, PDiagMat(abs2.(σ)))

# constructor with scalar standard deviation
MvNormal::AbstractVector{<:Real}, σ::Real) = MvNormal(μ, ScalMat(length(μ), abs2(σ)))
function MvNormal(
μ::AbstractVector{<:Real}, Σ::Diagonal{<:Real,<:FillArrays.AbstractFill{<:Real,1}}
)
return MvNormal(μ, ScalMat(size(Σ, 1), FillArrays.getindex_value.diag)))
end

# constructor without mean vector
MvNormal::AbstractVecOrMat{<:Real}) = MvNormal(Zeros{eltype(Σ)}(size(Σ, 1)), Σ)
"""
MvNormal(Σ::AbstractMatrix{<:Real})
Construct a multivariate normal distribution with zero mean and covariance matrix `Σ`.
"""
MvNormal::AbstractMatrix{<:Real}) = MvNormal(Zeros{eltype(Σ)}(size(Σ, 1)), Σ)

# special constructor
MvNormal(d::Int, σ::Real) = MvNormal(Zeros{typeof(σ)}(d), σ)
# deprecated constructors with standard deviations
Base.@deprecate MvNormal::AbstractVector{<:Real}, σ::AbstractVector{<:Real}) MvNormal(μ, Diagonal(map(abs2, σ)))
Base.@deprecate MvNormal::AbstractVector{<:Real}, σ::Real) MvNormal(μ, σ^2 * I)
Base.@deprecate MvNormal::AbstractVector{<:Real}) MvNormal(Diagonal(map(abs2, σ)))
Base.@deprecate MvNormal(d::Int, σ::Real) MvNormal(Diagonal(Fill^2, d)))

Base.eltype(::Type{<:MvNormal{T}}) where {T} = T

Expand Down
78 changes: 36 additions & 42 deletions src/multivariate/mvnormalcanon.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,6 @@ const ZeroMeanDiagNormalCanon{Axes} = MvNormalCanon{PDiagMat, Zeros{Float64,1}}
const ZeroMeanIsoNormalCanon{Axes} = MvNormalCanon{ScalMat, Zeros{Float64,1,Axes}}
```
A multivariate distribution with canonical parameterization can be constructed using a common constructor `MvNormalCanon` as:
MvNormalCanon(h, J)
Construct a multivariate normal distribution with potential vector `h` and precision matrix represented by `J`.
MvNormalCanon(J)
Construct a multivariate normal distribution with zero mean (thus zero potential vector) and precision matrix represented by `J`.
MvNormalCanon(d, J)
Construct a multivariate normal distribution of dimension `d`, with zero mean and
an isotropic precision matrix corresponding `J*I`.
# Arguments
- `d::Int`: dimension of distribution
- `h::Vector{T<:Real}`: the potential vector, of type `Vector{T}` with `T<:Real`.
- `J`: the representation of the precision matrix, which can be in either of the following forms (`T<:Real`):
1. an instance of a subtype of `AbstractPDMat`,
2. a square matrix of type `Matrix{T}`,
3. a vector of type `Vector{T}`: indicating a diagonal precision matrix as `diagm(J)`,
4. a real number: indicating an isotropic precision matrix corresponding `J*I`.
**Note:** `MvNormalCanon` share the same set of methods as `MvNormal`.
"""
struct MvNormalCanon{T<:Real,P<:AbstractPDMat,V<:AbstractVector} <: AbstractMvNormal
Expand All @@ -80,46 +56,64 @@ const ZeroMeanIsoNormalCanon{Axes} = MvNormalCanon{Float64,ScalMat{Float64},Zer


### Constructors

function MvNormalCanon::AbstractVector{T}, h::AbstractVector{T}, J::AbstractPDMat{T}) where T<:Real
function MvNormalCanon::AbstractVector{T}, h::AbstractVector{T}, J::AbstractPDMat{T}) where {T<:Real}
length(μ) == length(h) == dim(J) || throw(DimensionMismatch("Inconsistent argument dimensions"))
if typeof(μ) == typeof(h)
if typeof(μ) === typeof(h)
return MvNormalCanon{T,typeof(J),typeof(μ)}(μ, h, J)
else
return MvNormalCanon{T,typeof(J),Vector{T}}(collect(μ), collect(h), J)
end
end

function MvNormalCanon::AbstractVector{T}, h::AbstractVector{T}, J::P) where {T<:Real, P<:AbstractPDMat}
function MvNormalCanon::AbstractVector{T}, h::AbstractVector{T}, J::AbstractPDMat) where {T<:Real}
R = promote_type(T, eltype(J))
MvNormalCanon(convert(AbstractArray{R}, μ), convert(AbstractArray{R}, h), convert(AbstractArray{R}, J))
end

function MvNormalCanon::AbstractVector{T}, h::AbstractVector{S}, J::P) where {T<:Real, S<:Real, P<:AbstractPDMat}
function MvNormalCanon::AbstractVector{<:Real}, h::AbstractVector{<:Real}, J::AbstractPDMat)
R = Base.promote_eltype(μ, h, J)
MvNormalCanon(convert(AbstractArray{R}, μ), convert(AbstractArray{R}, h), convert(AbstractArray{R}, J))
end

function MvNormalCanon(J::AbstractPDMat)
z = Zeros{eltype(J)}(dim(J))
MvNormalCanon(z, z, J)
end

function MvNormalCanon(h::AbstractVector{T}, J::P) where {T<:Real, P<:AbstractPDMat}
function MvNormalCanon(h::AbstractVector{<:Real}, J::AbstractPDMat)
length(h) == dim(J) || throw(DimensionMismatch("Inconsistent argument dimensions"))
R = Base.promote_eltype(h, J)
hh, JJ = collect(convert(AbstractArray{R}, h)), convert(AbstractArray{R}, J)
MvNormalCanon{eltype(hh),typeof(JJ),typeof(hh)}(JJ \ hh, hh, JJ)
hh = convert(AbstractArray{R}, h)
JJ = convert(AbstractArray{R}, J)
MvNormalCanon(JJ \ hh, hh, JJ)
end

"""
MvNormalCanon(h::AbstractVector{<:Real}, J::AbstractMatrix{<:Real})
Construct a multivariate normal distribution with potential vector `h` and precision matrix
`J`.
"""
MvNormalCanon(h::AbstractVector{<:Real}, J::AbstractMatrix{<:Real}) = MvNormalCanon(h, PDMat(J))
MvNormalCanon(h::AbstractVector{<:Real}, prec::AbstractVector{<:Real}) = MvNormalCanon(h, PDiagMat(prec))
MvNormalCanon(h::AbstractVector{<:Real}, prec::Real) = MvNormalCanon(h, ScalMat(length(h), prec))
MvNormalCanon(h::AbstractVector{<:Real}, J::Diagonal{<:Real}) = MvNormalCanon(h, PDiagMat(J.diag))
function MvNormalCanon(h::AbstractVector{<:Real}, J::UniformScaling{<:Real})
return MvNormalCanon(h, ScalMat(length(h), J.λ))
end
function MvNormalCanon(
h::AbstractVector{<:Real}, J::Diagonal{<:Real,<:FillArrays.AbstractFill{<:Real,1}}
)
return MvNormalCanon(h, ScalMat(size(J, 1), FillArrays.getindex_value(J.diag)))
end

# Constructor without mean vector
"""
MvNormalCanon(J::AbstractMatrix{<:Real})
MvNormalCanon(J::AbstractMatrix) = MvNormalCanon(PDMat(J))
MvNormalCanon(prec::AbstractVector) = MvNormalCanon(PDiagMat(prec))
MvNormalCanon(d::Int, prec) = MvNormalCanon(ScalMat(d, prec))
Construct a multivariate normal distribution with zero mean (thus zero potential vector) and
precision matrix `J`.
"""
MvNormalCanon(J::AbstractMatrix{<:Real}) = MvNormalCanon(Zeros{eltype(J)}(size(J, 1)), J)

# Deprecated constructors
Base.@deprecate MvNormalCanon(h::AbstractVector{<:Real}, prec::AbstractVector{<:Real}) MvNormalCanon(h, Diagonal(prec))
Base.@deprecate MvNormalCanon(h::AbstractVector{<:Real}, prec::Real) MvNormalCanon(h, prec * I)
Base.@deprecate MvNormalCanon(prec::AbstractVector) MvNormalCanon(Diagonal(prec))
Base.@deprecate MvNormalCanon(d::Int, prec::Real) MvNormalCanon(Diagonal(Fill(prec, d)))

### Show

Expand Down
8 changes: 4 additions & 4 deletions test/mvlognormal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ end
(MvLogNormal(mu,PDMats.PDMat(C)), mu, C),
(MvLogNormal(mu_r,PDMats.PDMat(C)), mu_r, C),
(MvLogNormal(PDMats.PDiagMat(sqrt.(va))), zeros(3), Matrix(Diagonal(va))),
(MvLogNormal(mu, sqrt(0.2)), mu, Matrix(0.2I, 3, 3)),
(MvLogNormal(3, sqrt(0.2)), zeros(3), Matrix(0.2I, 3, 3)),
(MvLogNormal(mu, Vector{Float64}(sqrt.(va))), mu, Matrix(Diagonal(va))), # Julia 0.4 loses type information so Vector{Float64} can be dropped when we don't support 0.4
(MvLogNormal(Vector{Float64}(sqrt.(va))), zeros(3), Matrix(Diagonal(va))), # Julia 0.4 loses type information so Vector{Float64} can be dropped when we don't support 0.4
(@test_deprecated(MvLogNormal(mu, sqrt(0.2))), mu, Matrix(0.2I, 3, 3)),
(@test_deprecated(MvLogNormal(3, sqrt(0.2))), zeros(3), Matrix(0.2I, 3, 3)),
(@test_deprecated(MvLogNormal(mu, Vector{Float64}(sqrt.(va)))), mu, Matrix(Diagonal(va))), # Julia 0.4 loses type information so Vector{Float64} can be dropped when we don't support 0.4
(@test_deprecated(MvLogNormal(Vector{Float64}(sqrt.(va)))), zeros(3), Matrix(Diagonal(va))), # Julia 0.4 loses type information so Vector{Float64} can be dropped when we don't support 0.4
(MvLogNormal(mu, C), mu, C),
(MvLogNormal(C), zeros(3), C) ]
m, s = params(g)
Expand Down
47 changes: 30 additions & 17 deletions test/mvnormal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ end
using Distributions
using LinearAlgebra, Random, Test
using SparseArrays
using FillArrays

import Distributions: distrname

Expand Down Expand Up @@ -104,23 +105,23 @@ end
J = [4. -2. -1.; -2. 5. -1.; -1. -1. 6.]

for (g, μ, Σ) in [
(MvNormal(mu, sqrt(2.0)), mu, Matrix(2.0I, 3, 3)),
(MvNormal(mu_r, sqrt(2.0)), mu_r, Matrix(2.0I, 3, 3)),
(MvNormal(3, sqrt(2.0)), zeros(3), Matrix(2.0I, 3, 3)),
(MvNormal(mu, sqrt.(va)), mu, Matrix(Diagonal(va))),
(MvNormal(mu_r, sqrt.(va)), mu_r, Matrix(Diagonal(va))),
(MvNormal(sqrt.(va)), zeros(3), Matrix(Diagonal(va))),
(@test_deprecated(MvNormal(mu, sqrt(2.0))), mu, Matrix(2.0I, 3, 3)),
(@test_deprecated(MvNormal(mu_r, sqrt(2.0))), mu_r, Matrix(2.0I, 3, 3)),
(@test_deprecated(MvNormal(3, sqrt(2.0))), zeros(3), Matrix(2.0I, 3, 3)),
(@test_deprecated(MvNormal(mu, sqrt.(va))), mu, Matrix(Diagonal(va))),
(@test_deprecated(MvNormal(mu_r, sqrt.(va))), mu_r, Matrix(Diagonal(va))),
(@test_deprecated(MvNormal(sqrt.(va))), zeros(3), Matrix(Diagonal(va))),
(MvNormal(mu, C), mu, C),
(MvNormal(mu_r, C), mu_r, C),
(MvNormal(C), zeros(3), C),
(MvNormal(Symmetric(C)), zeros(3), Matrix(Symmetric(C))),
(MvNormal(Diagonal(dv)), zeros(3), Matrix(Diagonal(dv))),
(MvNormalCanon(h, 2.0), h ./ 2.0, Matrix(0.5I, 3, 3)),
(MvNormalCanon(mu_r, 2.0), mu_r ./ 2.0, Matrix(0.5I, 3, 3)),
(MvNormalCanon(3, 2.0), zeros(3), Matrix(0.5I, 3, 3)),
(MvNormalCanon(h, dv), h ./ dv, Matrix(Diagonal(inv.(dv)))),
(MvNormalCanon(mu_r, dv), mu_r ./ dv, Matrix(Diagonal(inv.(dv)))),
(MvNormalCanon(dv), zeros(3), Matrix(Diagonal(inv.(dv)))),
(@test_deprecated(MvNormalCanon(h, 2.0)), h ./ 2.0, Matrix(0.5I, 3, 3)),
(@test_deprecated(MvNormalCanon(mu_r, 2.0)), mu_r ./ 2.0, Matrix(0.5I, 3, 3)),
(@test_deprecated(MvNormalCanon(3, 2.0)), zeros(3), Matrix(0.5I, 3, 3)),
(@test_deprecated(MvNormalCanon(h, dv)), h ./ dv, Matrix(Diagonal(inv.(dv)))),
(@test_deprecated(MvNormalCanon(mu_r, dv)), mu_r ./ dv, Matrix(Diagonal(inv.(dv)))),
(@test_deprecated(MvNormalCanon(dv)), zeros(3), Matrix(Diagonal(inv.(dv)))),
(MvNormalCanon(h, J), J \ h, inv(J)),
(MvNormalCanon(J), zeros(3), inv(J)),
(MvNormal(mu, Symmetric(C)), mu, Matrix(Symmetric(C))),
Expand Down Expand Up @@ -159,11 +160,11 @@ end
h = J \ mu
@test typeof(MvNormal(mu, PDMat(Array{Float32}(C)))) == typeof(MvNormal(mu, PDMat(C)))
@test typeof(MvNormal(mu, Array{Float32}(C))) == typeof(MvNormal(mu, PDMat(C)))
@test typeof(MvNormal(mu, 2.0f0)) == typeof(MvNormal(mu, 2.0))
@test typeof(@test_deprecated(MvNormal(mu, 2.0f0))) == typeof(@test_deprecated(MvNormal(mu, 2.0)))

@test typeof(MvNormalCanon(h, PDMat(Array{Float32}(J)))) == typeof(MvNormalCanon(h, PDMat(J)))
@test typeof(MvNormalCanon(h, Array{Float32}(J))) == typeof(MvNormalCanon(h, PDMat(J)))
@test typeof(MvNormalCanon(h, 2.0f0)) == typeof(MvNormalCanon(h, 2.0))
@test typeof(@test_deprecated(MvNormalCanon(h, 2.0f0))) == typeof(@test_deprecated(MvNormalCanon(h, 2.0)))

@test typeof(MvNormalCanon(mu, Array{Float16}(h), PDMat(Array{Float32}(J)))) == typeof(MvNormalCanon(mu, h, PDMat(J)))

Expand All @@ -175,9 +176,21 @@ end
@test typeof(convert(MvNormalCanon{Float64}, d)) == typeof(MvNormalCanon(mu, h, PDMat(J)))
@test typeof(convert(MvNormalCanon{Float64}, d.μ, d.h, d.J)) == typeof(MvNormalCanon(mu, h, PDMat(J)))

@test MvNormal(mu, I) === MvNormal(mu, 1)
@test MvNormal(mu, 9 * I) === MvNormal(mu, 3)
@test MvNormal(mu, 0.25f0 * I) === MvNormal(mu, 0.5)
@test MvNormal(mu, I) === @test_deprecated(MvNormal(mu, 1))
@test MvNormal(mu, 9 * I) === @test_deprecated(MvNormal(mu, 3))
@test MvNormal(mu, 0.25f0 * I) === @test_deprecated(MvNormal(mu, 0.5))

@test MvNormal(mu, I) === MvNormal(mu, Diagonal(Ones(length(mu))))
@test MvNormal(mu, 9 * I) === MvNormal(mu, Diagonal(Fill(9, length(mu))))
@test MvNormal(mu, 0.25f0 * I) === MvNormal(mu, Diagonal(Fill(0.25f0, length(mu))))

@test MvNormalCanon(h, I) == MvNormalCanon(h, Diagonal(Ones(length(h))))
@test MvNormalCanon(h, 9 * I) == MvNormalCanon(h, Diagonal(Fill(9, length(h))))
@test MvNormalCanon(h, 0.25f0 * I) == MvNormalCanon(h, Diagonal(Fill(0.25f0, length(h))))

@test typeof(MvNormalCanon(h, I)) === typeof(MvNormalCanon(h, Diagonal(Ones(length(h)))))
@test typeof(MvNormalCanon(h, 9 * I)) === typeof(MvNormalCanon(h, Diagonal(Fill(9, length(h)))))
@test typeof(MvNormalCanon(h, 0.25f0 * I)) === typeof(MvNormalCanon(h, Diagonal(Fill(0.25f0, length(h)))))
end

@testset "MvNormal 32-bit logpdf" begin
Expand Down

2 comments on commit 02f1da3

@devmotion
Copy link
Member Author

Choose a reason for hiding this comment

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

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/40935

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.25.11 -m "<description of version>" 02f1da36de53b6bfb4a9fbfe6fd08cb8bf1410d0
git push origin v0.25.11

Please sign in to comment.