# Alternative to Inner which allows level specification

Is it possible to restrict Inner to a certain level? According to the documentation, the expected behaviour for Inner is to compute the generalized inner products of tensors at as high a level as possible.

As a simple example, if

f[{a_},{b_}]:={a b}
g[{a_},{b_}]:={a+b}


then for the calls

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


the first throws an error (the dimensions do not line up) and the second returns {{{f[a,b]}}}.

However, I want to restrict Inner to, say, level 2, so that

level2Inner[f, {{{a}, {b}}, {{c}, {d}}}, {{{x}}, {{y}}}, g] === {{a x + b y}, {c x + d y}}
level2Inner[f, {{{a}}}, {{{b}}}, g] === {{a b}}


I only really care about the level 2 behaviour, but it would be nice to know if more general behaviour is possible. Is there an elegant or standard way to do this?

Edit: One special case I am concerned with is the following. Define the function

f[nestedList1_,nestedList2_]:=Flatten /@ Tuples[{nestedList1, nestedList2}]


which just computes all possible concatenations of lists from the collection nestedList1 with lists from the collection nestedList2. Then given two matrices M1 and M2, where the entries of these matrices are lists of lists, I want something like

level2Inner[f, M1, M2, Join]


One can think of the operation f as computing all possible combinations, except the level2Inner restricts such combinations only to "valid pairs", i.e. where the indices in M1 and M2 line up appropriately.

For example, if you have a directed graph on n vertices, and M is the matrix n by n matrix with entry i,j consisting of a given list of paths (where each path is a list of edges) in G connecting vertex i with vertex j, then the i,j entry of level2Inner[f,M,M,Join] is a list of all possible (valid) paths from vertex i to vertex j which are concatenations of two paths originally specified in the matrix M.

• Could you tell us what you are actually trying to accomplish, i.e. what is the original problem you are trying to solve? The mess of nested lists is scary. – MarcoB Jan 8 at 21:16
• From the help: Inner effectively contracts the last index of the first tensor with the first index of the second tensor. And: 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. – Daniel Huber Jan 8 at 21:40
• @DanielHuber Unfortunately, I tested this earlier and it is not equivalent to the level-like behaviour I am trying to establish. – arutar Jan 8 at 22:43

Why don't you strip appropriate List heads first? Like this

f[a_, b_] := a*b;
g[a__] := Plus[a];
level2Inner = Inner[#1, #2[[All, All, 1]], #3[[All, All, 1]], #4] &;
level2Inner[f, {{{a}, {b}}, {{c}, {d}}}, {{{x}}, {{y}}}, g](*=> {{a x+b y},{c x+d y}}*)
level2Inner[f, {{{a}}}, {{{b}}}, g](*=> {{a b}}*)


Note that for your second example to work g should be defined for arbitrary number of arguments.

EDIT: If you don't want to change the definitions of f and g, and also as a generalization for generic level, you can use

LevelInner[f_, m1_, m2_, g_, lvl_] :=
Module[{f1, g1, id},
Inner[f1, Map[id, m1, {lvl}], Map[id, m2, {lvl}], g1] /.
id -> Identity /. f1 -> f/. g1 -> g}];
LevelInner[f, {{{a}, {b}}, {{c}, {d}}}, {{{x}}, {{y}}}, g, 2](*=> {{g[f[{a}, {x}], f[{b}, {y}]]}, {g[f[{c}, {x}], f[{d}, {y}]]}}*)

• The generic solution of restricting the depth of Inner using symbol replacement is a nice idea. Thanks! – arutar Jan 11 at 12:44
• Sorry, I've noticed unbalanced "}" in my definition of LevelInner --- I hope you figured this out. – Roma Lee Jan 11 at 13:04
• No worries, it was an easy fix. The idea is the main point, and it was good. – arutar Jan 11 at 20:16

I believe you want

MapThread[f, {M1, M2}, 2]


where you could also take (just to mention the useful Outer function)

f[M1ij_, M2ij_] := Flatten[Outer[Join, M1ij, M2ij, 1], 1]


• MapThread goes element by element, but doesn't do the "row combinations" - for each i,j the operation f should be called on all possible pairs M1ik, M2kj and then combined using the second function g. – arutar Jan 9 at 19:09
• Ah, I see, I totally misinterpreted what you wanted! By the way, you could adapt the accepted answer above to contracting arbitrary indices together (once you've placed heads at your desired depth in each tensor) by using something like Contract[f_, M1_, M2_, g_, {n1_, n2_}] := Map[Inner[f, M1, #, g, n1] &, M2, {n2 - 1}] in there! This shuffles the indices weirdly, though, such that the resulting order is: [indices of M2 before n2] [indices of M1 before n1] [indices of M1 after n1] [indices of M2 after n2]. You could use an appropriately-called Transpose to change this if you wanted! – thorimur Jan 11 at 19:07
• (Note also that Map[_,_,{0}] is well-defined, and behaves the way you expect it to.) – thorimur Jan 11 at 19:08
• Thanks for your comments! An implementation that I had was perhaps somewhat similar to this idea: level2Inner[f_, M1_, M2_, g_] := Outer[g @@ MapThread[f, {#1, #2}] &, M1, Transpose[M2], 1]. But this is just hard-coded matrix multiplication. I am not sure if this idea works in general (I don't want to try to think about multiplying tensors right now) but perhaps it is plausible that it would. – arutar Jan 11 at 20:14

Maybe like this.

Flatten/@Inner[Times, {{{a}, {b}}, {{c}, {d}}}, {{{x}}, {{y}}}, Plus, 2]

level2Inner[f_, list1_?ListQ, list2_?ListQ, g_] :=
Flatten /@ Inner[f, list1, list2, g, 2];
level2Inner[Times, {{{a}, {b}}, {{c}, {d}}}, {{{x}}, {{y}}}, Plus]
level2Inner[Times, {{{a}}}, {{{b}}}, Plus]


{{a x + b y}, {c x + d y}}

{{a b}}

• This still will reach too deep in the case of deeper nested lists with other functions (only works when it is operating on a matrix where the entries are singleton lists) – arutar Jan 9 at 18:59