Hi all,
I've been recently running into a presumably frequently occurring problem,
and was wondering what the best way is to solve this in Julia. The problem
is how to dynamically choose amongst a set of functions based on some
constant parameter value (without the use of macros, which usually make my
head hurt). A use case would be the sampler for a probability distribution
for which several algorithms exist, and whose choice depends on the
parameters of this distribution. Given that this distribution is
implemented as an immutable class, one would want to pick the best
algorithm at construction of a class instance rather than evaluating it at
every time the sampler is called.
More specifically, an abstract example is to have some function f(a::BaseA,
x::Number) that chooses among
f1(x::Number) = x + 10
f2(x::Number) = x - 10
based on if some parameter y, used to construct BaseA(y), is positive of
negative. In addition, we would like to add generic vectorization, akin to
abstract BaseA
f{T <: Number}(a::BaseA, x::Vector{T}) = [f(a, xi) for xi in x]
The fastest approach might be
immutable A0 <: BaseA
y::Float64
A0(y::Number) = new(float(y))
end
f(a::A0, x::Number) = a.y >= 0.0 ? f1(x) : f2(x)
f{T <: Number}(a::A0, x::Vector{T}) = a.y >= 0.0 ? [f1(xi) for xi in x] :
[f2(xi) for xi in x]
that is, re-implementing the vectorization, to avoid a.y >= 0.0 to be
called for every vector element. This seems to defeat the purpose of
writing generic methods. A second possibility is to perform the comparison
of y at every iteration by
immutable A1 <: BaseA
y::Float64
A1(y::Number) = new(float(y))
end
f(a::A1, x::Number) = a.y >= 0.0 ? f1(x) : f2(x)
For both implementation I find around the same run-time (vector of size
10^8, no gc):
A0: elapsed time: 0.687105729 seconds (1600000096 bytes allocated)
A1: elapsed time: 0.696713456 seconds (1600000096 bytes allocated)
However, I don't find either of them particularly satisfactory. A third
option would be to choose the function at construction of a:
immutable A2 <: BaseA
f::Function
A2(y::Number) = new(y >= zero(y) ? f1 : f2)
end
f(a::A2, x::Number) = a.f(x)
However, this is slow (elapsed time: 25.70402182 seconds (7999991920 bytes
allocated)), and defeats type inference (returns Array{Any,10^8}). As last
option would be to construct instances of different classes, depending on y:
immutable _A3f1 <: BaseA; end
f(a::_A3f1, x::Number) = f1(x)
immutable _A3f2 <: BaseA; end
f(a::_A3f2, x::Number) = f2(x)
A3(y::Number) = y >= zero(y) ? _A3f1() : _A3f2()
This is (surprisingly) still slower than A0 and A1 (elapsed time:
1.360536247 seconds (1600000096 bytes allocated)), and just shifts the
problem of type inference one level higher.
Are there any better solutions to tackle this, or are macros the only way?
Thanks for your helps,
Jan