import math
import numpy as np
from qiskit import *
nshots = 8192
qiskit.IBMQ.load_account()
#provider = IBMQ.get_provider(hub='ibm-q-community', group='ibmquantumawards', project='open-science-22')
#device = provider.get_backend('ibmq_jakarta')
provider = qiskit.IBMQ.get_provider(hub = 'ibm-q-research-2', group = 'federal-uni-sant-1', project = 'main')
device = provider.get_backend('ibmq_bogota')
simulator = Aer.get_backend('qasm_simulator')
from qiskit.tools.monitor import job_monitor
from qiskit.ignis.verification.tomography import state_tomography_circuits, StateTomographyFitter
from qiskit.tools.monitor import backend_overview, backend_monitor
from qiskit.tools.visualization import plot_histogram

qc = QuantumCircuit(5)
th, ph, lb = 0.1, 0.2, 0.3; qc.u(th, ph, lb, [0]); qc.barrier() # qubit estate preparation
qc.cx([0],[1]); qc.cx([1], [2]); qc.h([0, 1, 2]); qc.barrier() # encoding
qc.z([0]); qc.barrier() # phase flip error
qc.draw(output = 'mpl')

qc.h([0, 1, 2]); qc.cx([0], [3]); qc.cx([1], [3]); qc.cx([1], [4]); qc.cx([2], [4])
qc.draw(output = 'mpl')

def qc_ec_pf(th, ph, lb, j):
    qc = QuantumCircuit(5, name = 'EC_PF')
    qc.u(th, ph, lb, [0]) # qubit state preparation
    qc.barrier()
    qc.cx([0], [1]); qc.cx([1], [2]); qc.h([0, 1, 2]) # encoding
    qc.barrier()
    if j == 0 or j == 1 or j == 2:  # error
        qc.z([j])
    qc.barrier() 
    qc.h([0, 1, 2]); qc.cx([0], [3]); qc.cx([1], [3]); qc.cx([1], [4]); qc.cx([2], [4]) # syndrome detection
    qc.barrier()
    qc.x([3]); qc.ccx([4], [3], [2]); qc.x([3]); qc.ccx([4], [3], [1]) # correction
    qc.x([4]); qc.ccx([4], [3], [0]); qc.x([4]) # correction
    qc.barrier()
    return qc
th, ph, lb = math.pi, 0.0, 0.0; j = 2; qc_ec_pf_ = qc_ec_pf(th, ph, lb, j)
qc_ec_pf_.draw(output = 'mpl')

# Teste da funcionalidade do circuito para correção do phase flip
qc = QuantumCircuit(5, 3); 
j = 2 # sets the error
qc_ec_pf_ = qc_ec_pf(math.pi/2, 0.0, 0.0, j); qc.append(qc_ec_pf_, [0, 1, 2, 3, 4])
qc.measure([0, 1, 2], [0, 1, 2]);
#qc.decompose().draw(output = 'mpl')

job_sim = execute(qc, backend = simulator, shots = nshots)
plot_histogram(job_sim.result().get_counts(qc))

job_exp = execute(qc, backend = device, shots = nshots)
print(job_exp.job_id()); job_monitor(job_exp)
plot_histogram(job_exp.result().get_counts(qc))

qc = QuantumCircuit(2, 1)
th, ph, lb = math.pi, 0.0, 0.0; qc.u(th, ph, lb, [0]) # state preparation
qc.barrier(); qc.cx([0], [1]); qc.h([0]); qc.barrier()
qc.z([0]); qc.barrier()
qc.h([0]); qc.cx([0], [1]); qc.cx([1], [0])
qc.measure([0], [0])
qc.draw(output = 'mpl')

job = execute(qc, backend = simulator, shots = nshots)
plot_histogram(job.result().get_counts(qc))

job = execute(qc, backend = device, shots = nshots); job_monitor(job)
plot_histogram(job.result().get_counts(qc))

def qc_encoding_shor():
    qc = QuantumCircuit(9, name = 'encoding')
    qc.cx([0], [3]); qc.cx([0], [6])
    qc.h([0, 3, 6])
    qc.cx([0], [1]); qc.cx([0], [2]); qc.cx([3], [4])
    qc.cx([3], [5]); qc.cx([6], [7]); qc.cx([6], [8])
    return qc
qc_encoding_shor_ = qc_encoding_shor(); qc_encoding_shor_.draw(output = 'mpl')

def qc_gen_bf():
    qc = QuantumCircuit(9+6, name = 'BF')
    qc.decompose().draw(output = 'mpl')
    qc.cx([0], [9]); qc.cx([1], [9]); qc.cx([1], [10]); qc.cx([2], [10])
    qc.barrier()
    qc.x([9]); qc.ccx([10], [9], [2]); qc.x([9]); qc.ccx([10], [9], [1]); qc.x([10]); qc.ccx([10], [9], [0]); qc.x([10])
    qc.barrier()
    qc.cx([3], [11]); qc.cx([4], [11]); qc.cx([4], [12]); qc.cx([5], [12])
    qc.barrier()
    qc.x([11]); qc.ccx([12], [11], [5]); qc.x([11]); qc.ccx([12], [11], [4]); qc.x([12]); qc.ccx([12], [11], [3]); qc.x([12])
    qc.barrier()
    qc.cx([6], [13]); qc.cx([7], [13]); qc.cx([7], [14]); qc.cx([8], [14])
    qc.barrier()
    qc.x([13]); qc.ccx([14], [13], [8]); qc.x([13]); qc.ccx([14], [13], [7]); qc.x([14]); qc.ccx([14], [13], [6]); qc.x([14])
    return qc
qc_gen_bf_ = qc_gen_bf(); qc_gen_bf_.draw(output = 'mpl')
#qc.decompose().draw(output = 'mpl')

def qc_gen_pf():
    qc = QuantumCircuit(9+2, name = 'PF')
    qc.cx([0], [1]); qc.cx([0], [2]); qc.cx([3], [4]); qc.cx([3], [5]); qc.cx([6], [7]); qc.cx([6], [8]) # destrói o emaranhamento
    qc.h([0, 3, 6]) # destrói a coerência
    qc.cx([0], [9]); qc.cx([3], [9]); qc.cx([3], [10]); qc.cx([6], [10]) # medida de síndrome
    qc.barrier()
    qc.x([9]); qc.ccx([10], [9], [6]); qc.x([9]); qc.ccx([10], [9], [3]); qc.x([10]); qc.ccx([10], [9], [0]); qc.x([10]) # reversão do erro
    qc.barrier()
    qc.h([0, 3, 6]) # passa esses qubits para a base |+>,|->
    qc.cx([0], [1]); qc.cx([0], [2]); qc.cx([3], [4]); qc.cx([3], [5]); qc.cx([6], [7]); qc.cx([6], [8]) # recria o emaranhamento
    return qc
qc_gen_pf_ = qc_gen_pf(); qc_gen_pf_.draw(output = 'mpl')

def qc_decoding_shor():
    qc = QuantumCircuit(9, name = 'decoding')
    qc.cx([0], [2]); qc.cx([3], [5]); qc.cx([6], [8])
    qc.cx([0], [1]); qc.cx([3], [4]); qc.cx([6], [7])
    qc.h([0, 3, 6])
    qc.cx([0], [3]); qc.cx([0], [6])
    return qc
qc_decoding_shor_ = qc_decoding_shor(); qc_decoding_shor_.draw(output = 'mpl')

def qc_error():
    qc = QuantumCircuit(9, name = 'error')
    qc.x([0]); qc.z([0])
    #qc.z(4); qc.x([5])
    qc.x([8])
    return qc
qc_error_ = qc_error(); qc_error_.draw(output = 'mpl')

qc = QuantumCircuit(9+6+2, 1)
qc.h([0]) # prepara o estado |1>
qc.barrier()
qc_encoding_shor_ = qc_encoding_shor(); qc.append(qc_encoding_shor_, [0,1,2,3,4,5,6,7,8])
qc.barrier()
qc_error_ = qc_error(); qc.append(qc_error_, [0,1,2,3,4,5,6,7,8])
qc.barrier()
qc_gen_bf_ = qc_gen_bf(); qc.append(qc_gen_bf_, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14])
qc.barrier()
qc_gen_pf_ = qc_gen_pf(); qc.append(qc_gen_pf_, [0,1,2,3,4,5,6,7,8,15,16])
qc.barrier()
qc_decoding_shor_ = qc_decoding_shor(); qc.append(qc_decoding_shor_, [0,1,2,3,4,5,6,7,8])
qc.barrier()
qc.measure([0], 0)
qc.draw(output = 'mpl')
    

job = execute(qc, backend = simulator, shots = nshots)
plot_histogram(job.result().get_counts(qc))


qc = QuantumCircuit(9)
th, ph, lb = math.pi/2, 0.0, 0.0; qc.u(th, ph, lb, [0])

qc.barrier(); qc.x([0]); qc.z([0]); qc.barrier() # o erro
qc.cx([0], [1]); qc.cx([0], [2]); qc.cx([3], [4]); qc.cx([3], [5]); qc.cx([6], [7]); qc.cx([6], [8])
qc.ccx([2], [1], [0]); qc.ccx([5], [4], [3]); qc.ccx([8], [7], [6])
qc.h([0, 3, 6])
qc.cx([0], [3]); qc.cx([0], [6])
qc.ccx([6], [3], [0])
qc.draw(output = 'mpl')

qc = QuantumCircuit(3, 1)
th, ph, lb = math.pi, 0.0, 0.0; qc.u(th, ph, lb, [1]) # state preparation
qc.barrier(); qc.cx([1], [2]); qc.barrier()
qc.x([1]); qc.z([1]); qc.barrier()
qc.cx([1], [2]); qc.cx([2], [1])
qc.measure([1], [0])
qc.draw(output = 'mpl')