from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.compiler import transpile
from qiskit_ibm_runtime import QiskitRuntimeService

import numpy as np

################# MAKE SURE YOUR API TOKEN IS SAVED ##########################

#Store your API token locally (only need to do once)
#QiskitRuntimeService.save_account(channel='ibm_quantum', token='$API_TOKEN')

##############################################################################



#make sure your qiskit version is up to date
import qiskit.tools.jupyter
%qiskit_version_table

# Create a Quantum Circuit acting on a quantum register of two qubits
circ = QuantumCircuit(2)
print(circ)

# Add a H gate on qubit 0, putting this qubit in superposition.
circ.h(0)
# Add a CX (CNOT) gate on control qubit 0 and target qubit 1, putting
# the qubits in a Bell state.
circ.cx(0, 1)

circ.draw('mpl', style="iqp")

#One can also draw this as an ascii drawing
circ.draw()

#Create the circuit
qr = QuantumRegister(2)
cr = ClassicalRegister(2)

qc = QuantumCircuit(qr, cr)
# we can also write "qc = QuantumCircuit(2,2)"

#Add the hadamard and CNOT gates to the circuit
qc.h(0)
qc.cx(0, 1)

qc.draw('mpl', style="iqp")

# Import the SparesPauliOp class and create our observables variable
from qiskit.quantum_info import SparsePauliOp
 
observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])

# Import qiskit primitives
from qiskit.primitives import Estimator

estimator = Estimator()



job = estimator.run(qc, observable)
result = job.result()
print(result)
print(f"Expectation Value of <II+XX-YY+ZZ> = {result.values[0]:.3f}")


from qiskit_ibm_runtime import Estimator, Session

#Initialize a QiskitRuntimeService object
service = QiskitRuntimeService()

#We will use the 127-qubit ibm_nazca backend
backend = service.get_backend('ibm_nazca')

# create a Runtime session for efficient execution (optional)
session = Session(service=service, backend=backend)

# estimator = Estimator(backend=backend) 
estimator = Estimator(session=session)

#Here we can get some status information about the backend
status = backend.status()
is_operational = status.operational
jobs_in_queue = status.pending_jobs
print('Operational?: {} \n Jobs in Queue: {}\n'.format(is_operational, jobs_in_queue))


# We can also obtain some configuration information
config = backend.configuration()
print(64*"#","\nConfiguration for: {}, version: {}".format(config.backend_name, config.backend_version))
print("  Number of Qubits: {}".format(config.n_qubits))
print("  Basis Gates: {}".format(config.basis_gates))
print("  OpenPulse Enabled: {}".format(config.open_pulse))

job = estimator.run(qc, observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Session ID: {job.session_id}")
print(f">>> Job Status: {job.status()}")

# Use a job id from a previous result
job = service.job("cncfk64xswgg0087jbj0")
print(f">>> Job Status: {job.status()}")

#Examine our results once the job has completed
result = job.result()
print(f">>> {result}")
print(f"  > Expectation value: {result.values[0]}")
print(f"  > Metadata: {result.metadata[0]}")

from qiskit.primitives import Sampler

#Instantiate a new Sampler object
sampler = Sampler()


#We'll also need to add measurement
circ = QuantumCircuit(2)

circ.h(0)
circ.cx(0, 1)
# Insert measurements on all qubits
circ.measure_all()
circ.draw('mpl', style='iqp')


# Now run the job and examine the results
sampler_job = sampler.run(circ)
result = sampler_job.result()
print(f"Job Result:\n>>> {result}")
print(f"  > Quasi-probability distribution (integer): {result.quasi_dists[0]}")
print(f"  > Quasi-probability distribution (bits): {result.quasi_dists[0].binary_probabilities(2)}")
print(f"  > Metadata: {result.metadata[0]}")

from qiskit_ibm_runtime import Sampler
 
#Initialize a QiskitRuntimeService object
service = QiskitRuntimeService()

#We will use the 127-qubit ibm_nazca backend
backend = service.get_backend('ibm_nazca')

# sampler = Sampler(backend=backend)
sampler = Sampler(session=session)

job = sampler.run(circ)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Session ID: {job.session_id}")
print(f">>> Job Status: {job.status()}")

# Use a job id from a previous result
job = service.job("cncfm89hga30008f2660")
print(f">>> Job Status: {job.status()}")

result = job.result()
print(f"Job Result:\n>>> {result}")
print(f"  > Quasi-probability distribution (integer): {result.quasi_dists[0]}")
print(f"  > Quasi-probability distribution (bits): {result.quasi_dists[0].binary_probabilities(2)}")
print(f"  > Metadata: {result.metadata[0]}")

from qiskit.visualization import plot_distribution

import matplotlib.pyplot as plt
plt.style.use('dark_background')

#plot_distribution(result.quasi_dists[0])
plot_distribution(result.quasi_dists[0].binary_probabilities(2))

from qiskit_ibm_runtime import Options


# To set our resilience and optimization level we need to create this `Options` object
options = Options()
options.resilience_level = 2
options.optimization_level = 3

# We'll prepare the same example circuit as before
qr = QuantumRegister(2)
cr = ClassicalRegister(2)

qc = QuantumCircuit(qr, cr)
# we can also write "qc = QuantumCircuit(2,2)"

#Add the hadamard and CNOT gates to the circuit
qc.h(0)
qc.cx(0, 1)


# We'll also instantiate a new Runtime Estimator() object
# estimator = Estimator(options=options, backend=backend)
estimator = Estimator(options=options, session=session)

# and use the same observable as before
observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])


job = estimator.run(circuits=qc, observables=observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Session ID: {job.session_id}")
print(f">>> Job Status: {job.status()}")

# Use a job id from a previous result
job = service.job("cn9ee48q2k7g0088qvzg")
print(f">>> Job Status: {job.status()}")

#Examine our results once the job has completed
result = job.result()
print(f">>> {result}")
print(f"  > Expectation value: {result.values[0]}")
print(f"  > Metadata: {result.metadata[0]}")

from qiskit.providers.fake_provider import FakeSherbrooke
from qiskit import QuantumCircuit

backend = FakeSherbrooke()
print(backend.operation_names)
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.draw('mpl', style='iqp')

qc.decompose().draw('mpl')

qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.draw('mpl')

qc.decompose().draw('mpl')

