Find types of arguments of a function's method

Suppose I wanna compute the mean of every distribution in Distributions.jl.
I create a vector w/ all distributions then loop through each & compute the mean.
Thanks to @tfehring for showing me how to pull all the distributions.

using Distributions
subtypes(Distribution) #Vector{Any} with 78 elements

D_m = []; E_m = []; #
for d = subtypes(Distribution)
    try
        fn = mean(d())
        push!(D_m, [d, fn])
    catch e
        println("Err ", d, e)
        push!(E_m, [d, e])
    end
end

This works for distributions w/ default parameters:
Example: mean(Arcsine())

It does not work for distributions w/o default parameter values:
Example: Chi(ν) has one parameter w/ no default values.

I need help w/ the following, as I loop over a distribution “d” w/o default parameter values:

  1. for simplicity let’s choose the first method
    methods(d).ms[1]
    Example Chi: methods(Chi).ms[1] gives: (::Type{Chi})(ν::Integer)
  2. find the number of arguments in the first method
  3. find the required type of each argument in the first method (real number, matrix etc)
  4. fill in some arbitrary legal values for each parameter in the first method
    Use the simplest for each. Eg if it’s a matrix, set mu= [0], sigma=[1]…
  5. compute the mean

This turned out to be non-trivial for me.
Yes, I realize some of these distributions are abstract structures for which it won’t be possible.

Which step is the problem?

The number and types of arguments are methods(d).ms[1].sig. It looks like it’s a tuple where the first element is typeof(d), and the rest (Base.tail) are the types of expected arguments.

To provide a value of an arbitrary type is the most difficult part to me, I’ve no idea how to do that in a generic way, there’s only hope that there’s a method which accepts types you know how to instantiate.

1 Like

@Vasily_Pisarev
My goal is to compute the mean of every distribution.

  1. If it has default values for parameters then: mean(Normal())
  2. If not, to automatically set legal parameter values.
    Chi() doesn’t work bc it doesn’t have a default parameter.
    methods(Chi).ms[1] tells me it has 1 param which is an Integer: (::Type{Chi})(ν::Integer)
    I want the loop to automatically: mean(Chi(1))
    This can be tricky: methods(NormalInverseGaussian).ms[1] has four param, all integers
    I want: mean(NormalInverseGaussian(1,1,1,1))
    methods(MvNormal).ms[1]: (μ::AbstractArray{T,1}, Σ::PDMats.AbstractPDMat{T}) where T<:Real
    I want: mean(MvNormal([1],[1]))
    I want the loop to do this all automatically.

For each method signature in the table, you have a tuple-type like Tuple{Type{Uniform}, Integer}. You can do Base.tail(Tuple(tuptype.parameters)) to get the “everything else” of that method. From there I suppose you could try calling one on everything in that list and hoping there’s a method out there :man_shrugging:. Assuming this works for you, you could ostensibly do it like: distr(one.(the_tail)...)

Just as a heads up, I generated the full list (of “first” methods) with

 map(x-> isabstracttype(x) ? nothing : methods(x).ms[1].sig, subtypes(Distribution))

and took a look through it. There’s some definite oddballs that will give you a hard time time. e.g.

Tuple{Type{DirichletMultinomial},Integer,Array{T,1}} where T<:Integer    # default array size?
Tuple{Type{Truncated},Distribution{Univariate,S} where S<:ValueSupport,Integer,Integer}    # default distribution?

You are running into difficulties because this is an ill-defined problem: mostly, the vocabulary of Distributions.jl is not distributions, but functions that generate distributions (constructors). The distinction is important.

What you would need is either an API that allows you to query the space of valid arguments, or generates “some” valid instance of each distribution class. Neither exists, because it is a pretty rare use case.

What are you trying to accomplish?

1 Like