Use of `np.einsum()`
np.einsum() is a powerful function in NumPy that performs Einstein summation, which allows for flexible manipulation of multi-dimensional arrays (tensors) using summation notation. It can handle various operations like matrix multiplication, element-wise operations, and tensor contractions in a very efficient way.
Syntax:
np.einsum(subscripts, *operands, **kwargs)
-  
subscripts: A string representing the Einstein summation convention. -  
*operands: The arrays (tensors) on which the operation is performed. -  
**kwargs: Optional arguments likeoptimize, which can be used to improve performance. 
Einstein Summation Convention
The Einstein summation convention is a notational shorthand where repeated indices are implicitly summed over. For example:
-  
ij,jk->ikdenotes a matrix multiplication between two 2D arrays. -  
ii->isums the diagonal elements of a matrix. 
Common Examples
-  
Matrix Multiplication (
np.dotornp.matmul)import numpy as np A = np.array([[1, 2], [3, 4]]) B = np.array([[5, 6], [7, 8]]) # Matrix multiplication result = np.einsum('ij,jk->ik', A, B) print(result)Explanation:
-  
ij: Refers to the indices of the matrixA. -  
jk: Refers to the indices of the matrixB. -  
ik: The result is the matrix multiplication ofAandB. 
 -  
 -  
Sum over an axis (similar to
np.sum)input_array = np.array([[1, 2], [3, 4]]) # Sum over all elements (similar to np.sum) total_sum = np.einsum('ij->', input_array) print(total_sum)Explanation:
-  
ij->: This notation sums over all indices of the matrixinput_array, returning the total sum. 
 -  
 -  
Trace of a Matrix (sum of diagonal elements)
input_array = np.array([[1, 2], [3, 4]]) # Trace (sum of diagonal elements) trace = np.einsum('ii->', D) print(trace)Explanation:
-  
ii->: This notation picks the diagonal elements of the matrixinput_arrayand sums them. 
 -  
 -  
Element-wise multiplication
first_array = np.array([[1, 2], [3, 4]]) second_array = np.array([[5, 6], [7, 8]]) # Element-wise multiplication element_wise = np.einsum('ij,ij->ij', first_array, second_array) print(element_wise)Explanation:
-  
ij,ij->ij: The indicesijare the same for both arraysfirst_arrayandsecond_array, resulting in element-wise multiplication. 
 -  
 -  Dot product of vectors (
np.dot)x = np.array([1, 2, 3]) y = np.array([4, 5, 6]) # Dot product dot_product = np.einsum('i,i->', x, y) print(dot_product) -  
Tensor contraction (generalized summation over axes)
first_array = np.random.rand(3, 3, 3) second_array = np.random.rand(3, 3) # Contract tensor G and matrix H tensor_contraction = np.einsum('ijk,jl->ikl', first_array, second_array) print(tensor_contraction.shape)Explanation:
-  
ijk,jl->ikl: Summation is performed over the common axisj, resulting in a contraction of the tensor.Advantages of
np.einsum 
 -  
 
- Flexibility: You can perform many types of operations in a single function call.
 -  Efficiency: It can be faster than separate functions like 
np.dot,np.sum, etc., especially when you have complex operations to perform.