Simple question about macro arguments

Hello,

I would like to use a macro to compute derivative of several fields.
Concretly, in a simplified context, I have the following issue:
for the macro

macro testij(i,j) esc(:(a[$i]=b[$j] )) end

for a and b defined, if I do @macroexpand @testij(1+2,2) I get

:(a[1 + 2] = b[2])

I would like to obtain this :(a[3] = b[2]), how can I do it ?

In my particular case I do operation on several field:

macro compute_∇P(li,lj) esc( 
    quote
        ∂xPx = (P_shared[$li+1,$lj,1] - P_shared[$li-1,$lj,1])*0.5/Δ
        ∂zPx = (P_shared[$li,$lj+1,1] - P_shared[$li,$lj-1,1])*0.5/Δ
        ∂xPz = (P_shared[$li+1,$lj,2] - P_shared[$li-1,$lj,2])*0.5/Δ
        ∂zPz = (P_shared[$li,$lj+1,2] - P_shared[$li,$lj-1,2])*0.5/Δ
    end)
end

For boundary conditions I do

i_ = mod(i-1,1:N)
@compute_∇P(i_,j)

but I would like to write it this way @compute_∇P(mod(i-1,1:N),j) without computing the modulus several time.

Thank you for your help,
Best

If you write

macro compute_∇P(li, lj)
    quote
        let li = $li, lj = $lj
            lip, lim = li+1, li-1
            ljp, lim = lj+1, lj-1
            ∂xPx = (P_shared[lip, lj, 1] - P_shared[lim, lj, 1])*0.5/Δ
            ∂zPx = (P_shared[li, ljp, 1] - P_shared[li, ljm, 1])*0.5/Δ
            ∂xPz = (P_shared[lip, lj, 2] - P_shared[lim, lj, 2])*0.5/Δ
            ∂zPz = (P_shared[li, ljp, 2] - P_shared[li, ljm, 2])*0.5/Δ
        end
    end |> esc
end

then li and lj are evaluated only once:

julia> @macroexpand @compute_∇P(mod(i-1,1:N), j)
quote
    #= REPL[1]:3 =#
    let li = mod(i - 1, 1:N), lj = j
        #= REPL[1]:4 =#
        (lip, lim) = (li + 1, li - 1)
        #= REPL[1]:5 =#
        (ljp, lim) = (lj + 1, lj - 1)
        #= REPL[1]:6 =#
        ∂xPx = ((P_shared[lip, lj, 1] - P_shared[lim, lj, 1]) * 0.5) / Δ
        #= REPL[1]:7 =#
        ∂zPx = ((P_shared[li, ljp, 1] - P_shared[li, ljm, 1]) * 0.5) / Δ
        #= REPL[1]:8 =#
        ∂xPz = ((P_shared[lip, lj, 2] - P_shared[lim, lj, 2]) * 0.5) / Δ
        #= REPL[1]:9 =#
        ∂zPz = ((P_shared[li, ljp, 2] - P_shared[li, ljm, 2]) * 0.5) / Δ
    end
end

EDIT: Alternatively, why not use a function that returns the tuple (∂xPx, ∂zPx, ∂xPz, ∂zPz)?

Macro calls don’t act when function calls do, they transform source code, so they have to act before the code is evaluated. Consequently, their inputs are only a few inlined literals (Int, Float64, String), Symbol, and Expr that are initially parsed from source code. That means the macro actually takes i == :(1+2); it’s not like a function call where 1+2 evaluates beforehand. Instead, you need to generate code that evaluates 1+2 before its result is used for indexing. matthias314’s comment works off similar principles.

In the global scope, it is possible to normally eval-uate Expr interpolated with previously computed values of any type. Evaluation of arbitrary expressions can’t be done within local scopes after full parsing.

Thank you for your help,
It is amazing to have answers so fast !

I wouldn’t know exactly how to do it in an optimized way, this bloc of code is used in two CUDA.jl kernels.
My P field from the global memory is load into the shared memory, then I compute derivatives of P that I need for the next step in the same kernel.
As I don’t have enough space and as I need P each time I need ∇P, I don’t want to save ∇P in the global memory (I am trying to reduce the number of access to the global memory).
I need the value of ∇P in two different kernels and I prefer to not copy-past the code, that why I went for macro.

I am a physicist by training and know almost nothing about good practice for writing code. In fact I don’t even know exactly what I am allowed to write in a kernel. I am slowly learning by doing benchmark for my specific system.

Thank you again for your help !

EDIT: it is in fact not working with let but if I use

macro compute_∇P(li,lj)
    quote
        li=$li; lj=$lj
        lip, lim = li+1, li-1
        ljp, ljm = lj+1, lj-1
        ∂xPx = (P_shared[lip,lj,1] - P_shared[lim,lj,1])*0.5/Δ
        ∂zPx = (P_shared[li,ljp,1] - P_shared[li,ljm,1])*0.5/Δ
        ∂xPz = (P_shared[lip,lj,2] - P_shared[lim,lj,2])*0.5/Δ
        ∂zPz = (P_shared[li,ljp,2] - P_shared[li,ljm,2])*0.5/Δ
    end |> esc
end

it is enough to not compute the modulus multiple times

The issue with this way is that I systematically do two operations while I might need to evaluate only one. For this one I might need to define 4 new variables while I might only need zero or one li=$li; lj=$lj; i=$i; j=$j if I have to do only one modulus.

macro load_ρμPv(li,lj,i,j) esc(
    quote
        v_shared[$li,$lj,1] = v[$i,$j,1]
        P_shared[$li,$lj,1] = P[$i,$j,1]
        v_shared[$li,$lj,2] = v[$i,$j,2]
        P_shared[$li,$lj,2] = P[$i,$j,2]
        μ_shared[$li,$lj] = μ[$i,$j]
        ρ_shared[$li,$lj] = ρ[$i,$j]
    end)
end

concretely I would like to call @load_ρμPv(li,T+2,i,mod(j+1,1:N)) without having to write

jp = mod(j+1,1:N)
ljp = T+2
@load_ρμPv(li,ljp,i,jp)

it is mainly to make it easier to read and more compact.

I forgot to declare the variables before the let. There was also a typo. This one should work:

macro compute_∇P(li, lj)
    quote
        ∂xPx, ∂zPx, ∂xPz, ∂zPz = let li = $li, lj = $lj
            lip, lim = li+1, li-1
            ljp, ljm = lj+1, lj-1
            ∂xPx = (P_shared[lip, lj, 1] - P_shared[lim, lj, 1])*0.5/Δ
            ∂zPx = (P_shared[li, ljp, 1] - P_shared[li, ljm, 1])*0.5/Δ
            ∂xPz = (P_shared[lip, lj, 2] - P_shared[lim, lj, 2])*0.5/Δ
            ∂zPz = (P_shared[li, ljp, 2] - P_shared[li, ljm, 2])*0.5/Δ
            ∂xPx, ∂zPx, ∂xPz, ∂zPz
        end
    end |> esc
end

I’m using let to avoid overwriting existing variables li and lj. This may not be a concern in your case. A different way to achieve this would be to use gensym to create new variable names.

Thank you for your help, it is working well now !