import numpy as np 
import matplotlib.pyplot as plt 

def error_rate_3bit(p):
    
    return p**3 + 3*p**2*(1-p)

p_vals = np.linspace(0, 1, num=100)

plt.plot(p_vals, 1 - error_rate_3bit(p_vals), label='3bit no error')
# plt.plot(p_vals, error_rate_3bit(p_vals), label='3bit error')
plt.plot(p_vals, 1 - p_vals, label='1bit no error')
# plt.plot(p_vals, p_vals, label='1bit error')
plt.xlabel("single bit error p")
plt.legend()

from qiskit import * 

def bit_flip_correction(num_qubits):
    
    circ = QuantumCircuit(num_qubits, num_qubits)
    circ.h(0)
    for i in range(num_qubits - 1):
        circ.cx(i, i+1)
    return circ

circuit = bit_flip_correction(3)
circuit.draw()



backend = Aer.get_backend('statevector_simulator')
job = execute(circuit, backend)
results = job.result()
psi = results.get_statevector(circuit)
print(psi)

def bit_flip_correction_noisy(num_qubits, p):
        
    apply_error = np.random.binomial(1, p)
    circ = QuantumCircuit(num_qubits, num_qubits)
    circ.h(0)
    for i in range(num_qubits - 1):
        circ.cx(i, i+1) 
    if apply_error == 1:
        circ.x(0)
    return circ

circuit = bit_flip_correction_noisy(3, 0.25)
circuit.draw()

def phase_flip_circuit(num_qubits):
    
    circ = QuantumCircuit(num_qubits, num_qubits)
    for i in range(num_qubits - 1):
        circ.cx(i, i+1)
    circ.barrier()
    for i in range(num_qubits):
        circ.h(i)
    return circ

circ = phase_flip_circuit(3)
circ.draw()
    

import numpy as np 

Cx = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
I = np.array([[1, 0], [0, 1]])
M = np.kron(Cx, I)

print(M)

W = np.array([[1, 0, 0, 0, 0, 0, 0, 0], 
              [0, 1, 0, 0, 0, 0, 0, 0], 
              [0, 0, 1, 0, 0, 0, 0, 0], 
              [0, 0, 0, 1, 0, 0, 0, 0], 
              [0, 0, 0, 0, 0, 1, 0, 0], 
              [0, 0, 0, 0, 1, 0, 0, 0], 
              [0, 0, 0, 0, 0, 0, 0, 1], 
              [0, 0, 0, 0, 0, 0, 1, 0]])

print(W)

U = np.matmul(W, M)
print(U)

psi = np.array([0.3, 0.9])
zero = np.array([1.0, 0.0])
v = np.kron(psi, zero)
v = np.kron(v, zero)

def print_state(v):
    for i in range(len(v)):
        print(f"{bin(i)}: " + str(v[i]))
        
print_state(v)

v_final = np.matmul(U, v)
print_state(v_final)

from qiskit import * 

n = 5

qc = QuantumCircuit(n, n)

# initialize an equal superposition on the first qubit 
qc.h(0)

# entangle the first three qubits
qc.cx(0, 1)
qc.cx(0, 2)

# apply noise/error 
qc.barrier()
idx = np.random.randint(0, 3)
qc.x(idx)
qc.barrier()

# prepare syndrome qubits 
qc.cx(0, 3)
qc.cx(1, 3)
qc.cx(0, 4)
qc.cx(2, 4)

# add measurement to the syndrome qubits 
qc.measure(3, 3)
qc.measure(4, 4)

qc.draw()


