Storing evaled function inside a struct

Hi everyone!

I’m looking for feedback on the approach of some code I’ve written. Since the code is for work I’ll share a greatly simplified version of what it looks like.

I’d like to hear opinions on the “right way” to store an evaled function in a struct.
I’m not sure where I would need to be careful for performance pitfalls (Does the F<:Function take care of everything? Do I need FunctionWrappers?), or if there are more elegant ways to do this.

mutable struct S{T, F<:Function}
    c::T
    func::F
end

function S(config)
    exprs = get_exprs(config)
    # Running get_exprs takes a long time which is why I 
    # don't want this part in the cost function itself
    fn = quote
        function (s, x)
            $(exprs...)  # calculate a and b
            # I don't know which values will be fixed, which values taken
            # from x and which values calculated from those. So depending
            # on the config it might evaluate a and b, maybe a and c,
            # maybe only b... For example
            a = x[1]
            c = s.c
            b = a^2 + c
            d = x[2]
            return cost(a, b, c, d)
        end
    end
    s = S(config.c, eval(fn))
    return s
end

function (s::S)(x)
    s.func(s, x)
end

s = S(config)
optimize(x -> s(x)) 

Does this code look okay? What would be the fastest/most idiomatic/simply-the-best way to do this? If there are completely different approaches I’d be happy to hear them, too.

Perhaps a closure would suffice:

function S(config)  # store the parameters and return a cost function 
    a,b = get_exprs(config)
    x -> cost(a, b, config.c, x[2])
end

s = S(config)
optimize(x -> s(x))

I guess that would be one option. I like to have a struct to save some config as well as bounds and parameters. This let’s me have everything in one place and enables things like

function (s, x, use_struct)
    if use_struct
        # just use values from the struct, e.g.
        a = s.a
    else
        $(exprs...)  # calculate a and b
    end
    return cost(a, b, s.c, x[2])
end

Edit: this would still have to be evaled. I don’t know which values will be fixed, which values taken from x and which values calculated from those. So depending on the config it might evaluate a and b, maybe a and c, maybe only b
I’ll update to original post to reflect this.

I’d like to hear opinions on the “right way” to store an eval ed function in a struct .

I really have no idea what the “right way” is, but I usually just store the function directly

function S(config)
    exprs = get_exprs(config)
    # Running get_exprs takes a long time which is why I 
    # don't want this part in the cost function itself
    $(exprs...)
    fn = (s,x) -> cost(a, b, s.c, x[2])
    # consider  fn = (s,x) -> cost(copy(a), copy(b), s.c, x[2]) if you fear interferance
    s = S(config.c, fn)
    return s
end

Why is this considered to be problematic?

I’m not sure whether this would be problematic but it won’t work in my case since I don’t have a function per se but only expressions I have to evaluate to get a function. I’m not sure it this will still work as well then.

Why do you need to eval as function constructed from expressions? Why can’t you use higher-order functions or some other mechanism?

Some context on what problem you are trying to solve here would be helpful.

2 Likes

You are right, I had hoped it would be easier without this but I guess I’ll start from the beginning:

I have a bunch of functions and a cost function

f1(a, b) = ...
f2(a, d) = ...
f3(b, c) = ...
cost(a, b, c, d) = f1(a, b) + f2(a, d) + f3(b, c)

with some other equations to calculate the values a, b, c, d:

a = 2d
c = a * b
...

Now the user has the choice of setting some of those values as fixed, some to be in some range, some as free (do with these whatever). Then I want to find the best values for those parameters I can change via optimization.
If I set a as fixed and b to be in some range (to be optimized), I can get both c and d from the equations above, so my function would look like

function (x)
    a = 2  # the const value I want
    d = 1  # == a / 2;  I get this value by evaluating this equation while generating the expressions
    b = x[1]
    c = a * b
    cost(a, b, c, d)
end

If I only set b to be fixed, d can be optimized and the function will look like

function (x)
    b = 5  # the const value I want
    d = x[1]
    a = 2d
    c = a * b
    cost(a, b, c, d)
end

The resolving of these dependencies takes some time, which is why I wish to do this once instead on doing this in the cost function.

I don’t know how to do this just with functions without a big performance penalty to the cost function, that’s why I create the function from expressions and need the eval.

Why don’t you just pass around your function (x)...? No need to eval them, just let the user write them as code.

1 Like

I’m not sure I understand. Until I eval my

fn = quote
    function (s, x) ... end
end

I don’t have a function just a bunch of expressions. Or do you suggest the user writes this function themselves (in which case they’d evaluate it by running it)?

Yes, let the user-config-file be a julia-file and let them write it themselves.

Of course that’s possible but that is basically the entire purpose of the program: not having to sort out the dependencies of all the equations by hand every time you want to fix a different set of parameters (there are a lot more than in this example of course) but let the computer do it :man_shrugging:

Why can’t you write a function compute_dependencies(vars, rules) which fills in empty elements of the array args by applying the rules?

This would involve resolving the dependencies, right?
How would I do this quickly (meaning once per configuration, not once per call of the function which will be called thousands of times during (global) optimization)?

The user would only call this method once. From there, rather than passing in the args they made, they would pass the result of this function, which they presumably save in a variable

Hm… what would be the output of this function?
Just so we are not misunderstanding, not all unknown values can be computed in advance, they might depend on values that are being optimized (second example here). That I have to do while running the cost function.

You can always reactor the cost function to not do the dependency managing…

I don’t think we are understanding each other.
During a cost function call I’ll have to calculate some values I need from the values I’m optimizing, like

function (x)
    b = 5  # the const value I want
    d = x[1]
    a = 2d
    c = a * b
    cost(a, b, c, d)
end

there is no way around this (otherwise how would I know a?). The thing I can avoid is the dependency stuff (which is how I know I need the line a = 2d in there and not maybe d = a / 2).

The solution I came up was to stitch that function together with expressions. If you have a different idea I’d appreciate a short (pseudo) code snippet so I can better understand what you are proposing.

Sure. But you make your life hard. Maybe better to spend your time to teach the users how to use Julia?

1 Like

I am my user :smile:

I will need to run this optimization multiple times with different parameters. I really don’t want to write a specific function resolving the dependencies by hand for each one. That’s what computers are for.

If that doesn’t satisfy you, maybe just take it as a fun exercise or take it as given that this is what needs to be done. In my case it would be more or less practical to do this by hand but it’s easy to imagine a bigger set of equations where it would take an hour.

How are you resolving the dependencies? Name?

In that case you are just asking about a way to resolve from a name to a value? You can just write a function to do just that.