a = np.array([1., 2., 3.])  # 1. ensures data type is float
b = np.array([4.j, 5.j, 6.j])  # 1j = complex imaginary unit
 
print(a * b)  # use broadcasting: array as result, not a scalar product
print((a * b).sum(), np.sum(a * b))  # object functions should be faster
print(b @ a)  # no conjugation
print(np.dot(b, a))  # no conjugation, better style
print(np.vdot(b, a))  # conjugation of first argument
print(np.dot(b.conj(), a))  # manual conjugation; b.conj() makes no copy
print((b.conj() * a).sum(), np.sum(np.conjugate(b) * a))  # last makes a copy
print(np.tensordot(b, a, axes=1))  # same as dot
print(np.tensordot(b.conj(), a, axes=1))  # same as vdot
 
# Look up np.einsum
print(np.einsum("i,i", b, a))  # np.dot(b, a)
print(np.einsum("i,i", b.conj(), a))  # np.vdot(b, a)
 
# Make them 2D ndarrays; caution on type and shape of output
A = a.reshape(1, 3)
print(A)  # view, no copy
 
B = b.reshape(1, 3)
print(B)
 
print(A @ b)
# print(A @ B)  # mismatch
print(B @ A.T)  # real scalar product of b and a
print(B.conj() @ A.T)  # complex scalar product of b and a
print(A @ b, A @ b.conj())