# Missing control for depth of Inner

Working on a mechanics problem, I stumbled on something peculiar:

Since Inner is a generalisation of Dot, it changes its behaviour depending upon the structure of the input lists.

Inner[f, {a, b}, {x, y}, g]
g[f[a, x], f[b, y]]


Pretty straight forward, right? Tuples drawn from the lists get inserted into f and all resulting expressions are input into g.

Now if we use two nested lists as input, the following happens:

Inner[f, {{a, b}, {c, d}}, {{w, x}, {y, z}}, g]
{{g[f[a, w], f[b, y]], g[f[a, x], f[b, z]]}, {g[f[c, w], f[d, y]], g[f[c, x], f[d, z]]}}


With some formatting applied:

Inner[f, {{a, b}, {c, d}}, {{w, x}, {y, z}}, g]//Column
{g[f[a, w], f[b, y]], g[f[a, x], f[b, z]]}
{g[f[c, w], f[d, y]], g[f[c, x], f[d, z]]}


Whereas I would expect an output like this:

Inner[f, {{a, b}, {c, d}}, {{w, x}, {y, z}}, g]
g[f[{a,b},{w,x}],f[{c,d},{y,z}]]


Is there something I'm missing? Is there a way to have Inner be agnostic about the heads of the expressions inside the lists? I already found the following workaround here:

Inner[f, Unevaluated /@ {{a, b}, {c, d}}, Unevaluated /@ {{w, x}, {y, z}}, g]
g[f[Unevaluated[{a, b}], Unevaluated[{w, x}]], f[Unevaluated[{c, d}], Unevaluated[{y, z}]]]


But, as stated, this seems like a crude workaround. To sum up my main question: Is there a function which generalizes the behaviour I'm looking for? And wouldn't it make sense for Inner to take an optional argument to control the depth on which it operates?

• From the documentation for Inner, "Inner[f,Subscript[list, 1],Subscript[list, 2],g,n] contracts index n of the first tensor with the first index of the second tensor". It also states Inner[f,{{a,b},{c,d}},{x,y},g]->{g[f[a,x],f[b,y]],g[f[c,x],f[d,y]]}, so it is not unexpected behavior. Commented Nov 16, 2014 at 17:33
• Relevant: (9028) (I specifically link to my answer because I addressed level control.) Commented Nov 16, 2014 at 18:07

Maybe MapThread gives the levelspec control you need:

lst = {{{a, b}, {c, d}}, {{w, x}, {y, z}}};

g @@ MapThread[f, lst, 1]  (* or just g @@ MapThread[f, lst] -- thanks: Mr.W *)
(* g[f[{a,b},{w,x}],f[{c,d},{y,z}]] *)

(* g[{f[a,w],f[b,x]},{f[c,y],f[d,z]}] *)


or,

g @@ Thread[f @@ lst]
(* g[f[{a,b},{w,x}],f[{c,d},{y,z}]] *)


or

innerL1F = #4 @@ Thread[#[#2, #3]] &;

innerL1F[f, {{a, b}, {c, d}}, {{w, x}, {y, z}}, g]
(* g[f[{a,b},{w,x}],f[{c,d},{y,z}]] *)

• I don't believe you need the , 1 here. Or am I forgetting something? Commented Nov 16, 2014 at 17:09
• Thanks Mr.W, you are right. I was intending to show the level spec control we get; just posted an example where it happens to be the default levelspec.
– kglr
Commented Nov 16, 2014 at 17:14

This probably doesn't address the full scope of your question but for the particular example you could use MapThread and Apply:

ex1 = {{a, b}, {c, d}};
ex2 = {{w, x}, {y, z}};


g[f[{a, b}, {w, x}], f[{c, d}, {y, z}]]


Or Transpose and Apply:

g @@ f @@@ ({ex1, ex2}\[Transpose])

g[f[{a, b}, {w, x}], f[{c, d}, {y, z}]]


You probably want something that works in higher dimensions but, for your 2D example can hack Dataset with Transpose:

{{{a, b}, {c, d}}, {{w, x}, {y, z}}} // Dataset // Transpose //
Query[Apply@g, Apply@f] // Normal

(* g[f[{a, b}, {w, x}], f[{c, d}, {y, z}]] *)


Maybe Inner for order tensors can be emulated with some combo of arguments to Transpose applied at the right Dataset levels.

Transpose[list,{n1,n2,…}] transposes list so that the k^(th) level in list is the ^(th) level in the result.