# Efficient way to MapApply for Tensor

I need to Map a function with certain arguments $$f(\alpha,\beta,\gamma,\delta)$$ onto a whole tensor.

My current approach looks like

n = 400;

ATensor = Table[{# + i, # + i + 1, # + i + 2, # + i + 3} & /@ Range[n], {i, 1, n}];

f[#1, #2, #3, #4] & @@@ ATensor[[#]] & /@ Range[n]; // AbsoluteTiming


and I end up getting the desired result within the following computation time on my maschine.

{0.0712512, Null}


I'd like to be more efficient when mapping the function onto the tensor so I tried to speed up this computation. I noticed that the majority of time is taken while iterating through every single row because mapping the function onto one row just takes a small amount of time.

f[#1, #2, #3, #4] & @@@ ATensor[[1]]; // AbsoluteTiming

{0.0001304, Null}


Due to the fact that I have to apply functions to tensors with large dimensions I'd like to know if there is a smarter and more efficient way to map the function onto the whole tensor or speed up the iteration through every single row. At this point I am very thankful for any tips on how to improve the computational time.

UPDATE

• Fixed the mistake of the indexing mentioned by thorimur.

• I implemented the input of thorimur and changed f[#1, #2, #3, #4] & to f with the following speedup (twice as fast)

f @@@ ATensor[[#]] & /@ Range[n]; // AbsoluteTiming

{0.0298548, Null}

• I also tried to use Apply[f, ATensor, {2}] and it give the desired result but it is not faster than my approach but more neat I guess.

Apply[f, ATensor, {2}]; // AbsoluteTiming

{0.0282078, Null}

• I tried using ArrayReduce aswell and ended up being slower than the first to variants

ArrayReduce[Apply[f], ATensor, 3]; // AbsoluteTiming

{0.190799, Null}

• First, I suggest using RepeatedTiming instead of AbsoluteTiming to get more consistent and robust timings. Second, you haven't given us any f – Are you sure that it is the mapping and not the actual f that will be a performance bottleneck in your computation? Jun 11, 2023 at 12:06
• Thanks for the input. Ya my main problem was to efficiently map the function onto the tensor. The function itself is just a numerical integration. Jun 11, 2023 at 13:33

Note that f[#1, #2, #3, #4] & is just f—this substitution by itself saves some time—and that Apply (@@@) has a secret levelspec argument! You want {2}, i.e. Apply[f, ATensor, {2}].
Note also that you've accidentally pseudo-0-indexed ATensor with the {i, 0, n} iterator. This means that you lose a row when writing /@ Range[n], since Range[n] is {1, ... , n}, and list indices are likewise 1-indexed (so you lose the last row, not the first). However, you should never have to map over indices like this (well, almost never); all your mapping should be doable with Map and Apply directly, both of which take levelspec args. You may also find functions like TensorTranspose and ArrayReduce (and, in that context, the operator form Apply[f]) helpful. Hope this helps!
Edit, after seeing the edits to the question: turns out f @@@ ATensor[[#]] & /@ Range[n] is slightly faster than Apply[f, ATensor, {2}]! I'm pretty surprised.
• Thanks for your fast response and your input. You helped me alot. I updated my question and posted the computation time for the adjustments you mentioned. I was not able to implement TensorTranspose right away but I will try this aswell in the near future and compare the results. Jun 11, 2023 at 11:15
• May I ask a further question to this topic? In some cases I have a function just like the already mentioned function $f(\alpha,\beta,\gamma,\delta)$ but this function $g(\alpha,\beta,\gamma,\delta,x)$ now depends on a additional parameter $x$ which "remains". So I'd like to map the function onto ATensor and evaluate the function just with respect to $\alpha,\beta,\gamma$ and $\delta$. Am I still able to get rid of the overhead when using Result[x_] := f[#1, #2, #3, #4, x] & @@@ ATensor[[#]] & /@ Range[n]; for example to achieve a speedup in this case aswell? Jun 12, 2023 at 10:14
• @mathetronaut Hmm, good question! I'm not sure. You might try defining things such that it's actually written g[x][a,b,c,d] instead of g[a,b,c,d,x]. But simply replacing g[x][a,b,c,d] with g[a,b,c,d,x] is slower, by itself; instead, if you structure your code such that g[x][a,b,c,d] is directly what you want it to be, it might be faster, as then you can simply write g[x] @@@ ATensor[[#]] & /@ Range[n] for the mapping part, and no extra "intermediate" rewrites have to happen. I think the goal is to have as few rewrites as possible. Jun 12, 2023 at 18:05