Write an inner nested loop of (essentially) the form
for i1 = 1:N-M+1, i2 = i1+1:N-M+2, ..., iM = I{M-1}+1:N
# do something
end
where M
is a type parameter, so the loop can just be written out. I am aiming for M in the range up to at least 5. I created a macro to generate this loop
"""
`@symm`: symmetrises a loop over a cartesian range. For example
for i1 = a0:a1-2, i2 = i1+1:a1-1, i3 = i2+1:a1
dosomething(i1, i2, i3)
end
may be written as
@symm 3 for i = a0:a1
dosomething(i[1], i[2], i[3])
end
here, `i` is a `CartesianIndex`.
"""
macro symm(N, ex)
if N isa Symbol
N = eval(N)
end
@assert ex.head == :for
@assert length(ex.args) == 2
ex_for = ex.args[1]
ex_body = ex.args[2]
# iteration symbol
i = ex_for.args[1]
# lower and upper bound
a0 = ex_for.args[2].args[1]
a1 = ex_for.args[2].args[2]
# create the for-loop without body
loopstr = "for $(i)1 = ($a0):(($a1)-$(N-1))"
for n = 2:N
loopstr *= ", $i$n = $i$(n-1)+1:(($a1)-$(N-n))"
end
loopstr *= "\n $i = SVector{$N, Int}($(i)1"
for n = 2:N
loopstr *= ", $i$n"
end
loopstr *= ") \n end"
loopex = parse(loopstr)
append!(loopex.args[2].args, ex_body.args)
# return the expression
esc(quote
$loopex
end)
end
It actually seems to be working ok, though whenever I mess around with meta-programming there end up being some lose ends left. So I’ll be grateful about any pointers.
Anyhow, since I call this macro from a function where M
is passed in as a type parameter, I believe that I needed a @generated
function here:
@generated function myfun!(::MyType{N}) where N
quote
# get some upper and lower iteration bounds
a0, a1 = 1, 20 # get these somewhere useful normally
@symm $N for J = a0:a1
# compute with J
end
end
end
But I’ll be thrilled to learn if I’m wrong. Or indeed, if anybody can tell me a better approach altogether. (I probably could just create an iterator which might be only marginally slower, but I’ll admit it just seemed more fun to do it with a macro.)