So I had quick look at the code of your package and I think you are doing thing just fine
The only instance of metaprogramming is when you define your differentiation functions programmatically:
for N in (2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
samples = Rational{BigInt}.((1-N)//2:(N-1)//2)
A = TaylorMatrix(samples)
Ai = inv(A)
@eval begin
@inline function fc(::Order{$N}, axis, F, I)
$(Expr(:call, :+, _generate_terms(@view(Ai[1, :]))...))
end
@inline function dfdxc(::Order{$N}, axis, F, I, dxi)
$(Expr(:call, :+, _generate_terms(@view(Ai[2, :]))...)) * dxi
end
@inline function dfdx(::Order{$N}, axis, F, I, dxi)
$(Expr(:call, :+, _generate_terms(@views(_shift_and_add(Ai[1, :], Ai[2, :])))...)) * dxi
end
@inline function d2fdx(::Order{$N}, axis, F, I, dxi)
$(Expr(:call, :+, _generate_terms(@views(_shift_and_add(Ai[2, :], Ai[2, :])))...)) * dxi^2
end
end
end
I personally think this is totally reasonable since your package relies on/promises the performance of these methods.
Some considerations:
- One can in principle try to avoid this explicit generation of terms, however I think in this case it would be quite hard to actually get the compiler to do it at all at compile time (need to construct and invert some matrix apparently…). And as said above it is crucial that this does not happen at every call to these methods. That more than justifies using metaprogramming for me in this case.
- There is another design decision here: One could have done this with
@generated function
s. This would look like this:
@generated function fc(::Order{N}, axis, F, I)
samples = Rational{BigInt}.((1-N)//2:(N-1)//2)
A = TaylorMatrix(samples)
Ai = inv(A)
return Expr(:call, :+, _generate_terms(@view(Ai[1, :]))...)
end
In principle this would allow for arbitrarily high orders to be generated. However, I see 2 arguments in favour of your current design: 1) Your approach is more efficient since it reuses the same matrix for all 4 functions of the same order. 2) Your algorithm for generating the coefficients likely becomes impractical at some order so it can make sense to limit the maximum order (a bit like the factorial function for Ints just uses a lookup table).