ADAPT-QAOA Algorithm
A variant of QAOA that, like ADAPT-VQE, grows its mixer adaptively — picking the operator with the largest gradient at each layer instead of a fixed mixer. Understand first why it does this, then see how qalgora-Q expresses the whole loop in a few lines of code.
The problem it solves — a fixed mixer is too rigid
Standard QAOA alternates two kinds of operator: the cost Hamiltonian HC
that encodes the problem, and a mixing operator that drives transitions between states — the
latter usually fixed to a transverse field Σ Xi on every qubit. That fixed mixer
treats every problem, and every layer, identically; it pays no attention to which direction the current
state most needs to be stirred. The result is that reaching a good solution often demands many layers —
and depth is exactly the scarcest resource on shallow, near-term hardware.
Intuition — pick the best way to stir at every layer
ADAPT-QAOA stops fixing the mixer and instead chooses it adaptively, layer by layer. Prepare a pool of candidate mixing operators, and whenever a new layer is to be added, ask the same old question: which operator drives the energy down fastest? The gradient answers it. Pick the largest-gradient operator as this layer's mixer, re-optimize all the angles, then move on to the next layer. This way the stirring at each layer is tailored to the current state, and a good solution is usually reached in far fewer layers.
The math — measuring mixer gradients with a commutator
The criterion is the same as in ADAPT-VQE: in the current state, the gradient of the energy with
respect to the angle of a candidate mixer Aj, once it is switched in, is given by
the expectation of its commutator with the cost Hamiltonian
gj = −i⟨ [HC, Aj] ⟩.
The −i makes the gradient real-valued: for Hermitian HC and Aj the commutator is anti-Hermitian, so −i[HC, Aj] is Hermitian and its expectation is real.
The derivative is taken on the current optimized state |ψp⟩ at the candidate
mixer parameter β=0:
gj = d/dβ ⟨ψp| e(iβ Aj) HC e(−iβ Aj) |ψp⟩|β=0 = −i⟨ψp|[HC, Aj]|ψp⟩
so the operator actually measured is the Hermitian −i[HC, Aj], not
the non-Hermitian product HC·Aj.
The operator with the largest absolute gradient is the stirring direction that currently pushes the energy down hardest — so it is chosen as this layer's mixer. The only difference is what the criterion is used for: ADAPT-VQE uses it to grow a chemistry ansatz, while ADAPT-QAOA uses it to select QAOA's mixer one layer at a time.
How it works — the layer-by-layer iteration
- Start from the uniform superposition and a given cost Hamiltonian
HC. - For each operator
Ajin the pool, measure the gradient−i⟨[HC, Aj]⟩. - Add the highest-gradient operator to the circuit as the next mixer layer.
- Re-optimize all angles, then return to step 2 to grow the next layer.
The initial state is not always the uniform superposition: constrained problems need a feasible
reference state and a constraint-preserving mixer pool. And on re-optimization, all
γl and βl are optimized together.
Honest limits
- Per-layer screening costs measurements. Each new layer requires measuring the gradient of every candidate in the pool — this is precisely the price paid in exchange for shallower circuits.
- It is still greedy. Each layer takes only the locally steepest direction; there is no guarantee of the globally optimal layer ordering or the minimum total number of layers.
- The mixer pool design is critical. If the pool contains no suitable operator, adaptivity cannot conjure a good mixer; if the pool is too large, the screening cost climbs.
- Re-optimization grows more expensive with depth. Every added layer means re-optimizing all existing angles together, so each later round costs progressively more.
Seeing it in code
Read this code against the explanation above. pool collects the candidate mixers —
both single-qubit spin.x(i) and the two-qubit spin.y(i) * spin.y(j) acting along
the graph's edges. Each pass of the loop first computes the gradients grads for every
operator in the pool (the −i⟨[HC, Aj]⟩ values), uses
max(..., key=lambda k: abs(grads[k])) to select the operator with the largest absolute
gradient and append it as a new layer, then calls reoptimize(...) to re-optimize all the
angles. One pass, one layer — exactly matching the iteration above. Note that
commutator(cost, a) builds the actual commutator [HC, Aj],
and the measured operator is the Hermitian −i[HC, Aj], not the
non-Hermitian product HC·Aj.
import qalgora
from qalgora import spin
cost = build_maxcut_cost(graph)
pool = [spin.x(i) for i in range(n)] + \
[spin.y(i) * spin.y(j) for i, j in edges]
def commutator(h, a):
return h * a - a * h # [H_C, A_j] = H_C·A_j − A_j·H_C
layers = []
for _ in range(depth):
# g_j = -i<[H_C, A_j]> — measure the Hermitian observable -i[H_C, A_j]
grads = [(-1j * qalgora.observe(commutator_kernel,
commutator(cost, a), params).expectation()).real
for a in pool]
layers.append(pool[max(range(len(pool)), key=lambda k: abs(grads[k]))])
params = reoptimize(cost, layers)
ADAPT-QAOA 算法
QAOA 的一种变体,与 ADAPT-VQE 类似,以自适应方式生长混合层——在每一层选取梯度最大的混合算符,而非沿用固定的混合层。先理解它为何这样做,再看 qalgora-Q 如何把这套循环写成几行代码。
它解决的问题 固定混合层不够灵活
标准 QAOA 交替施加两类算符:把问题编码的代价哈密顿量 HC,和一个用来制造态间跃迁的混合算符——后者通常固定为所有比特上的横场 Σ Xi。这个固定混合层对每个问题、每一层都一视同仁,并不关心当前态最需要往哪个方向被搅动。结果是:要达到好解往往得堆很多层,而层数正是近期浅层硬件最稀缺的资源。
直觉 每一层都挑最合适的搅动方式
ADAPT-QAOA 不再固定混合层,而是逐层自适应地挑选。准备一池候选混合算符,每要加新一层时,问的还是那个老问题:哪个算符能让能量下降得最快?答案由梯度给出。把梯度最大的算符选作这一层的混合算符,再重新优化所有角度,然后进入下一层。这样每一层的搅动方式都是针对当前态量身挑的,往往用更少的层就能逼近好解。
数学 用对易子衡量混合层梯度
沿用与 ADAPT-VQE 相同的判据:在当前态下,把候选混合算符 Aj 接入后能量对其角度的梯度,由它与代价哈密顿量的对易子期望给出
gj = −i⟨ [HC, Aj] ⟩。
其中 −i 使梯度为实数:当 HC 与 Aj 为厄米算符时,对易子为反厄米算符,故 −i[HC, Aj] 为厄米算符,其期望为实数。
该导数是在当前已优化态 |ψp⟩ 上、于候选混合参数 β=0 处取得的:
gj = d/dβ ⟨ψp| e(iβ Aj) HC e(−iβ Aj) |ψp⟩|β=0 = −i⟨ψp|[HC, Aj]|ψp⟩
因此实际测量的算符是厄米的 −i[HC, Aj],而不是非厄米的乘积 HC·Aj。
梯度绝对值最大的算符,就是当下最能推动能量下降的搅动方向——把它选作这一层的混合层。区别仅在于:ADAPT-VQE 用它来生长一个化学拟设,ADAPT-QAOA 用它来逐层选 QAOA 的混合算符。
机制 逐层迭代
- 从均匀叠加态出发,给定代价哈密顿量
HC。 - 对算符池中的每个算符
Aj,测量梯度−i⟨[HC, Aj]⟩。 - 将梯度最大的算符作为下一个混合层加入线路。
- 重新优化全部角度,然后回到第二步继续生长下一层。
初态不一定总是均匀叠加态;带约束的问题需要可行的参考态和保持约束的 mixer pool。重新优化时同时优化所有 γl 与 βl。
诚实的边界
- 逐层筛选有测量开销。每加一层都要把池中所有候选算符的梯度测一遍——这正是它换取浅线路所付的代价。
- 它依然是贪心的。每层只取当前最陡的方向,不保证全局最优的层序或最少的总层数。
- 混合算符池的设计很关键。池子里若没有合适的算符,自适应也挑不出好混合层;池子太大则筛选成本上升。
- 重优化随层数累积变贵。每加一层都要把已有角度一并重优化,后期每轮成本递增。
对照代码理解
请对照上文来读这段代码。pool 收集候选混合算符——既有单比特的 spin.x(i),也有沿图的边作用的两比特 spin.y(i) * spin.y(j)。循环每一轮先对池中每个算符算出梯度 grads(即 −i⟨[HC, Aj]⟩ 的值),用 max(..., key=lambda k: abs(grads[k])) 选出梯度绝对值最大的算符追加为新的一层,再调用 reoptimize(...) 重新优化全部角度。一轮一层,恰好对应上面的迭代流程。注意 commutator(cost, a) 构造的是真正的对易子 [HC, Aj],实际测量的算符是厄米的 −i[HC, Aj],而不是非厄米的乘积 HC·Aj。
示例代码
import qalgora
from qalgora import spin
cost = build_maxcut_cost(graph)
pool = [spin.x(i) for i in range(n)] + \
[spin.y(i) * spin.y(j) for i, j in edges]
def commutator(h, a):
return h * a - a * h # [H_C, A_j] = H_C·A_j − A_j·H_C
layers = []
for _ in range(depth):
# g_j = -i<[H_C, A_j]> — 测量厄米可观测量 -i[H_C, A_j]
grads = [(-1j * qalgora.observe(commutator_kernel,
commutator(cost, a), params).expectation()).real
for a in pool]
layers.append(pool[max(range(len(pool)), key=lambda k: abs(grads[k]))])
params = reoptimize(cost, layers)