I have a function that constructs a NamedTuple out of a vector of numbers subject to some transformation, something like
function vec2tuple(x)
( a = x[1], b = 1 + exp(x[2]), c = 2 + exp(x[3]), d = exp(x[4]) / (1 + exp(x[4]))
end
This function does 2 things. It transforms individual elements of x
an appropriate transformation and then assembles the results into a named tuple with keys a
, b
, c
, and d
.
vec2tuple
runs very fast and doesn’t allocate. But the downside is code duplication, which gets tedious as the size of the tuple increases. Also, vec2tuple is kind of hard to read b/c it doesn’t make explicit the logic that informs the choice of the transformation. Ultimately, every xi
in x
will get transformed based on a known range of values each element of the NamedTuple can take. In the example above, a can take any value on the real line, b must be in (1,Inf), c must be in (2,Inf), and d must be in (0,1).
What I really want is to specify a set of rules in a legible way and then have these transformations be constructed accordingly. Here is an example for how to express these rules, but I’m not wedded to this.
bounds = (a = (-Inf,Inf), b = (1,Inf), c = (2,Inf), d = (0,1) )
I first decided to tackle this using functional programming. I wrote a function that returns anonymous functions to perform the transformation for any kind of open interval:
function get_transform_function(arg)
lb, ub = arg
if lb>=ub
throw(ArgumentError("Upper bound must be strictly greater than lower bound"))
end
if lb==-Inf && ub==Inf
return x -> x
elseif lb!=Inf && ub==Inf
return x -> lb + exp(x)
elseif lb==-Inf && ub!=Inf
return x -> ub-exp(-x)
else
return x -> lb + (ub-lb)*exp(x)/(1+exp(x))
end
end
I then constructed rules for each of a,b, c, and d:
rules = map(x -> get_transform_function(x), bounds)
And wrote transform
to call instead vec2tuple
:
function transform(rules::NamedTuple{Names,<:Tuple}, vec::AbstractVector) where Names
N = length(rules)
return NamedTuple{Names}(ntuple(i -> rules[i](vec[i]), N))
end
Problem is, transform
is about 20x slower than vec2tuple
because it allocates a lot.
It seems like the solution should be in meta-programming. Something as performant as vec2tuple
should be generated once based on the information in bounds
before being called many times.
I tried rewriting get_transform_function
so that it takes an additional argument for the value to be transformed and returns expressions instead of anonymous functions, and then construct a transformation as follows. Note that I’m passing an expression as an argument to get_transform_function
so it gets interpolated as x[i]
, not any specific value.
names=keys(bounds)
args=[get_transform_function(:(x[$i]), v) for (i,v) in enumerate(bounds)]
tup=:($(Expr(:tuple, arguments...)))
transform=:(NamedTuple{$names}( :($(Expr(:tuple, arguments...))) ))
But transform
is an expression that needs to be evaluated for a given value of x
when x
is in scope. This sounds like it should be a macro that returns a vec2tuple(x)
-style function, but I can’t quite figure out how to write this.
And furthermore, what if I don’t know bounds
at compile-time? I can still know it before I need to call vec2tuple
a whole bunch of time, so I don’t mind paying a price to construction this function once during runtime, as long as it’s fast when I call it.
Sorry for the long post and a less-than-specific question, but any ideas are really welcome! Thanks.