import numpy as np
from pprint import pprint
import matplotlib.pyplot as plt
# hadamard product = element-wise   = a*b
# outer product                     = a*b.T
# inner product = dot product       = a.T*b (Transpose here is only for distinction, np.dot() does not automagically Transpose, np.dot() expects proper dimensions for matrix math)

# a*b does a hadamard product
# dot(av,bv) of vectors does a hadamard product
# dot(am,bm) of matrix does matrix multiplication

Hadamard Product

a = np.array([1,1,1]) #this is not a column or row vector
b = np.array([2,2,2]) #this is not a column or row vector
print(f"\n(1)\na and b shape: {a.shape} -->  dot(a,b) = \n{a*b}")
(1)
a and b shape: (3,) -->  dot(a,b) = 
[2 2 2]

Outer Product

# where [[0],  is a 2x1 column vector
#        [0]]
# and [[0,0]] is a 1x2 row vector

ac = np.array([ [1],
                [1],
                [1]]) 
bc = np.array( [[2],
                [2],
                [2]]) 
print(f"\n(2)\nar and bc shape: {ac.shape} {bc.shape} -->  \ndot(ac,bc.T) = \n{np.dot(ac,bc.T)} \ndot(bc,ac.T) = \n{np.dot(bc,ac.T)}")
(2)
ar and bc shape: (3, 1) (3, 1) -->  
dot(ac,bc.T) = 
[[2 2 2]
 [2 2 2]
 [2 2 2]] 
dot(bc,ac.T) = 
[[2 2 2]
 [2 2 2]
 [2 2 2]]

Inner Product

a = np.array([1,1,1]) #this is not a column or row vector
b = np.array([2,2,2]) #this is not a column or row vector
print(f"\n(1)\na and b shape: {a.shape} -->  dot(a,b) = \n{np.dot(a,b)}")
(1)
a and b shape: (3,) -->  dot(a,b) = 
6
# where [[0],  is a 2x1 column vector
#        [0]]
# and [[0,0]] is a 1x2 row vector

ac = np.array([ [1],
                [1],
                [1]]) 
bc = np.array( [[2],
                [2],
                [2]]) 
print(f"\n(2)\nar and bc shape: {ac.shape} {bc.shape} -->  \ndot(ac.T,bc) = \n{np.dot(ac.T,bc)} \ndot(bc.T,ac) = \n{np.dot(bc.T,ac)}")

# Compare this with the Outer Product version above
(2)
ar and bc shape: (3, 1) (3, 1) -->  
dot(ac.T,bc) = 
[[6]] 
dot(bc.T,ac) = 
[[6]]
ar = np.array([[1,1,1 ]]) 
bc = np.array( [[2],
                [2],
                [2]]) 
print(f"\n(3)\nar and bc shape: {ar.shape} {bc.shape} -->  \ndot(ar,bc) = \n{np.dot(ar,bc)} \ndot(bc,ar) = \n{np.dot(bc,ar)}")
(3)
ar and bc shape: (1, 3) (3, 1) -->  
dot(ar,bc) = 
[[6]] 
dot(bc,ar) = 
[[2 2 2]
 [2 2 2]
 [2 2 2]]
ar = np.array([[1,1],
               [1,1]]) 
bc = np.array( [[1, 3],
                [2, 2]
                ]) 
print(f"ar and bc shape: {ar.shape} {bc.shape} -->  \ndot(ar,bc) = \n{np.dot(ar,bc)} \ndot(bc,ar) = \n{np.dot(bc,ar)} \n ar*bc = \n{ar*bc}")
ar and bc shape: (2, 2) (2, 2) -->  
dot(ar,bc) = 
[[3 5]
 [3 5]] 
dot(bc,ar) = 
[[4 4]
 [4 4]] 
 ar*bc = 
[[1 3]
 [2 2]]

Wrong Dimensions

# Wrong Dimensions
am = np.array([[1,1,1,1]]) 
bm = np.array( [[2],
                [2],
                [2]]) 
print(f"ar and bc shape: {am.shape} {bm.shape} -->  dot(a,b) = {np.dot(am,bm)}")
# Inner dimensions must match, ie 4 and 4 or 3 and 3 here.
# Error: ValueError: shapes (1,4) and (3,1) not aligned: 4 (dim 1) != 3 (dim 0)