Creating variables from file

Hi everyone,

I am designing a large model that has a lot of parameters that will be imported from a file.
I can load the file in a dictionary and I would like to define the variables corresponding to each one of these parameter where the name of the variable would be the key and the assigned value the vale.

For example, this work in the global scope:

d = Dict("a" => 1, "b" => 2)

for param in d
    @eval $(Symbol(param[1])) = @eval $(param[2])
end

This is convenient as I can then do

x = a + b

instead of

x = d["a"] + d["b"]

This is convenient because it makes the code more readable. Unfortunately, I think that this does only work in the global scope. Is there a way to do it inside a function?

Thanks for your help!

Do you need a dictionary? You could directly load the variables from the file:

function readparams()
    include("params.jl")
    a + b
end

with params.jl:

a = 1
b = 2
1 Like

Dynamically creating variable names is not considered good programming practice (in any language, not just Julia). It will make code *less * readable, not more.

If you don’t know the symbol names beforehand, you should use the dictionary. If you do know them, just write

a = d["a"] 
etc. 

explicitly.

3 Likes

My take on this (if the set of parameters is known beforehand) would be to convert the dict to a structure. Then, with Parameters, you can “unpack” the variables needed for specific functions to clean up the code:

julia> using Parameters

julia> @with_kw struct Params
           a::Int
           b::Int
       end
Params

julia> d = Dict("a" => 1, "b" => 2)
Dict{String, Int64} with 2 entries:
  "b" => 2
  "a" => 1

julia> pars = Params(a = d["a"], b = d["b"])
Params
  a: Int64 1
  b: Int64 2


julia> pars.a + pars.b
3

julia> @unpack a, b = pars
Params
  a: Int64 1
  b: Int64 2


julia> a + b
3


This package can be useful: https://github.com/Roger-luo/Configurations.jl

2 Likes

Or, similarly,
mauro3/Parameters.jl: Types with default field values, keyword constructors and (un-)pack macros (github.com)

Great thank for all your responses! It is really helpful :slight_smile:

May I ask how do you load a dictionary from a text file?

This will depend on your application and which file you are reading from. Here an example if you have an Excel file with one sheet name “DataInfo”

myDataInfoDF = DataFrame(XLSX.readtable(aExcelFileName, "DataInfo")...)
myParameters = Dict()
for myRow in eachrow(myDataInfoDF)
    ... your code ...
    # example:
    # myParameters[myRow["Name"]] = myRow["Value"]
end
1 Like

Note that this does work but it’s a bit subtle why it works. In particular, the include does not create local variables in the body of readparams, rather it creates global variables called a and b which are then used in the computation of a + b at the end of the function. If you evaluate the code, you’ll see that a and b are globals afterwards:

julia> function readparams()
           include("params.jl")
           a + b
       end
readparams (generic function with 1 method)

julia> readparams()
3

julia> a
1

julia> b
2

In Julia you cannot (intentionally) do include or eval into a local scope. If you want something like that you can instead generate a function definition based on the code, evaluate that definition and then call it (as many times as you want), like this (in a fresh REPL session so that a and b aren’t defined):

julia> @eval function useparams()
           $(Meta.parseall(read("params.jl", String)).args...)
           a + b
       end
useparams (generic function with 1 method)

julia> useparams()
3

julia> a
ERROR: UndefVarError: a not defined

julia> b
ERROR: UndefVarError: b not defined

What’s the difference? The performance is quite different for one:

julia> @time readparams()
  0.000571 seconds (123 allocations: 6.875 KiB)
3

julia> @time useparams()
  0.000001 seconds
3

This is because readparams opens, parses and evaluates the contents of params.jl every time you call it and then evaluates a + b based on the new global values of a and b. This is quite slow. On the other hand useparams was generated with specific values of a and b and then when you call it, that fixed code is evaluated and since a and b are in the function body all it does is return the constant value 3:

julia> @code_llvm useparams()
;  @ REPL[5]:1 within `useparams`
define i64 @julia_useparams_274() #0 {
top:
  ret i64 3
}

Which do you want? It depends. Which behavior to you need? And do you care about the usage of the values you read from the file being fast or not? If you do want to use them and have it be fast, then you need to take a staged approach where you read the values/code from a file, generate code from that, compile that code and then call it as many times as you need to.

5 Likes

Thanks for the facts - on this forum You’ll Never Walk Alone.

2 Likes