Structure type + in place modification of columns of arrays

In this post, I have two questions I am not sure are completely separate or not, so I ask them both here.

I define a structure and a substructure that has a field B which is an array of Distributions. It can be univariate, product, multivariate etc.
I define the structure like that

using Distributions
abstract type mainT{F<:VariateForm} end

struct SubT{F} <: mainT{F}
    B::AbstractArray{Distribution{F}}
end

However, when I try to define a SubT from an array of Bernoulli it does not work

K = 5
T = 2
B = Bernoulli.(rand(K,T)) # Array of Bernoulli distributions
h = SubT(B) # Fail
ERROR: MethodError: no method matching SubT(::Matrix{Bernoulli{Float64}})
Closest candidates are:
  SubT(::AbstractArray{Distribution{F, S} where S<:ValueSupport, N} where N) where F at REPL[3]:2

So I can do the following to make it work

B = convert(Matrix{UnivariateDistribution{S} where S<:ValueSupport}, B)
h = SubT2(B) # Works

This type re-definition is problematic for many reason so I would like to avoid and be able to define my h the first way. Ideally, the type of h would not be UnivariateDistribution{S} where S<:ValueSupportbut specific to each choice of distribution, e.g. here Matrix{Bernoulli{Float64}}.

The second problem is, when I want to sort columns of my array in some specific way with in place modification. I have the function

function sort_wrt_ref!(B::AbstractVector)
	K = size(B, 1)
	sorting = [succprob(h.B[k]) for k in 1:K]
	B[:] = B[sortperm(sorting)]
end

However, it does not seem to modify in place the columns of my array.

julia> h.B
5×2 Matrix{UnivariateDistribution{S} where S<:ValueSupport}:
 Bernoulli{Float64}(p=0.958907)   Bernoulli{Float64}(p=0.204364)
 Bernoulli{Float64}(p=0.0382291)  Bernoulli{Float64}(p=0.898818)
 Bernoulli{Float64}(p=0.953247)   Bernoulli{Float64}(p=0.367522)
 Bernoulli{Float64}(p=0.196388)   Bernoulli{Float64}(p=0.0861771)
 Bernoulli{Float64}(p=0.333265)   Bernoulli{Float64}(p=0.275637)

julia> sort_wrt_ref!(h.B[:,1]) # it is sorting as the output shows but not changing the column h.B[:,1]
5-element Vector{UnivariateDistribution{S} where S<:ValueSupport}:
 Bernoulli{Float64}(p=0.038229098624871005)
 Bernoulli{Float64}(p=0.1963878250239648)
 Bernoulli{Float64}(p=0.3332646196249027)
 Bernoulli{Float64}(p=0.9532465128727561)
 Bernoulli{Float64}(p=0.958907107799279)

julia> h.B
5×2 Matrix{UnivariateDistribution{S} where S<:ValueSupport}:
 Bernoulli{Float64}(p=0.958907)   Bernoulli{Float64}(p=0.204364)
 Bernoulli{Float64}(p=0.0382291)  Bernoulli{Float64}(p=0.898818)
 Bernoulli{Float64}(p=0.953247)   Bernoulli{Float64}(p=0.367522)
 Bernoulli{Float64}(p=0.196388)   Bernoulli{Float64}(p=0.0861771)
 Bernoulli{Float64}(p=0.333265)   Bernoulli{Float64}(p=0.275637)

I have the same issue with other in place function. They work as expected on vector but not on slice of array.

For your first problem, you are looking for:

struct SubT1{F, N}
    # Not AbstractArray - use a concrete type
    # The eltype of B is a subtype of Distribution
    B :: Array{<: Distribution{F}, N}
end

K = 5
T = 2
B = Bernoulli.(rand(K,T))

h = SubT1(B)
2 Likes

For your second problem, the issue is that B[:, 1] creates a copy of that column. One solution is to sort a @view instead. MWE:

julia> B0 = [4 3; 2 1];

julia> function sort_wrt_ref!(B::AbstractVector{F}) where F
               K = size(B, 1)
               B[:] = B[sortperm(B)]
       end
sort_wrt_ref! (generic function with 1 method)

julia> B = copy(B0);

julia> sort_wrt_ref!(B[:, 1])
2-element Vector{Int64}:
 2
 4

julia> @show B
B = [4 3; 2 1]
2×2 Matrix{Int64}:
 4  3
 2  1

julia> B = copy(B0);

julia> sort_wrt_ref!(@view B[:, 1])
2-element Vector{Int64}:
 2
 4

julia> @show B
B = [2 3; 4 1]
2×2 Matrix{Int64}:
 2  3
 4  1
2 Likes

Thanks that works!

  1. Why should one use a concrete type instead of Abstract type?
  2. Ok.

Your first problem occurs because Matrix{Bernoulli{Float64}} is not a subtype of AbstractArray{Distribution} Just like Array{Float64} is not a subtype of AbstractArray{Real}. The key change is the use of {<:Distribution{F}} as opposed to your original definition.

I don’t think the change from AbstractArray to Array is strictly necessary for the code to run, but generally, you want to avoid putting abstract types in structs for performance reasons since it adds an extra layer of indirection every time you access a structure member.