View on GitHub
Open this notebook in GitHub to run it yourself
- Phase-separation operator (based on the cost Hamiltonian H^C),
- Mixer (a unitary to explore the solution space).
Exercise
Let’s assume that we will solve the following problem using QAOA and GM-QAOA. We impose the following constraint: i=0∑3xi=1 The objective function is given by: f(x0,x1,x2,x3)=x0+2(x1+x2+x3)−3x2+10x3 Find x0,x1,x2,x3 that minimize f.Algorithm Description
In GM-QAOA, a Grover-type mixer is introduced: UM(β)=e−iβ∣F⟩⟨F∣, where ∣F⟩ is the equal superposition of all feasible solutions: ∣F⟩=∣F∣1x∈F∑∣x⟩. If we define U^S as the unitary that generates ∣F⟩ from the initial state ∣0⟩⊗n, U^S∣0⟩⊗n=∣F⟩, then the GM-QAOA circuit of depth p can be expressed as: ∣β,γ⟩=U^M(βp)U^P(γp)⋯U^M(β1)U^P(γ1)U^S∣0⟩⊗n, where U^P(γ)=e−iγH^C.Approach 1: QAOA
First, lets try to implement general QAOA.import math
import matplotlib.pyplot as plt
import numpy as np
import scipy
from tqdm import tqdm
from classiq import *
total_qbit = 4
# constraint is one hot.
def constraint(x: QArray[QBit]):
const = (x[0] + x[1] + x[2] + x[3] - 1) ** 2
return const
def object_func(x: QArray[QBit]):
obj = 1 * x[0] + 2 * (x[1] + x[2] + x[3]) - 3 * x[2] + 10 * x[3]
return obj
def cost(x: QArray[QBit]):
return constraint(x) + object_func(x)
@qfunc
def cost_layer(gamma: CReal, x: QArray[QBit, total_qbit]):
phase(cost(x), gamma)
@qfunc
def mixer_layer(beta: CReal, qba: QArray):
apply_to_all(lambda q: RX(beta, q), qba)
@qfunc
def qaoa_ansatz(
cost_layer: QCallable[CReal, QArray],
mixer_layer: QCallable[CReal, QArray],
gammas: CArray[CReal],
betas: CArray[CReal],
qba: QArray,
):
repeat(
betas.len,
lambda i: [
cost_layer(gammas[i], qba),
mixer_layer(betas[i], qba),
],
)
NUM_LAYERS = 3
@qfunc
def main(
params: CArray[CReal, NUM_LAYERS * 2],
x: Output[QArray[QBit, total_qbit]],
):
allocate(x)
gammas = params[0:NUM_LAYERS]
betas = params[NUM_LAYERS : 2 * NUM_LAYERS]
hadamard_transform(x)
qaoa_ansatz(cost_layer, mixer_layer, gammas, betas, x)
qprog_qaoa = synthesize(main)
show(qprog_qaoa)
Output:
Quantum program link: https://platform.classiq.io/circuit/3HoaA5iURiARFVcNH5AhPHL3SFG
NUM_SHOTS = 1000
MAX_ITERATIONS = 30
# for NUM_LAYERS=3, initial_params = [γ0,γ1,γ2,β0,β1,β2] = [0.0pi, 0.5pi, 1.0pi, 1.0pi, 0.5pi, 0.0pi]
initial_params = (
np.concatenate((np.linspace(0, 1, NUM_LAYERS), np.linspace(1, 0, NUM_LAYERS)))
* math.pi
)
cost_trace = []
def evaluate_params(es, params):
cost_estimation = es.estimate_cost(
cost_func=lambda state: cost(state["x"]),
parameters={"params": params.tolist()},
num_shots=NUM_SHOTS,
)
cost_trace.append(cost_estimation)
return cost_estimation
es = ExecutionSession(qprog_qaoa)
with tqdm(total=MAX_ITERATIONS, desc="Optimization Progress", leave=True) as pbar:
def progress_bar(xk: np.ndarray) -> None:
pbar.update(1) # increment progress bar
final_params = scipy.optimize.minimize(
fun=lambda params: evaluate_params(es, params),
x0=initial_params,
method="COBYLA",
options={"maxiter": MAX_ITERATIONS},
callback=progress_bar,
).x.tolist()
print(f"Optimized parameters: {final_params}")
plt.plot(cost_trace)
plt.xlabel("Iterations")
plt.ylabel("Cost")
plt.title("Cost convergence")
Output:
Optimization Progress: 0%| | 0/30 [00:00<?, ?it/s]
Output:
Optimization Progress: 3%|████▍ | 1/30 [00:21<10:14, 21.19s/it]
Output:
Optimization Progress: 7%|████████▊ | 2/30 [00:26<05:32, 11.87s/it]
Output:
Optimization Progress: 10%|█████████████▏ | 3/30 [00:31<04:00, 8.92s/it]
Output:
Optimization Progress: 13%|█████████████████▌ | 4/30 [00:34<02:48, 6.50s/it]
Output:
Optimization Progress: 17%|██████████████████████ | 5/30 [00:39<02:29, 5.98s/it]
Output:
Optimization Progress: 20%|██████████████████████████▍ | 6/30 [00:42<01:55, 4.80s/it]
Output:
Optimization Progress: 23%|██████████████████████████████▊ | 7/30 [00:44<01:33, 4.06s/it]
Output:
Optimization Progress: 27%|███████████████████████████████████▏ | 8/30 [00:49<01:34, 4.29s/it]
Output:
Optimization Progress: 30%|███████████████████████████████████████▌ | 9/30 [00:56<01:47, 5.12s/it]
Output:
Optimization Progress: 33%|███████████████████████████████████████████▋ | 10/30 [00:59<01:26, 4.30s/it]
Output:
Optimization Progress: 37%|████████████████████████████████████████████████ | 11/30 [01:01<01:12, 3.80s/it]
Output:
Optimization Progress: 40%|████████████████████████████████████████████████████▍ | 12/30 [01:07<01:16, 4.27s/it]
Output:
Optimization Progress: 43%|████████████████████████████████████████████████████████▊ | 13/30 [01:13<01:23, 4.93s/it]
Output:
Optimization Progress: 47%|█████████████████████████████████████████████████████████████▏ | 14/30 [01:18<01:19, 4.98s/it]
Output:
Optimization Progress: 47%|█████████████████████████████████████████████████████████████▏ | 14/30 [01:21<01:32, 5.79s/it]
Output:
Optimized parameters: [-0.014613961592609545, 2.6070511680624566, 4.1245105183721344, 3.1725451837671925, 1.660404323219326, 0.9980182358385975]
Output:
Text(0.5, 1.0, 'Cost convergence')

es = ExecutionSession(qprog_qaoa)
res_qaoa = es.sample({"params": final_params}, num_shots=1000)
es.close()
for sampled in res_qaoa.parsed_counts:
x = sampled.state["x"]
print(f"solution={x} probability={sampled.shots/NUM_SHOTS} cost={cost(x)}")
Output:
solution=[1, 0, 1, 0] probability=0.275 cost=1
solution=[1, 0, 0, 0] probability=0.264 cost=1
solution=[0, 1, 1, 0] probability=0.123 cost=2
solution=[1, 1, 0, 0] probability=0.079 cost=4
solution=[1, 0, 1, 1] probability=0.076 cost=16
solution=[0, 0, 1, 0] probability=0.057 cost=-1
solution=[0, 1, 0, 1] probability=0.029 cost=15
solution=[1, 1, 1, 0] probability=0.022 cost=6
solution=[0, 1, 0, 0] probability=0.021 cost=2
solution=[0, 0, 0, 1] probability=0.018 cost=12
solution=[0, 0, 0, 0] probability=0.011 cost=1
solution=[0, 1, 1, 1] probability=0.011 cost=17
solution=[0, 0, 1, 1] probability=0.006 cost=12
solution=[1, 1, 1, 1] probability=0.004 cost=23
solution=[1, 1, 0, 1] probability=0.003 cost=19
solution=[1, 0, 0, 1] probability=0.001 cost=14
Approach 2: GM-QAOA
Next, we use GM-QAOA.from classiq import *
total_qbit = 4
@qfunc
def initial_state(x: QArray):
prepare_dicke_state(1, x[0:4])
def object_func(x: QArray[QBit]):
obj = 1 * x[0] + 2 * (x[1] + x[2] + x[3]) - 3 * x[2] + 10 * x[3]
return obj
def cost(x: QArray[QBit]):
return object_func(x)
@qfunc
def cost_layer(gamma: CReal, x: QArray[QBit, total_qbit]):
phase(cost(x), gamma)
@qfunc
def mixer_layer(beta: CReal, x: QArray):
x_lsbs = QNum(size=x.len - 1)
x_msb = QBit()
within_apply(
lambda: (invert(lambda: initial_state(x)), bind(x, [x_lsbs, x_msb]), X(x_msb)),
lambda: control(x_lsbs == 0, lambda: RZ(-1.0 / np.pi * beta, x_msb)),
)
@qfunc
def qaoa_ansatz(
cost_layer: QCallable[CReal, QArray],
mixer_layer: QCallable[CReal, QArray],
gammas: CArray[CReal],
betas: CArray[CReal],
qba: QArray,
):
repeat(
betas.len,
lambda i: [
cost_layer(gammas[i], qba),
mixer_layer(betas[i], qba),
],
)
NUM_LAYERS = 3
@qfunc
def main(
params: CArray[CReal, NUM_LAYERS * 2],
x: Output[QArray[QBit, total_qbit]],
):
allocate(x)
gammas = params[0:NUM_LAYERS]
betas = params[NUM_LAYERS : 2 * NUM_LAYERS]
initial_state(x)
qaoa_ansatz(cost_layer, mixer_layer, gammas, betas, x)
qprog_gmqaoa = synthesize(main)
show(qprog_gmqaoa)
Output:
Quantum program link: https://platform.classiq.io/circuit/3HoaLmSgiwXX96QQhlWB0Xw3gQb
import math
import matplotlib.pyplot as plt
import numpy as np
import scipy
from tqdm import tqdm
NUM_SHOTS = 1000
MAX_ITERATIONS = 30
# start with a linear scheduling guess
initial_params = (
np.concatenate((np.linspace(0, 1, NUM_LAYERS), np.linspace(1, 0, NUM_LAYERS)))
* math.pi
)
cost_trace = []
def evaluate_params(es, params):
cost_estimation = es.estimate_cost(
cost_func=lambda state: cost(state["x"]),
parameters={"params": params.tolist()},
num_shots=NUM_SHOTS,
)
cost_trace.append(cost_estimation)
return cost_estimation
es = ExecutionSession(qprog_gmqaoa)
with tqdm(total=MAX_ITERATIONS, desc="Optimization Progress", leave=True) as pbar:
def progress_bar(xk: np.ndarray) -> None:
pbar.update(1) # increment progress bar
final_params = scipy.optimize.minimize(
fun=lambda params: evaluate_params(es, params),
x0=initial_params,
method="COBYLA",
options={"maxiter": MAX_ITERATIONS},
callback=progress_bar,
).x.tolist()
print(f"Optimized parameters: {final_params}")
plt.plot(cost_trace)
plt.xlabel("Iterations")
plt.ylabel("Cost")
plt.title("Cost convergence")
Output:
Optimization Progress: 0%| | 0/30 [00:00<?, ?it/s]
Output:
Optimization Progress: 3%|████▍ | 1/30 [00:26<13:02, 26.97s/it]
Output:
Optimization Progress: 7%|████████▊ | 2/30 [00:30<06:09, 13.20s/it]
Output:
Optimization Progress: 10%|█████████████▏ | 3/30 [00:33<03:50, 8.53s/it]
Output:
Optimization Progress: 13%|█████████████████▌ | 4/30 [00:36<02:41, 6.20s/it]
Output:
Optimization Progress: 17%|██████████████████████ | 5/30 [00:42<02:38, 6.33s/it]
Output:
Optimization Progress: 20%|██████████████████████████▍ | 6/30 [00:49<02:33, 6.39s/it]
Output:
Optimization Progress: 23%|██████████████████████████████▊ | 7/30 [00:55<02:24, 6.28s/it]
Output:
Optimization Progress: 27%|███████████████████████████████████▏ | 8/30 [00:58<01:55, 5.26s/it]
Output:
Optimization Progress: 30%|███████████████████████████████████████▌ | 9/30 [01:03<01:50, 5.28s/it]
Output:
Optimization Progress: 33%|███████████████████████████████████████████▋ | 10/30 [01:09<01:51, 5.56s/it]
Output:
Optimization Progress: 37%|████████████████████████████████████████████████ | 11/30 [01:12<01:29, 4.73s/it]
Output:
Optimization Progress: 40%|████████████████████████████████████████████████████▍ | 12/30 [01:15<01:15, 4.21s/it]
Output:
Optimization Progress: 43%|████████████████████████████████████████████████████████▊ | 13/30 [01:22<01:23, 4.89s/it]
Output:
Optimization Progress: 47%|█████████████████████████████████████████████████████████████▏ | 14/30 [01:25<01:08, 4.30s/it]
Output:
Optimization Progress: 50%|█████████████████████████████████████████████████████████████████▌ | 15/30 [01:27<00:57, 3.86s/it]
Output:
Optimization Progress: 50%|█████████████████████████████████████████████████████████████████▌ | 15/30 [01:35<01:35, 6.36s/it]
Output:
Optimized parameters: [0.6592184251974426, 2.8637333779121588, 4.499777756514392, 4.190715977678571, 2.6325123152812044, -0.5853196397274727]
Output:
Text(0.5, 1.0, 'Cost convergence')

es = ExecutionSession(qprog_gmqaoa)
res_gmqaoa = es.sample({"params": final_params}, num_shots=1000)
es.close()
for sampled in res_gmqaoa.parsed_counts:
x = sampled.state["x"]
print(f"solution={x} probability={sampled.shots/NUM_SHOTS} cost={cost(x)}")
Output:
solution=[0, 0, 1, 0] probability=0.533 cost=-1
solution=[1, 0, 0, 0] probability=0.218 cost=1
solution=[0, 1, 0, 0] probability=0.202 cost=2
solution=[0, 0, 0, 1] probability=0.047 cost=12
import pandas as pd
df_qaoa = res_qaoa.dataframe
df_gmqaoa = res_gmqaoa.dataframe
df_qaoa["x"] = df_qaoa["x"].apply(
lambda v: "".join(map(str, v)) if isinstance(v, list) else v
)
df_gmqaoa["x"] = df_gmqaoa["x"].apply(
lambda v: "".join(map(str, v)) if isinstance(v, list) else v
)
df_qaoa = df_qaoa.rename(columns={"probability": "QAOA"})
df_gmqaoa = df_gmqaoa.rename(columns={"probability": "GMQAOA"})
df = pd.merge(df_qaoa, df_gmqaoa, on="x", how="outer").fillna(0)
df = df[["x", "QAOA", "GMQAOA"]]
df.set_index("x").plot.bar(figsize=(10, 4))
Output:
<Axes: xlabel='x'>
