# useful additional packages 
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
from math import pi
import sys

# importing the QISKit
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import available_backends, execute, register, get_backend

# importing API token to access remote backends
try:
    sys.path.append("../../") # go to parent dir
    import Qconfig
    qx_config = {
        "APItoken": Qconfig.APItoken,
        "url": Qconfig.config['url']}
except:
    qx_config = {
        "APItoken":"YOUR_TOKEN_HERE",
        "url":"https://quantumexperience.ng.bluemix.net/api"}



# import basic plot tools
from qiskit.tools.visualization import plot_histogram

backend = 'local_qasm_simulator' # the device to run on
shots = 1024    # the number of shots in the experiment 

#to record the rotation number for encoding 00, 10, 11, 01
rotationNumbers = {"00":1, "10":3, "11":5, "01":7}

# Creating registers
# qubit for encoding 2 bits of information
qr = QuantumRegister(1)
# bit for recording the measurement of the qubit
cr = ClassicalRegister(1)

# dictionary for encoding circuits
encodingCircuits = {}
# Quantum circuits for encoding 00, 10, 11, 01
for bits in ("00", "01", "10", "11"):
    circuitName = "Encode"+bits
    encodingCircuits[circuitName] = QuantumCircuit(qr, cr, name=circuitName)
    encodingCircuits[circuitName].u3(rotationNumbers[bits]*pi/4.0, 0, 0, qr[0])
    encodingCircuits[circuitName].barrier()

# dictionary for decoding circuits
decodingCircuits = {}
# Quantum circuits for decoding the first and second bit
for pos in ("First", "Second"):
    circuitName = "Decode"+pos
    decodingCircuits[circuitName] = QuantumCircuit(qr, cr, name=circuitName)
    if pos == "Second": #if pos == "First" we can directly measure
        decodingCircuits[circuitName].h(qr[0])
    decodingCircuits[circuitName].measure(qr[0], cr[0])

#combine encoding and decoding of QRACs to get a list of complete circuits
circuitNames = []
circuits = []
for k1 in encodingCircuits.keys():
    for k2 in decodingCircuits.keys():
        circuitNames.append(k1+k2)
        circuits.append(encodingCircuits[k1]+decodingCircuits[k2])

print("List of circuit names:", circuitNames) #list of circuit names

job = execute(circuits, backend=backend, shots=shots)
results = job.result()
print("Experimental Result of Encode01DecodeFirst")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode01DecodeFirst")]))  #We should measure "0" with probability 0.85
print("Experimental Result of Encode01DecodeSecond")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode01DecodeSecond")])) #We should measure "1" with probability 0.85
print("Experimental Result of Encode11DecodeFirst")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode11DecodeFirst")]))  #We should measure "1" with probability 0.85
print("Experimental Result of Encode11DecodeSecond")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode11DecodeSecond")])) #We should measure "1" with probability 0.85

#to enable sleep
import time

backend = "ibmqx4" 

#connect to remote API to be able to use remote simulators and real devices
register(qx_config['APItoken'], qx_config['url'])
print("Available backends:", available_backends())

if get_backend(backend).status["available"] is True:
   
    job_exp = execute(circuits, backend=backend, shots=shots)
    
    lapse = 0
    interval = 50
    while not job_exp.done:
        print('Status @ {} seconds'.format(interval * lapse))
        print(job_exp.status)
        time.sleep(interval)
        lapse += 1
    print(job_exp.status)
    
    results = job_exp.result()
    
    print("Experimental Result of Encode01DecodeFirst")
    plot_histogram(results.get_counts(circuits[circuitNames.index("Encode01DecodeFirst")]))  #We should measure "0" with probability 0.85
    print("Experimental Result of Encode01DecodeSecond")
    plot_histogram(results.get_counts(circuits[circuitNames.index("Encode01DecodeSecond")])) #We should measure "1" with probability 0.85
    

backend = 'local_qasm_simulator' # the device to run on
shots = 1024    # the number of shots in the experiment 
from math import sqrt, cos, acos

#compute the value of theta
theta = acos(sqrt(0.5 + sqrt(3.0)/6.0))

#to record the u3 parameters for encoding 000, 010, 100, 110, 001, 011, 101, 111
rotationParams = {"000":(2*theta, pi/4, -pi/4), "010":(2*theta, 3*pi/4, -3*pi/4), 
                  "100":(pi-2*theta, pi/4, -pi/4), "110":(pi-2*theta, 3*pi/4, -3*pi/4), 
                  "001":(2*theta, -pi/4, pi/4), "011":(2*theta, -3*pi/4, 3*pi/4), 
                  "101":(pi-2*theta, -pi/4, pi/4), "111":(pi-2*theta, -3*pi/4, 3*pi/4)}

# Creating registers
# qubit for encoding 3 bits of information
qr = QuantumRegister(1)
# bit for recording the measurement of the qubit
cr = ClassicalRegister(1)

# dictionary for encoding circuits
encodingCircuits = {}
# Quantum circuits for encoding 000, ..., 111
for bits in rotationParams.keys():
    circuitName = "Encode"+bits
    encodingCircuits[circuitName] = QuantumCircuit(qr, cr, name=circuitName)
    encodingCircuits[circuitName].u3(*rotationParams[bits], qr[0])
    encodingCircuits[circuitName].barrier()

# dictionary for decoding circuits
decodingCircuits = {}
# Quantum circuits for decoding the first, second and third bit
for pos in ("First", "Second", "Third"):
    circuitName = "Decode"+pos
    decodingCircuits[circuitName] = QuantumCircuit(qr, cr, name=circuitName)
    if pos == "Second": #if pos == "First" we can directly measure
        decodingCircuits[circuitName].h(qr[0])
    elif pos == "Third":
        decodingCircuits[circuitName].u3(pi/2, -pi/2, pi/2, qr[0])
    decodingCircuits[circuitName].measure(qr[0], cr[0])

#combine encoding and decoding of QRACs to get a list of complete circuits
circuitNames = []
circuits = []
for k1 in encodingCircuits.keys():
    for k2 in decodingCircuits.keys():
        circuitNames.append(k1+k2)
        circuits.append(encodingCircuits[k1]+decodingCircuits[k2])

print("List of circuit names:", circuitNames) #list of circuit names

job = execute(circuits, backend=backend, shots=shots)
results = job.result()
print("Experimental Result of Encode010DecodeFirst")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode010DecodeFirst")]))  #We should measure "0" with probability 0.78
print("Experimental Result of Encode010DecodeSecond")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode010DecodeSecond")])) #We should measure "1" with probability 0.78
print("Experimental Result of Encode010DecodeThird")
plot_histogram(results.get_counts(circuits[circuitNames.index("Encode010DecodeThird")]))  #We should measure "0" with probability 0.78

backend = "ibmqx4"
#backend = "local_qasm_simulator"

print("Available backends:", available_backends())

if get_backend(backend).status["available"] is True:
   
    job_exp = execute(circuits, backend=backend, shots=shots)
    
    lapse = 0
    interval = 50
    while not job_exp.done:
        print('Status @ {} seconds'.format(interval * lapse))
        print(job_exp.status)
        time.sleep(interval)
        lapse += 1
    print(job_exp.status)
    
    results = job_exp.result()
    
    print("Experimental Result of Encode010DecodeFirst")
    plot_histogram(results.get_counts(circuits[circuitNames.index("Encode010DecodeFirst")]))  #We should measure "0" with probability 0.78
    print("Experimental Result of Encode010DecodeSecond")
    plot_histogram(results.get_counts(circuits[circuitNames.index("Encode010DecodeSecond")])) #We should measure "1" with probability 0.78
    print("Experimental Result of Encode010DecodeThird")
    plot_histogram(results.get_counts(circuits[circuitNames.index("Encode010DecodeThird")]))  #We should measure "0" with probability 0.78

