Questions about using `Parameters`

I thought I’d ask some questions about the Parameters package here because hopefully the answers will be useful to others. (Maybe some answers actually will make their way to the Parameters package documentation?)

Ok, so I work best by example, so here we go, let’s build a Para type and a parameter container p:

using Parameters
@with_kw struct Para
    a = 1.0
    b = 3.0 + im
end
p = Para()

I put two default values of different types in here. Well first thing I want is to have an eltype function for this type, which promotes the type of every parameter (in other words here, I want eltype(p) to be the promotion of the types of a and b). Just below is my current version of overloading eltype to do that (please let me know how this could be done better):

function Base.:eltype(p::Para)
    ot = Int # Starting somewhere 
    pnames = fieldnames(typeof(p))
    for pname in pnames
        ot = promote_type(ot, typeof(getfield(p, pname)))
    end
    return ot
end
eltype(p)

Then I want to convert p to a vector and back. To convert it to a vector x, I do the following (again, this works but I am not sure this is the correct way):

function para2vec(p::Para)
    pnames = fieldnames(typeof(p))
    np = length(pnames)
    x = zeros(eltype(p), np)
    for ix in 1:np
        x[ix] = getfield(p, pnames[ix])
    end
    return x
end
x = para2vec(p)

Now the hard part for me is to convert x into p. Could someone jump in and help with that? I am guessing I could use the @pack macro but I haven’t figured out how yet…


EDIT #1
Converting x into p is in fact just as simple as

p2 = Para(x...)

:smile:


EDIT #2
Now I would like to write derivatives of a function f(u, p::Para) w.r.t. p. (u is a vector of element-type :<Number.) Right now I do it by hand, i.e., I write

function Dpf(u, p::Para)
    @unpack a, b = p
    Daf = # derivative of `f` w.r.t `a`
    Dbf = # derivative of `f` w.r.t `b`
    return [Daf Dbf]
end

But I am sure there is a better approach, something with more abstraction. I thought of creating a type for those derivatives, i.e. a container like

struct DerivativeWRTPara
    Daf
    Dbf
end

That would contain the same number of fields as the Para type (so that there is an “automatic” correspondence between the types). I’m a bit afraid of exploring this avenue: is this stupid?

This is not Parameters-specific advice (I love Parameters.@unpack, but don’t really use anything else), but I would have a type parameter, and have the constructor promote it.

struct Para{T}
    a::T
    b::T
end

This will allow the compiler to optimize your code. Then

Base.eltype(::Para{T}) where T = T

For conversion, I would define an iterator, so I could also do

a, b = Para(1, 2)

and collect. See the example for Base.Pair.

Converting from a vector should be as easy as Para(v...).

Hard to say without context. I would just use automatic differentiation, eg

1 Like

I don’t think eltype is the right function to overload here, unless you view your type as some sort of iterable collection. Also, Tamas’ advice is good: parameterize the type, see https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-containers-with-abstract-type-parameters-1.

1 Like

Thanks, I’ll try to implement all that and repot back

So I have made the changes you suggested and it works nicely, so thanks :slight_smile: