from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram
from qiskit.circuit import Parameter
import numpy as np

# Define a variable theta to be a parameter with name 'theta'
theta = Parameter('theta')

# Initialize a quantum circuit with one qubit
quantum_circuit = QuantumCircuit(1)

# Add a parametrized RX rotation on the qubit
quantum_circuit.rx(theta, 0)

quantum_circuit.draw(output="mpl")

# Set the value of the parameter
theta_value = np.pi

# Bind the value to the parametrized circuit
qc = quantum_circuit.bind_parameters({theta: theta_value})

# Add a measurement gate to the circuit before we run it
qc.measure_all()

qc.draw(output="mpl")

# Running on a simulator
from qiskit_aer import AerSimulator

sim = AerSimulator()
job = sim.run(qc) # shots = 1024 is the default
result = job.result() 
counts = result.get_counts() 
counts

# running on a quantum device

from qiskit import transpile
from qiskit_ibm_provider import IBMProvider, least_busy

provider = IBMProvider(instance='ibm-q/open/main')

# get the least-busy backend 
real_backend = least_busy(provider.backends(filters=lambda b: b.configuration().n_qubits >= 1 and
                                   not b.configuration().simulator and b.status().operational==True))

print(f"The best backend is: ", real_backend.name)


# transpilation
circuit_transpiled = transpile(qc, real_backend)

# submit the job
job = real_backend.run(circuit_transpiled, shots = 1024)

# Monitoring our job
from qiskit_ibm_provider.job import job_monitor
print(f"Job ID: {job.job_id()}")
job_monitor(job)

results = job.result()
counts = results.get_counts()
counts

from qiskit import QuantumCircuit
bell = QuantumCircuit(2)
bell.h(0)
bell.cx(0, 1)
bell.measure_all()
bell.draw()

from qiskit.primitives import Sampler

sampler = Sampler(options={"shots": 100})
job = sampler.run(bell) # uses local simulator under the hood
result = job.result()

print(result)

# quasi-distribution:
dist = result.quasi_dists[0].binary_probabilities()
print(f"Quasi-distribution is: ", dist)

plot_histogram(dist)

quantum_circuit.measure_all()
quantum_circuit.draw()
#notice that theta is an unbound parameter

thetas = [0, np.pi/4, np.pi/3, np.pi/2, 2*np.pi/3, 3*np.pi/4, np.pi]

indv_thetas = [[ph] for ph in thetas] #[[0], [np.pi/4], [np.pi/3], [np.pi/2], [2*np.pi/3], [3*np.pi/4], [np.pi]]

job = sampler.run([quantum_circuit]*len(indv_thetas), parameter_values=indv_thetas) # uses local simulator under the hood
result = job.result()

quasi_dists = result.quasi_dists
quasi_dists

# Get the prob values for each of the states

prob_values_0 = [dist.get(0, 0) for dist in result.quasi_dists]
prob_values_1 = [dist.get(1, 0) for dist in result.quasi_dists]

import matplotlib.pyplot as plt

plt.plot(thetas, prob_values_0, 'o', label='zero state')
plt.plot(thetas, prob_values_1, 'o', label='one state')
plt.xlabel('Theta')
plt.ylabel('Probability')
plt.legend();

from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler
from qiskit import QuantumCircuit

# QiskitRuntimeService.save_account(channel='ibm_quantum', token='my_token', overwrite=True) #uncomment if you need to save your account again
service = QiskitRuntimeService(
    channel='ibm_quantum',
    instance='ibm-q/open/main',
)

print(service.backends())

real_backend = service.least_busy(min_num_qubits=2, simulator=False, operational=True)

print(f"The best backend is {real_backend.name}")

# backend = service.get_backend('ibmq_qasm_simulator') # simulator
# can also use noisy simulators

# Execute both example circuits
with Session(service=service, backend=real_backend) as session:
    sampler = Sampler(session=session)

    # now we can run multiple jobs but only queue once
    job = sampler.run([bell], [[]])
    samples_bell = job.result().quasi_dists[0]

    job = sampler.run([quantum_circuit]*len(indv_thetas), parameter_values=indv_thetas)
    samples_param = job.result().quasi_dists

samples_bell.binary_probabilities()

samples_param

import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

