import numpy as np

n = 4
dimension = 2**n
v = np.zeros(dimension, dtype=np.complex128)
print(np.shape(v))

real = np.random.rand(dimension)
imag = np.random.rand(dimension)
v = real + 1j*imag

# calculate norm of v
def norm(v):
    norm = np.sqrt(np.dot(np.conjugate(v), v))
    return norm
print(norm(v))

v_normalized = v/norm(v)
print(v)
print(v_normalized)
print(norm(v))
print(norm(v_normalized))

def print_probabilities(v):
    sum = 0
    for i in range(len(v)):
        p = abs(np.conjugate(v[i])*v[i])
        sum = sum + p
        print("state: {0:04b}".format(i) + ", p = {0:.4f}".format(p))
    print("total probability = {0:.4f}".format(sum))

print_probabilities(v_normalized)

import matplotlib.pyplot as plt

states = []
for i in range(len(v)):
    s = "{0:04b}".format(i)
    states.append(s)

prob = list(np.real(np.conjugate(v_normalized)*v_normalized))
    
plt.bar(states, v)
plt.xticks(rotation=45)

v = np.zeros(dimension, dtype=np.complex128)
v[0] = 1/np.sqrt(2)
v[-1] = 1/np.sqrt(2)

import matplotlib.pyplot as plt

v_normalized = v
states = []
for i in range(len(v)):
    s = "{0:04b}".format(i)
    states.append(s)

prob = list(np.real(np.conjugate(v_normalized)*v_normalized))
    
plt.bar(states, prob)
plt.xticks(rotation=45)

def tensor_product(u, v):
    prod = np.zeros(len(u)*len(v))
    for i in range(len(u)):
        prod[i*len(v):(i+1)*len(v)] = u[i]*v
    return prod

u = np.array([1, -1, 2])
v = np.array([0.6, 0.7, 0.8])
print(tensor_product(u, v))

def tensor_product(A, B):
    m = np.shape(A)[0]
    n = np.shape(A)[1]
    r = np.shape(B)[0]
    p = np.shape(B)[1]
    prod = np.zeros((m*r, n*p))
    for i in range(m):
        for j in range(n):
            prod[i*r:(i+1)*r, j*p:(j+1)*p] = A[i, j]*B
    return prod

X = np.array([[0, 1], [1, 0]])
Z = np.array([[1, 0], [0, -1]])
print(tensor_product(X, Z))

import numpy as np
def print_pauli(A):
    c1 = (A[0, 1] + A[1, 0])/2
    c2 = (A[0, 1] - A[1, 0])/(2j)
    c3 = (A[0, 0] + A[1, 1])/2
    c4 = (A[0, 0] - A[1, 1])/2
    print(f"{c1}X + {c2}Y + {c3}Z + {c4}I")
    
M = np.array([[1, 1], [-1, 3]])
print_pauli(M)

