Parameters.jl usage: group variables to reduce number of inputs to function

I am looking at Parameters.jl to reduce the number of inputs to some functions.

How do I add an already-defined parameter to a parameter struct?

For context, one of my functions requires many constants as input: N1, N2, …, which are themselves computed by another function. So I can’t just say

@with_kw struct Para
    N1::Int64
end

and give it a value because N1 is already defined.

…and I guess while I’m at it, how would I define an array inside the Para struct? One of the arrays I would try to define is of type StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}. (Can that string be truncated and/or is there a better way to do that?) Similar situation as above, that array is already defined, so I would not give it a value inside the struct.

Thanks so much!

Unclear what you mean when you say that you can’t do exactly what you have written. Structs pretty much just pack things together when they are created. Parameters.jl just adds some convenience functionality for working with structs.

So your code example defines a struct Para which you can create an instance of by doing Para(N1=N1value) where N1value is whatever value you are trying to give to N1 in the parameter struct. You can get N1value any way you like, no worries that it is a computed constant.

1 Like

You can even call it N1 (instead of N1value), that’s not a problem:

julia> using Parameters

julia> @with_kw struct Para
           N1::Int64
       end
Para

julia> N1 = 42
42

julia> Para(N1=N1)
Para
  N1: Int64 42
1 Like

Of course, but it does become a bit tricky to describe it to someone who is new to the process :wink:

2 Likes

Thanks for the replies.

I’m confused by the order of your code. I imagine the use case as:

N1 = func1(inputs)    # N1 now has some value from another function

@with_kw struct Para  # structure I want to create to group many constants/arrays
     N1::Int          # N1 is already defined, so I simply want to add it to struct
end

function func2(Para)  # function I want to feed some of Para constants/arrays
@unpack N1
# do math using N1
end

I interpret your code as 1) assigning N1 a value, 2) create a struct containing N1, (3) specify value of N1 using Para(N1=N1). This seems redundant, but I’m sure I’m missing something in connecting Parameters usage to my use case.

You’ve missed the creation of the actual variable

# N1 = func1(inputs)    # I remove this line because N1 as variable and as a field of the Para struct is confusing

@with_kw struct Para  # structure I want to create to group many constants/arrays
     N1::Int          # N1 is already defined, so I simply want to add it to struct
end

p = Para(func1(inputs))

function func2(p)  # function I want to feed some of Para constants/arrays
@unpack N1 = p
# do math using N1
end

By the way, it is not necessary to create intermediate structure, you can use NamedTuple

p = (N1 = func1(inputs))

Or even better

N1 = func1(inputs)
p = (; N1) # This is equivalent to the creation of NamedTuple (N1 = N1)
1 Like

I can’t get p = (N1 = func1(inputs)) to work when func1 has multiple outputs.

It’d be nice and concise if I could do:

p = (N1a,N1b,N1c,... = func1(inputs1))
p = (N2a,N2b,N2c,... = func1(inputs2))

and group all of those outputs in p so that I can later pick & choose those that I want in other functions.

If you structure the return of func1 appropriately you get this for free:

function func1(inputs)
# do stuff
return (N1=this,N2=that)
end

then

p1 = func1(inputs1)
p2 = func1(inputs2)

Done.

This is exactly what I do.

1 Like

That’s true, but now p1 and p2 are separate tuples, right?

If a separate function required parameters from both, they’d have to be fed in separately.

What I’m really doing is computing grid parameters (step size, number of points, etc.) So I have a function func that looks like gp1,gp2,gp3 = func(inputs), where “gp” are my resulting grid parameters.

I need to compute grid parameters for multiple grids, say a low-res and a high-res one:

gp1lo,gp2lo,gp3lo = func(inputslo)
gp1hi,gp2hi,gp3hi = func(inputshi)

Some combination of these will be needed as input to another function. So ideally I could group all gp variables (constants and arrays) into a structure GP, where GP can be the only input to my new function and I can @unpack whatever I need inside it.

Don’t you want simply an array?

julia> f(n) = rand(n), 10, 100;

julia> p = f(5);

julia> p[1]
5-element Array{Float64,1}:
 0.382830386230846
 0.19886498332704194
 0.7040674905637088
 0.5274814696569752
 0.6055869206344655

julia> p[2]
10

julia> p[3]
100

Then pass p as a parameter to the other function.

That could work, but if I wanted to use the 2nd element of that array, for example, I’d have to call p[1][2], instead of being able to point to a more memorable variable.

Something like this?

julia> f(n) = rand(n), 10, 100;

julia> struct Params
          N1
          N2
          N3
       end

julia> p = Params(f(5)...) # the 3 dots here is the "splat operator", that does the magic
Params([0.567716392452982, 0.9718828072619585, 0.2503661667618038, 0.9976792632899436, 0.42025703584303065], 10, 100)

julia> p.N1
5-element Array{Float64,1}:
 0.567716392452982
 0.9718828072619585
 0.2503661667618038
 0.9976792632899436
 0.42025703584303065

julia> p.N2
10


Instead of

gp1lo,gp2lo,gp3lo = func(inputslo)
gp1hi,gp2hi,gp3hi = func(inputshi)

try

helperfunc(x,y) = (gplo=func(x),gphi=func(y))
gparams = helperfunc(inputslo,inputshi)

then you can get stuff by for example gparams.gplo.N1

1 Like

Maybe use an array or tuple of values instead of separate struct fields?

1 Like

Yup, using an array or tuple of values seems like the way to go.

I guess this was a habit carried over from MATLAB, where one could group many already-defined variables into a “structure”: STRUCT.a = a, STRUCT.b = b. I’ll read up on where/when structures are actually the optimal choice.

Many thanks, everybody!

You can still use @unpack with named tuples too I think, which gets you most of what you might want from Parameters.jl

3 Likes

I ended up going with NamedTuples:

gplo = NamedTuple{(:gp1lo,:gp2lo,:gp3lo)}(func(inputslo))
gphi = NamedTuple{(:gp1hi,:gp2hi,:gp3hi)}(func(inputshi))
GP   = merge(gplo,gphi)

making it straightforward to call any variable GP.gp1lo. Tried to find a way to define GP in one line but decided it wasn’t worth the time at the moment.

I don’t know if this solution optimizes performance, but it is easy to understand for me (took me a while to get a grip on this) and it’s nice to not need external packages.

Thanks again for the help.