%matplotlib inline
# Importing standard Qiskit libraries and configuring account
from qiskit import *
from qiskit.visualization import *
import numpy as np

def encoding_x(qc):
    qc.cx(0,1)
    qc.cx(0,2)
    qc.barrier()

def decoding_x(qc):
    qc.cx(0,2)
    qc.cx(0,1)
    qc.barrier()

def random_init(qc,theta,phi,index):
    
    ## Prepare a random single-qubit state
    qc.ry(theta,index)
    qc.rz(phi,index)
    qc.barrier()

def random_init_inv(qc,theta,phi,index):
    
    ## Inverse of the random single-qubit state preparation
    qc.rz(-phi,index)
    qc.ry(-theta,index)
    qc.barrier()

q = QuantumRegister(3)
c = ClassicalRegister(1)
qec = QuantumCircuit(q,c)

# Initialize the first qubit in a random state
theta = np.random.random()*np.pi
phi = np.random.random()*2*np.pi
random_init(qec,theta,phi,0)

# QEC encoding for correcting a bit-flip error
encoding_x(qec)

# QEC decoding for correcting a bit-flip error
decoding_x(qec)

# Correction
qec.ccx(1,2,0)

# Insert inverse of the random initial state preparation
# to check that the QEC has worked.
random_init_inv(qec,theta,phi,0)
qec.measure(q[0],c)
qec.draw(output='mpl')

# Use Aer's qasm_simulator
backend_q = Aer.get_backend('qasm_simulator')

# Execute the circuit on the qasm simulator.
job_qec_ref = execute(qec, backend_q, shots=4096)

# Grab the results from the job.
result_qec_ref = job_qec_ref.result()
plot_histogram(result_qec_ref.get_counts(qec))

# Define bit-flip errors with probability p
def bitflip(qc,qubit,p):
    if np.random.binomial(1,p) == 1:
        qc.x(qubit)

q = QuantumRegister(3)
c = ClassicalRegister(1)
qec = QuantumCircuit(q,c)

# Initialize the first qubit in a random state
theta = np.random.random()*np.pi
phi = np.random.random()*2*np.pi
random_init(qec,theta,phi,0)

# QEC encoding for correcting a bit-flip error
encoding_x(qec)

# Insert error
p=1
bitflip(qec,0,p)
# bitflip(qec,1,p)
# bitflip(qec,2,p)
qec.barrier()

# QEC decoding for correcting a bit-flip error
decoding_x(qec)

# Correction
qec.ccx(1,2,0)

# Insert inverse of the random initial state preparation
# to check that the QEC has worked.
random_init_inv(qec,theta,phi,0)
qec.measure(q[0],c)
qec.draw(output='mpl')

# Execute the circuit on the qasm simulator.
job_qec = execute(qec, backend_q, shots=4096)

# Grab the results from the job.
result_qec = job_qec.result()
plot_histogram(result_qec.get_counts(qec))

q = QuantumRegister(3)
c = ClassicalRegister(1)
qec = QuantumCircuit(q,c)

# Initialize the first qubit in a random state
theta = np.random.random()*np.pi
phi = np.random.random()*2*np.pi
random_init(qec,theta,phi,0)

# QEC encoding for correcting a bit-flip error
encoding_x(qec)

# Insert error
p=1
bitflip(qec,0,p)
bitflip(qec,1,p)
# bitflip(qec,2,p)
qec.barrier()

# QEC decoding for correcting a bit-flip error
decoding_x(qec)

# Correction
qec.ccx(1,2,0)

# Insert inverse of the random initial state preparation
# to check that the QEC has worked.
random_init_inv(qec,theta,phi,0)
qec.measure(q[0],c)
qec.draw(output='mpl')

# Execute the circuit on the qasm simulator.
job_qec = execute(qec, backend_q, shots=4096)

# Grab the results from the job.
result_qec = job_qec.result()
plot_histogram(result_qec.get_counts(qec))

q = QuantumRegister(3)
c = ClassicalRegister(1)
qec = QuantumCircuit(q,c)

# Initialize the first qubit in a random state
theta = np.random.random()*np.pi
phi = np.random.random()*2*np.pi
random_init(qec,theta,phi,0)

# QEC encoding for correcting a bit-flip error
encoding_x(qec)

# Insert error
p=1
bitflip(qec,0,p)
bitflip(qec,1,p)
bitflip(qec,2,p)
qec.barrier()

# QEC decoding for correcting a bit-flip error
decoding_x(qec)

# Correction
qec.ccx(1,2,0)

# Insert inverse of the random initial state preparation
# to check that the QEC has worked.
random_init_inv(qec,theta,phi,0)
qec.measure(q[0],c)
qec.draw(output='mpl')

# Execute the circuit on the qasm simulator.
job_qec = execute(qec, backend_q, shots=4096)

# Grab the results from the job.
result_qec = job_qec.result()
plot_histogram(result_qec.get_counts(qec))

import numpy as np
from numpy import pi
# importing Qiskit
from qiskit import *
from qiskit.visualization import *
from qiskit.tools.monitor import backend_overview, backend_monitor, job_monitor
from qiskit.tools.visualization import plot_gate_map, plot_error_map

qftc = QuantumCircuit(3)
qftc.h(0)
qftc.draw(output='mpl')

qftc.cu1(2*pi/(2**2), 1, 0) # CROT from qubit 1 to qubit 0
qftc.draw(output='mpl')

qftc.cu1(2*pi/(2**3), 2, 0) # CROT from qubit 1 to qubit 0
qftc.draw(output='mpl')

qftc.h(1)
qftc.cu1(2*pi/(2**2), 2, 1) # CROT from qubit 2 to qubit 1
qftc.h(2)
qftc.draw(output='mpl')

# Complete the QFT circuit by reordering qubits (by exchanging qubit 1 and 3)
qftc.swap(0,2)
qftc.draw(output='mpl')

def qft_rotations(circuit, n):
    if n == 0: # Exit function if circuit is empty
        print("The number of qubits must be greater than 0")
        return circuit

    index = 0
    circuit.h(index) # Apply the H-gate to the most significant qubit
    index += 1
    n -= 1
    for qubit in range(n): # Apply the controlled rotations conditioned on n-1 qubits
        # For each less significant qubit, we need to do a
        # smaller-angled controlled rotation: 
        circuit.cu1(2*pi/2**(2+qubit), index + qubit, 0)

qc = QuantumCircuit(4)
qft_rotations(qc,4)
qc.draw(output = 'mpl')

def qft_rotations(circuit, n, start):
    
    if n == 0: # Exit function if circuit is empty
        return circuit
    
    circuit.h(start) # Apply the H-gate to the most significant qubit
    
    if start == n-1:
        return circuit

    for qubit in range(n-1-start): # Apply the controlled rotations conditioned on n-1 qubits
        # For each less significant qubit, we need to do a
        # smaller-angled controlled rotation: 
        circuit.cu1(2*pi/2**(2+qubit), start + 1 + qubit, start)
    start += 1
    circuit.barrier()    
    # Apply QFT recursively
    qft_rotations(circuit, n, start)

qc = QuantumCircuit(5)
qft_rotations(qc,5,0)
qc.draw(output = 'mpl')

