Problem with parentheses around quoted expression in generated function

Hello everyone.

I’m trying to use generated functions to write functions that would return the sum of the values of the nearest neighbors of given site in a multidimensional integer periodic lattice.

For example: In a periodic 2D lattice, the sum of the values of the nearest neighbors of the site s would be

lattice[mod1(s[1] - 1, size(lattice, 1)), s[2]] + lattice[mod1(s[1] + 1, size(lattice, 1)), s[2]] + lattice[s[1], mod1(s[2] - 1, size(lattice, 2))] + lattice[s[1], mod1(s[2] + 1, size(lattice, 2))]

My attempt

Reading through the examples provided in the Manual, I came up with the following:

# Sum of nearest neighbors of site $s$ in a periodic integer lattice
function square_lattice_nn_sum_impl(lattice::Type{Array{Int,dims}}, s::Type{NTuple{dims,Int}}) where dims
    # Accumulate final expression
    ex = :()
    # Loop on the dimensions
    for d in 1:dims
        # Indices for both nearest neighbors in the current dimension
        idx_prev_nn = :(mod1(s[$d] - 1, size(lattice, $d)))
        idx_next_nn = :(mod1(s[$d] + 1, size(lattice, $d)))
        # Fill indices before the current dimension
        idx_before = :()
        for k in d-1:-1:1
            idx_before = :(s[$k], $idx_before)
        end
        # Fill indices after the current dimension
        idx_after = :()
        for k in d+1:dims
            idx_after = :($idx_after, s[$k])
        end
        # Accumulate sum
        ex = :($ex + lattice[$idx_before $idx_prev_nn $idx_after] + lattice[$idx_before $idx_next_nn $idx_after])
    end
    return :($ex)
end

# Extract generated function to the body of a regular function
@generated function square_lattice_nn_sum(lattice::Array{Int,dims}, s::NTuple{dims,Int}) where dims
    square_lattice_nn_sum_impl(lattice, s)
end

For the 2D case, my program returns the following expression:

julia> square_lattice_nn_sum_impl(Array{Int,2}, NTuple{2,Int})

:((() + lattice[() mod1(s[1] - 1, size(lattice, 1)) ((), s[2])] + lattice[() mod1(s[1] + 1, size(lattice, 1)) ((), s[2])]) + lattice[(s[1], ()) mod1(s[2] - 1, size(lattice, 2)) ()] + lattice[(s[1], ()) mod1(s[2] + 1, size(lattice, 2)) ()])

Which would be the desired result if it wasn’t for the several misplaced parentheses.

My questions

Is it possible to accumulate quoted expression without parentheses around them?

Is there a better way of achieving this?

Are generated functions even the right tool for this job?

Thanks.

There are other ways but I’d make arrays of expressions, and splat them. Rather than try to recursively build what aren’t really recursive expressions.

julia> function new_short(lattice::Type{Array{Int,dims}}, s::Type{NTuple{dims,Int}}) where dims
           terms = map(1:dims) do d
               idx_prev_nn = :(mod1(s[$d] - 1, size(lattice, $d)))
               idx_next_nn = :(mod1(s[$d] + 1, size(lattice, $d)))

               idx_before = [:(s[$k]) for k in d-1:-1:1]
               
               term = :(lattice[$(idx_before...), $idx_prev_nn] + lattice[$(idx_before...), $idx_next_nn])
           end
           return :(+($(terms...)))
       end;

julia> new_short(Array{Int,2}, NTuple{2,Int})
:((lattice[mod1(s[1] - 1, size(lattice, 1))] + lattice[mod1(s[1] + 1, size(lattice, 1))]) + (lattice[s[1], mod1(s[2] - 1, size(lattice, 2))] + lattice[s[1], mod1(s[2] + 1, size(lattice, 2))]))

julia> new_short(Array{Int,3}, NTuple{3,Int})
:((lattice[mod1(s[1] - 1, size(lattice, 1))] + lattice[mod1(s[1] + 1, size(lattice, 1))]) + (lattice[s[1], mod1(s[2] - 1, size(lattice, 2))] + lattice[s[1], mod1(s[2] + 1, size(lattice, 2))]) + (lattice[s[2], s[1], mod1(s[3] - 1, size(lattice, 3))] + lattice[s[2], s[1], mod1(s[3] + 1, size(lattice, 3))]))

julia> function orig_short(lattice::Type{Array{Int,dims}}, s::Type{NTuple{dims,Int}}) where dims
           ex = :()  # Trimming the original function to the same degree
           for d in 1:dims
               idx_prev_nn = :(mod1(s[$d] - 1, size(lattice, $d)))
               idx_next_nn = :(mod1(s[$d] + 1, size(lattice, $d)))
               idx_before = :()
               for k in d-1:-1:1
                   idx_before = :(s[$k], $idx_before)
               end
               ex = :($ex + lattice[$idx_before $idx_prev_nn] + lattice[$idx_before $idx_next_nn])
           end
           return :($ex)
       end
orig_short (generic function with 1 method)

julia> orig_short(Array{Int,2}, NTuple{2,Int})
:((() + lattice[() mod1(s[1] - 1, size(lattice, 1))] + lattice[() mod1(s[1] + 1, size(lattice, 1))]) + lattice[(s[1], ()) mod1(s[2] - 1, size(lattice, 2))] + lattice[(s[1], ()) mod1(s[2] + 1, size(lattice, 2))])
1 Like

You don’t need a generated function for this. See: Seemingly unnecessary allocation within for loop - #8 by stevengj

2 Likes