QAOA Tutorial
[ ]:
# where to find this example code?
# https://learning.quantum.ibm.com/tutorial/quantum-approximate-optimization-algorithm
[1]:
# Find this qiskit code at: https://learning.quantum.ibm.com/tutorial/quantum-approximate-optimization-algorithm
import rustworkx as rx
from rustworkx.visualization import mpl_draw as draw_graph
import numpy as np
n = 5
graph = rx.PyGraph()
graph.add_nodes_from(np.arange(0, n, 1))
edge_list = [(0, 1, 1.0), (0, 2, 1.0), (0, 4, 1.0), (1, 2, 1.0), (2, 3, 1.0), (3, 4, 1.0)]
graph.add_edges_from(edge_list)
draw_graph(graph, node_size=600, with_labels=True)
[2]:
from qiskit.quantum_info import SparsePauliOp
def build_max_cut_paulis(graph: rx.PyGraph) -> list[tuple[str, float]]:
"""Convert the graph to Pauli list.
This function does the inverse of `build_max_cut_graph`
"""
pauli_list = []
for edge in list(graph.edge_list()):
paulis = ["I"] * len(graph)
paulis[edge[0]], paulis[edge[1]] = "Z", "Z"
weight = graph.get_edge_data(edge[0], edge[1])
pauli_list.append(("".join(paulis)[::-1], weight))
return pauli_list
max_cut_paulis = build_max_cut_paulis(graph)
cost_hamiltonian = SparsePauliOp.from_list(max_cut_paulis)
print("Cost Function Hamiltonian:", cost_hamiltonian)
Cost Function Hamiltonian: SparsePauliOp(['IIIZZ', 'IIZIZ', 'ZIIIZ', 'IIZZI', 'IZZII', 'ZZIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
[3]:
from qiskit.circuit.library import QAOAAnsatz
circuit = QAOAAnsatz(cost_operator=cost_hamiltonian, reps=2)
circuit.measure_all()
circuit.draw('mpl')
[3]:
[4]:
circuit.parameters
[4]:
ParameterView([ParameterVectorElement(β[0]), ParameterVectorElement(β[1]), ParameterVectorElement(γ[0]), ParameterVectorElement(γ[1])])
[5]:
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
# QiskitRuntimeService.save_account(channel="ibm_quantum", token="<MY_IBM_QUANTUM_TOKEN>", overwrite=True, set_as_default=True)
#service = QiskitRuntimeService(channel='ibm_quantum')
#backend = service.least_busy(min_num_qubits=127)
#print(backend)
import QuantumRingsLib
from QuantumRingsLib import QuantumRingsProvider
from quantumrings.toolkit.qiskit import QrBackendV2
provider = QuantumRingsProvider()
backend = QrBackendV2(provider, num_qubits = 5)
# if you like to save the account locally, call the following once and then invoke QrBackendForQiskit with no arguments
#QuantumRingsProvider.save_account(provider, token = <YOUR_TOKEN>, name=<YOUR_ACCOUNT>)
#backend = QrBackendV2()
# Create pass manager for transpilation
pm = generate_preset_pass_manager(optimization_level=3,
backend=backend)
candidate_circuit = pm.run(circuit)
candidate_circuit.draw('mpl', fold=False, idle_wires=False)
[5]:
[6]:
initial_gamma = np.pi
initial_beta = np.pi/2
init_params = [initial_gamma, initial_beta, initial_gamma, initial_beta]
[7]:
def cost_func_estimator(params, ansatz, hamiltonian, estimator):
# transform the observable defined on virtual qubits to
# an observable defined on all physical qubits
isa_hamiltonian = hamiltonian.apply_layout(ansatz.layout)
pub = (ansatz, isa_hamiltonian, params)
job = estimator.run([pub])
results = job.result()[0]
cost = results.data.evs
objective_func_vals.append(cost)
return cost
[8]:
#from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
from qiskit_ibm_runtime import Session
from scipy.optimize import minimize
from quantumrings.toolkit.qiskit import QrEstimatorV2 as Estimator
objective_func_vals = [] # Global variable
with Session(backend=backend) as session:
# If using qiskit-ibm-runtime<0.24.0, change `mode=` to `session=`
#estimator = Estimator(mode=session)
estimator = Estimator(backend=backend)
estimator.options.default_shots = 1000
# Set simple error suppression/mitigation options
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XY4"
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
result = minimize(
cost_func_estimator,
init_params,
args=(candidate_circuit, cost_hamiltonian, estimator),
method="COBYLA",
tol=1e-2,
)
print(result)
C:\Users\vkasi\.conda\envs\QiskitNew\Lib\site-packages\qiskit_ibm_runtime\session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
message: Optimization terminated successfully.
success: True
status: 1
fun: -1.15625
x: [ 4.659e+00 1.303e+00 2.044e+00 1.502e+00]
nfev: 29
maxcv: 0.0
[9]:
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()
[10]:
optimized_circuit = candidate_circuit.assign_parameters(result.x)
optimized_circuit.draw('mpl', fold=False, idle_wires=False)
[10]:
[11]:
#from qiskit_ibm_runtime import SamplerV2 as Sampler
from quantumrings.toolkit.qiskit import QrSamplerV2 as Sampler
# If using qiskit-ibm-runtime<0.24.0, change `mode=` to `backend=`
sampler = Sampler(backend=backend)
sampler.options.default_shots = 10000
# Set simple error suppression/mitigation options
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.twirling.enable_gates = True
sampler.options.twirling.num_randomizations = "auto"
pub= (optimized_circuit, )
job = sampler.run([pub], shots=int(1e4))
counts_int = job.result()[0].data.meas.get_int_counts()
counts_bin = job.result()[0].data.meas.get_counts()
shots = sum(counts_int.values())
final_distribution_int = {key: val/shots for key, val in counts_int.items()}
final_distribution_bin = {key: val/shots for key, val in counts_bin.items()}
print(final_distribution_int)
{0: 0.0055, 1: 0.005, 2: 0.0017, 3: 0.0165, 4: 0.005, 5: 0.0747, 6: 0.0153, 7: 0.0074, 8: 0.0054, 9: 0.0975, 10: 0.0467, 11: 0.0884, 12: 0.0399, 13: 0.0452, 14: 0.0361, 15: 0.0057, 16: 0.0079, 17: 0.0377, 18: 0.046, 19: 0.0378, 20: 0.094, 21: 0.0469, 22: 0.0983, 23: 0.0051, 24: 0.0087, 25: 0.0165, 26: 0.0747, 27: 0.0043, 28: 0.0171, 29: 0.0015, 30: 0.0035, 31: 0.004}
[12]:
# auxiliary functions to sample most likely bitstring
def to_bitstring(integer, num_bits):
result = np.binary_repr(integer, width=num_bits)
return [int(digit) for digit in result]
keys = list(final_distribution_int.keys())
values = list(final_distribution_int.values())
most_likely = keys[np.argmax(np.abs(values))]
most_likely_bitstring = to_bitstring(most_likely, len(graph))
most_likely_bitstring.reverse()
print("Result bitstring:", most_likely_bitstring)
Result bitstring: [0, 1, 1, 0, 1]
[13]:
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams.update({"font.size": 10})
final_bits = final_distribution_bin
values = np.abs(list(final_bits.values()))
top_4_values = sorted(values, reverse=True)[:4]
positions = []
for value in top_4_values:
positions.append(np.where(values == value)[0])
fig = plt.figure(figsize=(11, 6))
ax = fig.add_subplot(1, 1, 1)
plt.xticks(rotation=45)
plt.title("Result Distribution")
plt.xlabel("Bitstrings (reversed)")
plt.ylabel("Probability")
ax.bar(list(final_bits.keys()), list(final_bits.values()), color="tab:grey")
for p in positions:
ax.get_children()[int(p)].set_color("tab:purple")
plt.show()
C:\Users\vkasi\AppData\Local\Temp\ipykernel_22396\3823459478.py:19: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
ax.get_children()[int(p)].set_color("tab:purple")
[14]:
# auxiliary function to plot graphs
def plot_result(G, x):
colors = ["tab:grey" if i == 0 else "tab:purple" for i in x]
pos, default_axes = rx.spring_layout(G), plt.axes(frameon=True)
rx.visualization.mpl_draw(G, node_color=colors, node_size=100, alpha=0.8, pos=pos)
plot_result(graph, most_likely_bitstring)
[15]:
from typing import Sequence
def evaluate_sample(x: Sequence[int], graph: rx.PyGraph) -> float:
assert len(x) == len(list(graph.nodes())), "The length of x must coincide with the number of nodes in the graph."
return sum(x[u] * (1 - x[v]) + x[v] * (1 - x[u]) for u, v in list(graph.edge_list()))
cut_value= evaluate_sample(most_likely_bitstring, graph)
print('The value of the cut is:', cut_value)
The value of the cut is: 5
[ ]:
[ ]:
[ ]: