I want to use a custom multivariate distribution that actually generates arrays, and I want to know if there’s anything I need to modify in order to be adapted to be used in Turing
using Distributions
struct OrthoNNDist <: DiscreteMultivariateDistribution
x0::Vector{Float64}
oc::Array{Float64,2}
x1s::Array
prob::Float64
#return a new uniform distribution with all vectors in x1s orthogonal to oc
function OrthoNNDist(x0::Vector{Float64}, oc::Array{Float64,2})
x1s = []
for i = 1:size(oc)[2]
x1 = x0 + oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
x1 = x0 - oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
end
new(x0, oc, x1s, 1.0/length(x1s))
end
end
Base.length(d::OrthoNNDist) = length(d.x0)
Distributions.rand(d::OrthoNNDist) = rand(d.x1s)
Distributions.pdf(d::OrthoNNDist, x::Vector) = x in d.x1s ? d.prob : 0.0
Distributions.pdf(d::OrthoNNDist) = fill(d.prob, size(d.x1s))
Distributions.logpdf(d::OrthoNNDist, x::Vector) = log(pdf(d, x))
I don’t know if it’s fit to be used in Turing or no.
I don’t think your code will work with automatic differentiation. The problem is that your types need to be a sub-type of real.
This might get you started
struct OrthoNNDist{T<:Real} <: DiscreteMultivariateDistribution
x0::Vector{T}
oc::Array{T,2}
x1s::Array{T}
prob::T
#return a new uniform distribution with all vectors in x1s orthogonal to oc
function OrthoNNDist(x0::Vector{T}, oc::Array{T,2}) where {T<:Real}
x1s = T[]
for i = 1:size(oc)[2]
x1 = x0 + oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
x1 = x0 - oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
end
new(x0, oc, x1s, 1.0/length(x1s))
end
end
2 Likes
i wanted to try it, but then it shows me:
ERROR: syntax: too few type parameters specified in "new{...}" around REPL[25]:1
Stacktrace:
[1] top-level scope
@ REPL[25]:1
Can you please provide the code that generated that error?
Ok. I see that you were trying to run the code for the constructor. You have two options. 1) you can pass the type information to new as new{T}
, 2) you can pull your constructor outside of the struct definition. I personally prefer the second option because its easier to read.
using Distributions
struct OrthoNNDist{T<:Real} <: DiscreteMultivariateDistribution
x0::Vector{T}
oc::Array{T,2}
x1s::Array{T}
prob::T
end
#return a new uniform distribution with all vectors in x1s orthogonal to oc
function OrthoNNDist(x0::Vector{T}, oc::Array{T,2}) where {T<:Real}
x1s = T[]
for i = 1:size(oc)[2]
x1 = x0 + oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
x1 = x0 - oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
end
return OrthoNNDist(x0, oc, x1s, 1.0/length(x1s)::T)
end
1 Like
A few notes that might be helpful. In the return statement above, I annotated the last argument as 1.0/length(x1)::T
, to ensure that prob
assumes type T
.
The code assumes that all fields will be an array with elements of type T
, or type T
itself. If that is too restrictive, you can define T1
,T2
, … to make allow fields to have different subtypes of Real
.
1 Like
I tried the last code you put in the comment and I took these arguments to run the code: oc=[3.0 2.0 1.0; 0.0 1.0 1.0]
, x0=[1.0;2.0]
and i got this:
ERROR: MethodError: Cannot `convert` an object of type Vector{Float64} to an object of type Float64
Closest candidates are:
convert(::Type{T}, ::Base.TwicePrecision) where T<:Number at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/base/twiceprecision.jl:262
convert(::Type{T}, ::AbstractChar) where T<:Number at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/base/char.jl:185
convert(::Type{T}, ::CartesianIndex{1}) where T<:Number at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/base/multidimensional.jl:136
...
Stacktrace:
[1] push!(a::Vector{Float64}, item::Vector{Float64})
@ Base ./array.jl:994
[2] OrthoNNDist(x0::Vector{Float64}, oc::Matrix{Float64})
@ Main ./REPL[19]:7
[3] top-level scope
@ REPL[20]:1
There were a few issues that I fixed. First, upon closer inspection, it is clear that x1
is a vector of vectors. I updated the code to reflect that. Also, I introduced an error in defining probs, which is now fixed. The function nonneg
was not defined, so I added a definition. Finally, size(oc)[2]
makes an unnecessary memory allocation. The changing it to size(oc, 2)
fixes that minor performance issue.
The code below runs without an error. It should work with Turing as long as all elements should be of type T
.
Summary
using Distributions
nonneg(x) = x >= zero(x)
struct OrthoNNDist{T<:Real} <: DiscreteMultivariateDistribution
x0::Vector{T}
oc::Array{T,2}
x1s::Vector{Vector{T}}
prob::T
end
#return a new uniform distribution with all vectors in x1s orthogonal to oc
function OrthoNNDist(x0::Vector{T}, oc::Array{T,2}) where {T<:Real}
x1s = Vector{Vector{T}}(undef,0)
for i = 1:size(oc, 2)
x1 = x0 + oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
x1 = x0 - oc[:, i]
if nonneg(x1)
push!(x1s, x1)
end
end
prob::T = 1.0 / length(x1s)
return OrthoNNDist(x0, oc, x1s, prob)
end
oc = [3.0 2.0 1.0; 0.0 1.0 1.0]
x0 = [1.0;2.0]
dist = OrthoNNDist(x0, oc)
1 Like
yeah concerning the nonneg
function i forgot to put it down with the code it’s defined like this: nonneg(v) = all(v.>=0) ? true : false
where v
is a vector
If you’re going to use it with samplers such as HMC, you also need to
define bijector
for your distribution.
I recommend checking out
https://turing.ml/v0.21/docs/using-turing/advanced#how-to-define-a-customized-distribution
for more information regarding these things.
1 Like
i’m just trying to use a metropolis-hastings algorithm to begin with to see how it goes.
Depending on which MH approach you’re using, this might still be
required. For example, Random walk MH, i.e. MH with MvNormal
as
proposal assumes the space is unconstrained and so requires impl of
bijector
.
If you’re specifying your own proposal which statisfies the constraints
of the variables, then AFAIK you should be fine even without bijector
.
But I’d still recomemend just implementing it; shouldn’t be too difficult.