Function Repository Resource:

ArrayContractThread

Source Notebook

General contraction of levels of the outer product of arrays

Contributed by: Jose Martin-Garcia

ResourceFunction["ArrayContractThread"][f,{a1,a2,,an},{{l1,l2,,ln}}]

contracts level l1 of array a1, level l2 of array a2, in Outer[f,a1,,an], using head Plus.

ResourceFunction["ArrayContractThread"][f,arrays,{ctr1,ctr2,}]

performs several contractions.

ResourceFunction["ArrayContractThread"][f,arrays,ctrs,g]

performs contractions ctrs using head g.

ResourceFunction["ArrayContractThread"][f,{a1,,an},ctrs,g,{d1,,dn}]

performs contractions assuming that only the first di levels of array ai are array levels.

Details and Options

Each contraction ctrk in the contraction list must be a list of non-negative integers {lk1,,lkn}. A positive lki indicates the level of array ai to be contracted during contraction ctrk . A zero lki indicates that array ai does not participate in contraction ctrk.
For each contraction ctrk, all levels lki must have the same dimension. That is, Dimensions[ai][[lki]] must coincide for all i for each k.
Each array ai in ResourceFunction["ArrayContractThread"][f,{a1,,an},] must be rectangular up to the deepest level involved in the contractions.
The effective depth di in ResourceFunction["ArrayContractThread"][f,{a1,,an},ctrs,g,{d1,,dn}] can be used to indicate that array ai should be treated as if it had only di levels. Therefore di must be an integer between 0 and the depth of ai, and all contraction levels lki must be between 0 and di. Following the precedent of Outer, by default di is chosen to be the depth of the array ai.
ResourceFunction["ArrayContractThread"] cannot contract levels of the same array. Use the resource function ArrayContract for that.

Examples

Basic Examples (2) 

Take two arrays:

In[1]:=
a = Array[OperatorApplied[Subscript, 3][x], {2, 2}]
Out[1]=
In[2]:=
b = Array[OperatorApplied[Subscript, 3][y], {2, 2}]
Out[2]=

Contract the first levels of the arrays:

In[3]:=
ResourceFunction["ArrayContractThread"][f, {a, b}, {{1, 1}}]
Out[3]=

Contract level 2 of the first array and level 1 of the second:

In[4]:=
ResourceFunction["ArrayContractThread"][f, {a, b}, {{2, 1}}, g]
Out[4]=

Take three arrays:

In[5]:=
a = Array[OperatorApplied[Subscript, 3][x], {2, 2}]
Out[5]=
In[6]:=
b = Array[OperatorApplied[Subscript, 3][y], {2, 2}]
Out[6]=
In[7]:=
c = Array[OperatorApplied[Subscript, 4][z], {2, 2, 2}]
Out[7]=

Perform two simultaneous contractions:

In[8]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b, c}, {{2, 1, 3}, {1, 2, 1}}]
Out[8]=

Use head g for the same contractions:

In[9]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b, c}, {{2, 1, 3}, {1, 2, 1}}, g]
Out[9]=

Scope (6) 

Use arrays of any depth and dimension:

In[10]:=
a = Array[Subscript[x, ##] &, {2, 3}];
b = Array[Subscript[y, ##] &, {3, 2, 4}];
c = Array[Subscript[z, ##] &, {1, 2, 4, 3}];
In[11]:=
ResourceFunction["ArrayContractThread"][
  f, {a, b, c}, {{2, 1, 4}}] // Dimensions
Out[11]=

Perform any number of contractions simultaneously:

In[12]:=
a = Array[Subscript[x, ##] &, {2, 3}];
b = Array[Subscript[y, ##] &, {3, 2, 4}];
c = Array[Subscript[z, ##] &, {1, 2, 4, 3}];
In[13]:=
ResourceFunction["ArrayContractThread"][
  f, {a, b, c}, {{1, 2, 2}}] // Dimensions
Out[13]=
In[14]:=
ResourceFunction["ArrayContractThread"][
  f, {a, b, c}, {{1, 2, 2}, {2, 1, 4}}] // Dimensions
Out[14]=
In[15]:=
ResourceFunction["ArrayContractThread"][
  f, {a, b, c}, {{1, 2, 2}, {2, 1, 4}, {0, 3, 3}}] // Dimensions
Out[15]=

Use any head for contractions:

In[16]:=
a = Array[Subscript[x, ##] &, {2, 3}];
b = Array[Subscript[y, ##] &, {3, 2, 1}];
In[17]:=
ResourceFunction["ArrayContractThread"][f, {a, b}, {{1, 2}, {2, 1}},
  g]
Out[17]=

Specify the effective levels of the arrays:

In[18]:=
a = Array[Subscript[x, ##] &, {2, 2, 2}];
b = Array[Subscript[y, ##] &, {2, 2, 2}];
In[19]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b}, {{1, 2}, {2, 1}}, g, {3, 3}]
Out[19]=
In[20]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b}, {{1, 2}, {2, 1}}, g, {2, 2}]
Out[20]=
In[21]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b}, {{1, 2}, {2, 1}}, g, {2, 3}]
Out[21]=
In[22]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b}, {{1, 2}, {2, 1}}, g, {3, 2}]
Out[22]=

Arrays do not need to be rectangular beyond the contraction levels:

In[23]:=
ResourceFunction[
 "ArrayContractThread"][f, {{{1}, {{2}}}, {{{3}}, {4}}}, {{1, 1}}, g]
Out[23]=
In[24]:=
ResourceFunction[
 "ArrayContractThread"][f, {{{1}, {{2}}}, {{{3}}, {4}}}, {{1, 1}}, g,
  1]
Out[24]=

Effective depths can be zero, and then the whole array is treated as a scalar:

In[25]:=
a = Array[Subscript[x, ##] &, {2}];
b = Array[Subscript[y, ##] &, {2}];
In[26]:=
ResourceFunction[
 "ArrayContractThread"][f, {a, b, a}, {{1, 0, 1}}, g, {1, 0, 1}]
Out[26]=

Properties and Relations (8) 

Inner is a particular case of ArrayContractThread:

In[27]:=
a = Array[Subscript[x, ##] &, {2, 2}];
b = Array[Subscript[y, ##] &, {2, 2}];
In[28]:=
Inner[f, a, b, g] === ResourceFunction["ArrayContractThread"][f, {a, b}, {{2, 1}}, g]
Out[28]=
In[29]:=
Inner[f, a, b, g, 1] === ResourceFunction["ArrayContractThread"][f, {a, b}, {{1, 1}}, g]
Out[29]=
In[30]:=
Inner[f, a, Transpose[b], g] === ResourceFunction["ArrayContractThread"][f, {a, b}, {{2, 2}}, g]
Out[30]=
In[31]:=
Inner[f, a, Transpose[b], g, 1] === ResourceFunction["ArrayContractThread"][f, {a, b}, {{1, 2}}, g]
Out[31]=

Dot is a particular case of ArrayContractThread:

In[32]:=
a = Array[Subscript[x, ##] &, {2, 2}];
b = Array[Subscript[y, ##] &, {2, 2}];
c = Array[Subscript[z, ##] &, {2, 2}];
In[33]:=
Dot[a, b] === ResourceFunction["ArrayContractThread"][Times, {a, b}, {{2, 1}}]
Out[33]=
In[34]:=
Expand@Dot[a, b, c] === ResourceFunction["ArrayContractThread"][
  Times, {a, b, c}, {{2, 1, 0}, {0, 2, 1}}]
Out[34]=

Outer is a particular case of ArrayContractThread:

In[35]:=
a = Array[Subscript[x, ##] &, {2, 2}];
b = Array[Subscript[y, ##] &, {2, 2}];
c = Array[Subscript[z, ##] &, {2, 2}];
In[36]:=
Outer[f, a, b, c] === ResourceFunction["ArrayContractThread"][f, {a, b, c}]
Out[36]=
In[37]:=
Outer[f, a, b, c, 1] === ResourceFunction["ArrayContractThread"][f, {a, b, c}, {}, foo, 1]
Out[37]=
In[38]:=
Outer[f, a, b, c, 1, 2, 1] === ResourceFunction["ArrayContractThread"][f, {a, b, c}, {}, foo, {1, 2, 1}]
Out[38]=

ArrayContractThread can handle scalar factors, but Outer cannot:

In[39]:=
ResourceFunction["ArrayContractThread"][f, {a, 3, b}]
Out[39]=
In[40]:=
Outer[f, a, 3, b]
Out[40]=

MapThread is a particular case of ArrayContractThread:

In[41]:=
a = Array[Subscript[x, ##] &, {2, 2}];
b = Array[Subscript[y, ##] &, {2, 2}];
c = Array[Subscript[z, ##] &, {2, 2}];
In[42]:=
MapThread[f, {a, b, c}] === ResourceFunction["ArrayContractThread"][f, {a, b, c}, {{1, 1, 1}}, List, 1]
Out[42]=
In[43]:=
MapThread[f, {a, b, c}, 2] === ResourceFunction["ArrayContractThread"][
  f, {a, b, c}, {{1, 1, 1}, {2, 2, 2}}, List, 2]
Out[43]=

Thread is a particular case of ArrayContractThread:

In[44]:=
a = Array[Subscript[x, ##] &, {2, 2}];
b = Array[Subscript[y, ##] &, {2, 2}];
In[45]:=
Thread[f[a, 7, b]] === ResourceFunction["ArrayContractThread"][f, {a, 7, b}, {{1, 0, 1}}, List, {1, 0, 1}]
Out[45]=

TensorContract of a TensorProduct expression is a particular case of ArrayContractThread if each contraction involves at most one level of each array:

In[46]:=
a = Array[Subscript[x, ##] &, {2, 2}];
b = Array[Subscript[y, ##] &, {2, 2}];
c = Array[Subscript[z, ##] &, {2, 2}];
In[47]:=
TensorContract[TensorProduct[a, b, c], {{2, 4}, {1, 5}}] === ResourceFunction["ArrayContractThread"][
  Times, {a, b, c}, {{2, 2, 0}, {1, 0, 1}}, Plus]
Out[47]=

The special case ArrayContractThread[f,{expr},{{n}},List,n] is equivalent to Map[f,expr,{n}]:

In[48]:=
a = Array[Subscript[x, ##] &, {2, 2, 2}];
In[49]:=
Map[f, a] === ResourceFunction["ArrayContractThread"][f, {a}, {{1}}, List, 1]
Out[49]=
In[50]:=
Map[f, a, {2}] === ResourceFunction["ArrayContractThread"][f, {a}, {{2}}, List, 2]
Out[50]=
In[51]:=
Map[f, a, {3}] === ResourceFunction["ArrayContractThread"][f, {a}, {{3}}, List, 3]
Out[51]=

The special case ArrayContractThread[Identity,{expr},{{n}},g,n] is equivalent to Apply[g,expr,{n-1}]:

In[52]:=
a = Array[Subscript[x, ##] &, {2, 2, 2}];
In[53]:=
Apply[g, a] === ResourceFunction["ArrayContractThread"][Identity, {a}, {{1}}, g, 1]
Out[53]=
In[54]:=
Apply[g, a, {1}] === ResourceFunction["ArrayContractThread"][Identity, {a}, {{2}}, g, 2]
Out[54]=
In[55]:=
Apply[g, a, {2}] === ResourceFunction["ArrayContractThread"][Identity, {a}, {{3}}, g, 3]
Out[55]=

Possible Issues (2) 

ArrayContractThread effectively normalizes sparse and structured arrays in input:

In[56]:=
ResourceFunction[
 "ArrayContractThread"][Times, {SparseArray[{1, 2}], SparseArray[{3, 4}]}, {}]
Out[56]=

Compare to the equivalent computation with Outer:

In[57]:=
Outer[Times, SparseArray[{1, 2}], SparseArray[{3, 4}]]
Out[57]=
In[58]:=
% // Normal
Out[58]=

Publisher

Jose Martin-Garcia

Version History

  • 1.0.0 – 05 August 2020

Related Resources

Author Notes

• This function is rather slow, and normalizes everything. It is essentially a prototype to explore the generality of the possible concept.

• This function could still be generalized as follows: 1) the fourth argument could take a list of heads, one per contraction (this will be implemented in 12.2)—right now all contractions in the third argument use the same head in the fourth argument; and 2) like Tr, it should be possible to contract to the lowest dimension, instead of requiring coinciding dimensions.

License Information