Save user-defined input functions for different simulations

I’m running a bunch of simulations, whose inputs and outputs I would like to save, where the user inputs are both numbers and functions. Currently, I save all of my inputs and outputs using JLD2. In a toy example of solving a heat equation, I set up some initial parameters, allow the user to define a source function, and then save the inputs and the outputs,

N = 10
t_range = (0.0,1.0)
x_range = (0.0,10.0)
temp_source(t) = cos(t)

p = N, t_range, x_range, temp_source
sol = function_that_solves_heat_equation(p)

using JLD2
jldsave("naming_scheme.jld2"; p, sol)

This will save everything as intended and allow reproducible outputs. However, if someone else ran this code, I would like to be able to see how they defined temp_source(t). So in this example, I would like to be able to see cos(t) – probably as a string? (Or, ideally in a way that scales to more complicated functions with multiple inputs)

So far, I’ve only come up with a workaround that requires the user to input the function twice – once as above, and again in a second line, wrapped in a string,

temp_source_str = "cos(t)"

In addition to being clunky, this introduces another potential source of user error that I would like to avoid.

1 Like

This seems to me like a good usecase for a book-keeping macro. IIUC, you basically want to store the function body symbolically, right? So you could write a macro that does something like this:

# struct for storage of information
struct UserFunction{F}
    func::F
    source
end

(uf::UserFunction)(args...) = uf.func(args...)

# this macro call
@userfunc temp_source(t) = cos(t)
# then expands to something like
temp_source = UserFunction(t -> cos(t), :(cos(t)))

just as a rough sketch (am on mobile)

Addendum: I think you could even automate saving of this with JLD2 by overloading some function. You would then just save the expression. When loading you’d then eval the expression to recreate the function object.

I appreciate the insights! Unfortunately, I’m still a Julia amateur, so I can’t figure out how to build that macro myself (or the automated saving to JLD2). If you have any other thoughts or resources that might point me in the right direction that would be great. Thanks!

Just in case, check also this post out using CodeTracking.

1 Like

@rafael.guerra’s suggestion using CodeTracking.jl is likely much easier.

You could simply do:

julia> using CodeTracking
julia> temp_source(t) = cos(t);
julia> code_string(temp_source, (Float64,))
"temp_source(t) = cos(t)"

julia> f = temp_source # also works when you have the function object in some other variable
temp_source (generic function with 1 method)

julia> code_string(f, (Float64,))
"temp_source(t) = cos(t)"

So assuming all these user-defined functions have a known signature, you can get the definitions quite easily :slight_smile: The docs mention some limitation for complicated functions, but this is probably not an issue for you. I’d just try it :slight_smile:

1 Like

Thanks, both. I was having issues with CodeTracking initially (code_string and code_expr returning nothing), but it seems that issue was only appearing in IJulia. Unsure why that is, as other interactive util commands work fine. I was hoping to build the main interface through a notebook so the outputs are easier to play with, but perhaps that won’t be possible.

In any case, CodeTracking solves the main problem!