Creating Parameters.jl struct samples using variable keywords

JPool
Wed, 23 Oct 2024 13:12:27 +0000
General Usage

Hi, I’m trying to make a function that takes in a default struct which holds parameters for my model. I want to be able to make a sample list where some properties are kept constant, and some are sampled using a sampling algorithm. The properties/keywords I would like to change are defined in a vector of strings.

There is, as far as I know no simple way to input a string as key-word argument, so I used the Meta.parse and eval() function to convert a string to an expression, which when evaluated gives me the new object with the sampled parameters. I already felt that this is probably not ‘conventional’, but it worked.

A example can be seen below:

function create_samplelist(default::ParameterContainer, sample_params::Vector{String}, upper_bounds, lower_bounds; n_samples::Integer = 10)
    samples = typeof(default)[]
    # Make sure the lengths of parameters are all the same
    @assert length(sample_params) == length(upper_bounds) == length(lower_bounds)

    A = QuasiMonteCarlo.sample(n_samples, lower_bounds, upper_bounds, QuasiMonteCarlo.LatinHypercubeSample())

    for col in eachcol(A)
        #Build the expression string
        string = "$(typeof(default))(default, "
        for i in eachindex(col)
            string = string*"$(sample_params[i]) = $(col[i]),"
        end
        string = string*")"

        #Parse the string into an expression and add the evaluated expression to the sample list
        expr = Meta.parse(string)
        push!(samples, eval(expr))
    end

    return samples
end

# Define the variant, sample parameters to sample, and their upper&lower bounds
default = GridCell()
sample_params = ["growth_rate", "death_rate"]
upper_bounds = [0.1, 0.01]
lower_bounds = [0.0, 0.0]

# Create a sample list of GridCell to simulate for data fitting.
variant_samples = create_samplelist(default, sample_params, upper_bounds, lower_bounds)

This example works, until I put this function in a Module. Then I realized that eval() evaluates in the global scope, so it was using the default variable in the global scope the whole time instead of the one passed in the function (which is undefined in my Module, giving me an error)

I can always move the function back into the global scope to fix this problem, but I was wondering: Is there a better way, more conventional to do this?

5 posts - 3 participants