Here are some examples illustrating the use of np.einsum() in the implementation of some common tensor or n-dimensional operations.
Inputs
In [197]: vec Out[197]: array([0, 1, 2, 3]) In [198]: A Out[198]: array([[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]]) In [199]: B Out[199]: array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]])
1) matrix multiplication (similar to np.matmul(arr1, arr2) )
In [200]: np.einsum("ij, jk -> ik", A, B) Out[200]: array([[130, 130, 130, 130], [230, 230, 230, 230], [330, 330, 330, 330], [430, 430, 430, 430]])
2) Extract items along the main diagonal (similar to np.diag(arr) )
In [202]: np.einsum("ii -> i", A) Out[202]: array([11, 22, 33, 44])
3) Hadamard product (i.e. the elemental product of two arrays) (similar to arr1 * arr2 )
In [203]: np.einsum("ij, ij -> ij", A, B) Out[203]: array([[ 11, 12, 13, 14], [ 42, 44, 46, 48], [ 93, 96, 99, 102], [164, 168, 172, 176]])
4) Elementary squaring (similar to np.square(arr) or arr ** 2 )
In [210]: np.einsum("ij, ij -> ij", B, B) Out[210]: array([[ 1, 1, 1, 1], [ 4, 4, 4, 4], [ 9, 9, 9, 9], [16, 16, 16, 16]])
5) Tracing (i.e. the sum of the main diagonal elements) (similar to np.trace(arr) )
In [217]: np.einsum("ii -> ", A) Out[217]: 110
6) Matrix transposes (similar to np.transpose(arr) )
In [221]: np.einsum("ij -> ji", A) Out[221]: array([[11, 21, 31, 41], [12, 22, 32, 42], [13, 23, 33, 43], [14, 24, 34, 44]])
7) External product (vectors) (similar to np.outer(vec1, vec2) )
In [255]: np.einsum("i, j -> ij", vec, vec) Out[255]: array([[0, 0, 0, 0], [0, 1, 2, 3], [0, 2, 4, 6], [0, 3, 6, 9]])
8) Domestic product (vectors) (similar to np.inner(vec1, vec2) )
In [256]: np.einsum("i, i -> ", vec, vec) Out[256]: 14
9) The sum along the 0 axis (similar to np.sum(arr, axis=0) )
In [260]: np.einsum("ij -> j", B) Out[260]: array([10, 10, 10, 10])
10) Sum along axis 1 (similar to np.sum(arr, axis=1) )
In [261]: np.einsum("ij -> i", B) Out[261]: array([ 4, 8, 12, 16])
11) Mass matrix multiplication
In [287]: BM = np.stack((A, B), axis=0) In [288]: BM Out[288]: array([[[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]], [[ 1, 1, 1, 1], [ 2, 2, 2, 2], [ 3, 3, 3, 3], [ 4, 4, 4, 4]]]) In [289]: BM.shape Out[289]: (2, 4, 4) # batch matrix multiply using einsum In [292]: BMM = np.einsum("bij, bjk -> bik", BM, BM) In [293]: BMM Out[293]: array([[[1350, 1400, 1450, 1500], [2390, 2480, 2570, 2660], [3430, 3560, 3690, 3820], [4470, 4640, 4810, 4980]], [[ 10, 10, 10, 10], [ 20, 20, 20, 20], [ 30, 30, 30, 30], [ 40, 40, 40, 40]]]) In [294]: BMM.shape Out[294]: (2, 4, 4)
12) summarize along axis 2 (similar to np.sum(arr, axis=2) )
In [330]: np.einsum("ijk -> ij", BM) Out[330]: array([[ 50, 90, 130, 170], [ 4, 8, 12, 16]])
13) sum all the elements in the array (similar to np.sum(arr) )
In [335]: np.einsum("ijk -> ", BM) Out[335]: 480
14) the sum of several axes (i.e. marginalization)
(similar to np.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7)) )
# 8D array In [354]: R = np.random.standard_normal((3,5,4,6,8,2,7,9)) # marginalize out axis 5 (ie "n" here) In [363]: esum = np.einsum("ijklmnop -> n", R) # marginalize out axis 5 (ie sum over rest of the axes) In [364]: nsum = np.sum(R, axis=(0,1,2,3,4,6,7)) In [365]: np.allclose(esum, nsum) Out[365]: True
15) Double Dot Products (similar to np.sum (hadamard-product) cf. 3 )
In [772]: A Out[772]: array([[1, 2, 3], [4, 2, 2], [2, 3, 4]]) In [773]: B Out[773]: array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) In [774]: np.einsum("ij, ij -> ", A, B) Out[774]: 124
Read more here: Einstein-Summation and specifically here: Tensor-notation