I have a module that builds high performance kernels, based on user settings. I would like to construct them using settings that are provided by the user that imports the Kernels module. What would be the most logical way to pass the variables that I now set as global constants from the user side to the module, such that the settings are known at the time the macro is parsed? I am new to Julia and probably overlook something obvious, or do something in a non-Julian way. This is a highly simplified minimal working example of my code:
module Kernels
export kernel
# These should be removed.
const do_advection = true
const do_diffusion = false
const do_source = true
macro make_kernel()
ex_rhs = :( 0 )
if do_advection
ex_rhs = :($ex_rhs .+ 1)
end
if do_diffusion
ex_rhs = :($ex_rhs .+ 2)
end
if do_source
ex_rhs = :($ex_rhs .+ 4)
end
ex = :( a .+= $ex_rhs )
println(ex)
return esc(ex)
end
function kernel(a)
@make_kernel
end
end
using .Kernels
# I would like to be able to set the settings here.
a = zeros(10)
kernel(a)
println(a)
Macros are expanded during parsing, so if you want to make them user configurable I’d pass any custom settings you wish to use in front of the expression you want the macro to operate on as a key=val expression.
An alternative would be (if you want the setting to be global) to use a Ref instead of a plain value.
Most commonly just pass the parameters as parameters to the function (do not use macros) for that:
julia> Base.@kwdef struct Parameters
do_diffusion::Bool = true
do_source::Bool = true
end
Parameters
julia> function make_kernel(;p=Parameters())
if p.do_diffusion
println("do diffusion")
end
if p.do_source
println("do source")
end
end
make_kernel (generic function with 1 method)
julia> make_kernel() # default parameters
do diffusion
do source
julia> make_kernel(p=Parameters(do_diffusion=false))
do source
How can I make a single line statement with that solution? Can you show how my example would look like? In the actual use case the statement is inside a triple nested loop. Following your solution, it would end up with three separate loops, and this is why I am using macros.
If what you want is to elimitate the conditional inside an inner loop, you may want to use multiple dispatch. For example:
julia> function my_kernel(;p=Parameters())
for i in 1:2
for j in 1:2
inner_function(Val(p.do_source),Val(p.do_diffusion))
end
end
end
my_kernel (generic function with 2 methods)
julia> inner_function(::Val{true},::Val{true}) = println("do both")
inner_function (generic function with 1 method)
julia> inner_function(::Val{false},::Val{true}) = println("do diffusion")
inner_function (generic function with 2 methods)
julia> inner_function(::Val{true},::Val{false}) = println("do source")
inner_function (generic function with 3 methods)
julia> inner_function(::Val{false},::Val{false}) = println("do nothing")
inner_function (generic function with 4 methods)
julia> Base.@kwdef struct Parameters
do_diffusion::Bool = true
do_source::Bool = true
end
Parameters
julia> my_kernel()
do both
do both
do both
do both
julia> my_kernel(p=Parameters(do_source=false))
do diffusion
do diffusion
do diffusion
do diffusion
julia> my_kernel(p=Parameters(do_source=true,do_diffusion=false))
do source
do source
do source
do source
julia> my_kernel(p=Parameters(do_source=false,do_diffusion=false))
do nothing
do nothing
do nothing
do nothing
This eliminates the conditional inside the loop.
I’m not sure if this is the best solution for your problem, though, probably more information would be needed for actually suggesting something more specific.
(to really get this to be performant (more than just adding a conditional), you would need to somehow pass the values as parameters to the my_kernel function. Probably add a function barrier there).
The actual use case is here: https://github.com/Chiil/MicroHH.jl/blob/main/src/Dynamics.jl#L43. I made a macro-based finite difference stencil generator, but at the moment all processes are standard on. I wanted to make this more sophisticated by letting the user decide which terms are included in the expression, without losing performance. Given that I have four options at the moment, that would mean 2^4 functions to be defined following your solution, which is doable, but at the same time it would be great if that effort could be prevented.
My two cents is that then you can use a macro to create all the inner functions, instead of making that dependent on the user choices. But others may know better if that is a good alternative.