5

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?

tjger
  • 51
  • 1
  • 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. – Marius Ladegård Meyer Nov 16 '14 at 17:33
  • Relevant: (9028) (I specifically link to my answer because I addressed level control.) – Mr.Wizard Nov 16 '14 at 18:07

3 Answers3

5

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 @@ MapThread[f, lst, 2]
(* 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}]] *)
kglr
  • 394,356
  • 18
  • 477
  • 896
  • I don't believe you need the , 1 here. Or am I forgetting something? – Mr.Wizard Nov 16 '14 at 17:09
  • 1
    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 Nov 16 '14 at 17:14
4

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 @@ MapThread[f, {ex1, ex2}]
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}]]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
0

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.

alancalvitti
  • 15,143
  • 3
  • 27
  • 92