Skip to main content

View on GitHub

Open this notebook in GitHub to run it yourself
The notebook demonstrates the solution of a generic optimization problem using both the “vanilla” QAOA (Quantum Approximate Optimization Algorithm) and the adaptive version

Hamiltonian Encoding

The optimization objective function is encoded into a Hamiltonian function formulated as a weighted sum of Pauli strings: H=∑kckPkH = \sum_k{c_kP_k} with each pauli string, PkP_k, composed of a Pauli operation (σ∈{I,X,Y,Z}\sigma\in\left\{I,X,Y,Z\right\}) applied to each of the qubits in the register. For example, XIIZXIIZ, applies a Pauli XX to the first qubit and a Pauli ZZ to the fourth qubit The encoding of the objective start by defining the function with binary variables, xi∈{1,−1}x_i\in\left\{1,-1\right\}, and then defining Pauli strings with ZiZ_i corresponding to the original variables. If the objective variables’ domain is xi∈{0,1}x_i\in\left\{0,1\right\}, a transformation is used: xi=(1−Zi)/2x_i=\left(1-Z_i\right)/2 (commonly referred to as spin to bit transformation) Since the encoding results in Pauli strings composed only of II, and ZZ, the Pauli string is called diagonal, this has the following benefits:
  • Easy exponentiation - the exponentiation results in simple to implement single or multi-qubit Z rotations
  • No superposition mixing - the diagonal Hamiltonian does not create entanglement or rotate between basis states, enabling computation basis measurement
  • Efficient measurement - all {I,Z}⊗n\left\{I,Z\right\}^{\otimes n} strings commute, allowing for a single simultaneous measurement of all strings

QMod Tip

In the following cell the variable h is set to a diagonal hamiltonian using a self explanatory syntax that sums sparse Pauli strings, that is only the non identity Paulis are specified. For example the first line, - 0.1247375 * Pauli.Z(0) * Pauli.Z(1) creates the string ZZIII with the weight -0.1247375. The length of the string, is inferred from the largest qubit index in the sum. The resulating variable has the type SparsePauliOp For the optimization function a dual-use function is used for the Hamiltonian. This function takes a argument that can be either a QArray or a list[int]. This allows the same function to be used both to:
  • compute the cost expectation on the measurement results which are binary numbers (0,10,1)
  • create a quantum observable in the circuit, resulting in quantum gates
the additional cost_func function negates the hamiltonian function since the problem involves finding the maximum value, but the optimization procedure (scipy.optimize.minimize) performs a minimization

The Cost Layer

As mentioned above, since the Hamiltonian is diagonal, its exponentiation is straightforward and amounts to phase addition due to Z rotations.
This is accomplished with the phase function, which results in an efficient quantum implementation of e−iγHce^{-i \gamma H_c}.

The Mixer Layer

The QAOA mixer is defined as HM=∑XiH_M=\sum{X_i}, and its exponentiaion is simply a RXR_X rotation of the same angle (β\beta) on all the qubits

The QAOA Ansatz

The parametric QAOA ansatz is composed of pp pairs of exponentiated Mixer and Cost Hamiltonians: U(β⃗,γ⃗)=e−iβpHMe−iγpHC…e−iβ2HMe−iγ1HCe−iβ2HMe−iγ1HCU\left(\vec{\beta},\vec{\gamma}\right)=e^{-i \beta_p H_M}e^{-i \gamma_p H_C}\ldots e^{-i \beta_2 H_M}e^{-i \gamma_1 H_C}e^{-i \beta_2 H_M}e^{-i \gamma_1 H_C}

Assemble the Full QAOA Algorithm

We select p=3p=3 layers for the QAOA. The quantum program takes the 2p2p parameters as a single list (because the scipy.optimize.minimize function manipulate a 1-D array of parameters), and the quantum register of the 55 qubits. The qubits are initialized to ∣+⟩⊗5\left|+\right\rangle^{\otimes 5}, with a hadamard_transform and then the qaoa_ansatz, U(β⃗,γ⃗)U\left(\vec{\beta},\vec{\gamma}\right), is applied
Output:

Classical Optimization - “Vanilla” QAOA

The β⃗\vec{\beta}, and γ⃗\vec{\gamma} parameters are tuned until the objective function converges. The results of the parameters, and the solution vector are shown below
Output:
Output:
Plotting the convergence graph:
output

Displaying and Discussing the Results

Output:

ADAPT QAOA

The algorithm, based on the paper An adaptive quantum approximate optimization algorithm for solving combinatorial problems on a quantum computer, modifies QAOA by using different mixer layers, instead of the original HM=∑iXiH_M=\sum_i{X_i}. The algorithm defines a pool of potential mixer layers, that are simple to implement, and can match the problem better. The motivation to the algorithm is rooted in STA (shortcut to adiabaticity), with the hope of achieving better convergence than the “vanilla” QAOA. The algorithm proceeds incrementally:
  • initially, a single mixer layer is used (the original HMH_M) and the parameters are optimized
  • all the potential mixer layers, AjA_j, are tested as candidates.
The criterion is the resulting gradient of the commutator of a potential mixer layer with the cost Hamiltonian, applied to the current circuit: ∇Aj=⟨ψ(k−1)∣eiγ0HC[HC,Aj]e−iγ0HC∣ψ(k−1)⟩\nabla A_j = \left\langle\psi^{\left(k-1\right)}\right|e^{i \gamma_0 H_C}\left[H_C,A_j\right]e^{-i \gamma_0 H_C}\left|\psi^{\left(k-1\right)}\right\rangle
  • the mixer layer with the largest gradient is selected, the circuit grows by one layer, and the process continues (optimizing parameters and checking gradients)
  • the process terminates when the scaled norm of the gradient vector drops below a threshold

The Mixer Pool

The pool includes the default sum-of-X layer, as well as a sum-of-Y layer, followed by several single qubit layers with X and Y Paulis, and some two-qubit gates that add entanglement at the cost of a slightly more complicated layer. Several heuristics are suggested for building mixer pools based on the problem definition. A larger pool has a potential to find more efficient circuits, at the cost of computing many mixer gradients at each iteration. Each kind of mixer is generated programmatically over all NUM_QUBITS qubits (or qubit pairs).
The gradient of a mixer is computed by taking the circuit, e−iγ0HC∣ψ(k−1)⟩e^{-i \gamma_0 H_C}\left|\psi^{\left(k-1\right)}\right\rangle, provided by the es execution session and the params, and measuring the commutation of the mixer with the Hamiltonian: ∇Aj=⟨ψ(k−1)∣eiγ0HC[HC,Aj]e−iγ0HC∣ψ(k−1)⟩\nabla A_j=\left\langle\psi^{\left(k-1\right)}\right|e^{i \gamma_0 H_C}\left[H_C,A_j\right] e^{-i \gamma_0 H_C}\left|\psi^{\left(k-1\right)}\right\rangle Since ExecutionSession.observe accepts a list of observables and estimates all of them in a single execution job, the commutator of every mixer in the pool is built up front, and all the resulting gradients are estimated together in one observe call.

Classical Optimization

  • ADAPT QAOA
The implementation of the ADAPT QAOA uses the same hybrid quantum-classical structure to optimize the ansatz parameters (β⃗,γ⃗\vec{\beta},\vec{\gamma}) The differences are:
  • The ansatz mixers list is incrementally populated with mixer layers, and used in the ansatz
  • Following each convergence, the gradients of all mixers are computed, and either a new mixer is added, or the adaptive process concludes

QMod Tip

Since the mixer is no longer a sum of single bit Pauli strings (the default mixer can be written as: XII…I+IXI…I+IIX…I+III…XXII\ldots I+IXI\ldots I+IIX\ldots I+III\ldots X), the exponent is not a trivial Pauli rotation (applying RX(β)R_X\left(\beta\right) to each qubit) and the exponentiation e−iβAje^{-i \beta A_j} has to be computed. An efficient and simple construct is the suzuki_trotter function that exponentiates a SparsePauliOp (a weighted sum of Pauli strings) with a given coefficient. The adaptive_mixer_layer function uses this construct: suzuki_trotter(ansatz_mixers[mixer_idx], beta, 1, 1, qba), where the third and fourth arguments (1,1) specify the order and number of repetitions of the trotterization, which can be safely set to 11 for simple Pauli strings. Another variant is the multi_suzuki_trotter function that exponentiates a sum of Hamiltonians: e−i(H1t1+H2t2+…+Hntn)e^{-i\left(H_1t_1+H_2t_2+\ldots+H_nt_n\right)}
Output:
Output:
Output:
Output:
Output:
Output:
Output:
Output:
Output:
Output:
Plotting the convergence graph:
output
v counts probability bitstring
0 [0, 1, 0, 1, 0, 0, 1, 1] 56 0.027344 11001010
1 [1, 0, 1, 0, 1, 1, 0, 0] 43 0.020996 00110101
2 [1, 1, 0, 0, 1, 1, 0, 0] 42 0.020508 00110011
3 [1, 1, 0, 0, 1, 0, 1, 0] 40 0.019531 01010011
4 [0, 0, 1, 0, 0, 1, 0, 0] 38 0.018555 00100100
… … … … …
220 [1, 1, 0, 1, 1, 0, 1, 1] 1 0.000488 11011011
221 [0, 0, 1, 0, 0, 1, 1, 1] 1 0.000488 11100100
222 [0, 1, 1, 0, 0, 1, 1, 1] 1 0.000488 11100110
223 [0, 0, 0, 1, 0, 1, 1, 1] 1 0.000488 11101000
224 [1, 0, 0, 1, 1, 1, 1, 1] 1 0.000488 11111001

225 rows × 4 columns

Output:
v counts probability bitstring
0 [0, 1, 0, 1, 0, 0, 1, 1] 56 0.027344 11001010
1 [1, 0, 1, 0, 1, 1, 0, 0] 43 0.020996 00110101
2 [1, 1, 0, 0, 1, 1, 0, 0] 42 0.020508 00110011
3 [1, 1, 0, 0, 1, 0, 1, 0] 40 0.019531 01010011
4 [0, 0, 1, 0, 0, 1, 0, 0] 38 0.018555 00100100
… … … … …
201 [0, 1, 0, 1, 0, 1, 0, 0] 1 0.000488 00101010
200 [1, 1, 1, 0, 1, 0, 0, 0] 1 0.000488 00010111
199 [1, 0, 0, 0, 1, 0, 0, 0] 1 0.000488 00010001
198 [1, 1, 0, 1, 0, 0, 0, 0] 1 0.000488 00001011
224 [1, 0, 0, 1, 1, 1, 1, 1] 1 0.000488 11111001

225 rows × 4 columns