Hello everyone,
I need to compute the same sum of scalar functions (constant, linear, Gaussian kernel…)
for a large number of different values. I would like to create a structure that will hold the sum of these functions to speed-up the computations
I have created an abstract type ParamFcn
for these scalar functions. Each function is then a subtype of ParamFcn
. For instance rbf
is the subtype for a Gaussian kernel parameterized by a mean μ
and std σ
.
I have created a subtype of ParamFcn
called AddedParamFcn
that holds a tuple of ParamFcn
to compute the sum of the scalar functions.
However, even for simple computations, this way of proceeding is actually slower than taking the sum of the different functions . See example. I would expect to get some speed-up with this approach since the the functions can be precomputed.
Should I use a Tuple to store the different ParamFcn functions, or can I create an `Array{ParamFcn,1}$ ( I don’t know how to implement this second option) since I know in advance the number of scalar functions.
What is a better way to implement this?
import Base: show, zero
# Type to hold 1D functions
abstract type ParamFcn end
# zero(::T) where {T <: ParamFcn} = T()
struct AddedParamFcn{T <: Tuple} <: ParamFcn
list::T
end
function show(io::IO, Σg::AddedParamFcn)
println(io, "AddedParamFcn:")
for g in Σg.list
println(io, " $g")
end
end
"""
g₁::ParamFcn + g₂::ParamFcn
Add the parameterized functions so that `(g₁ + g₂)(z) = g₁(z) + g₂(z)`.
"""
+(g::ParamFcn, Σg::AddedParamFcn) = AddedParamFcn((g, Σg.list...))
+(Σg::AddedParamFcn, g::ParamFcn) = AddedParamFcn((Σg.list..., g))
function +(Σg₁::AddedParamFcn, Σg₂::AddedParamFcn)
AddedParamFcn((Σg₁..., Σg₂...))
end
+(g::ParamFcn...) = AddedParamFcn(g)
(Σg::AddedParamFcn)(z) = mapreduce(g->g(z),+, Σg.list)
struct constant <:ParamFcn
end
const Cst = constant()
(C::constant)(z::T) where {T<:Real} = 1.0
struct linear <:ParamFcn
end
const Lin = linear()
(L::linear)(z::T) where {T<:Real} = z
# Gaussian Kernel
struct rbf <:ParamFcn
μ::Float64
σ::Float64
end
function show(io::IO, N::rbf)
println(io,"Gaussian kernel with mean $(N.μ) and std $(N.σ)")
end
(N::rbf)(z::T) where {T<:Real} = 1/(N.σ*√(2*π))*exp(-(0.5/N.σ^2)*(z-N.μ)^2)
struct ψ₀ <:ParamFcn
ξ₀::Float64
σ₀::Float64
end
function show(io::IO, F::ψ₀)
println(io,"ψ₀ function with mean $(F.ξ₀) and std $(F.σ₀)")
end
function (F::ψ₀)(z::T) where {T<:Real}
@assert F.σ₀ >= 0.0 "std must be >= 0"
Δ₀ = (1/(F.σ₀*√2))*(z-F.ξ₀)
return 0.5*((z-F.ξ₀)*(1-erf(Δ₀))-F.σ₀*√(2/π)*exp(-Δ₀^2))
end
Speed benchmark
function time_estim()
@btime begin
z= 0.0
for i=1:1000
z= randn()
1.0 + z + rbf(1.0,1.0)(z) + ψ₀(1.0,2.0)(z)
end
end
@btime U = Cst + Lin + rbf(1.0,1.0) + ψ₀(1.0, 2.0)
@btime begin
U = Cst + Lin + rbf(1.0,1.0) + ψ₀(1.0, 2.0);
z= 0
for i=1:1000
z= randn()
U(z)
end
end
end
time_estim()
32.604 μs (0 allocations: 0 bytes)
0.015 ns (0 allocations: 0 bytes)
33.814 μs (0 allocations: 0 bytes)