Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Grover Adaptive Search for Combinatorial Polynomial Binary Optimization

Tags: algorithm optimization oracle-based

Grover Adaptive Search (GAS) minimizes a polynomial objective over binary variables by repeatedly asking one question of a Grover oracle — which xx satisfy f(x)<yf(x) < y? — and lowering the threshold yy whenever a better solution is found Gilliam et al., 2021.

This page solves an unconstrained portfolio selection problem with Qamomile’s GASConverter.

The tutorial is built on the following structure:

  1. Formulate the problem with JijModeling.

  2. Create an instance with concrete data.

  3. Use GASConverter to build the Grover circuit for the current threshold.

  4. Sample it, keep the best candidate, and repeat until a stopping criterion is reached. GAS is a hybrid loop, not a single circuit.

Background

Grover search takes an oracle that distinguishes target states by reversing their phase and uses amplitude amplification to raise their measurement probability. That solves a decision problem: a fixed condition says which states qualify, not which one is best.

GAS turns it into minimization by changing this condition. It distinguishes the quantum states corresponding to candidates with f(x)<yf(x) < y by reversing their phase and samples candidates. If a candidate has a lower objective value than the current threshold, the classical layer updates yy to that value. This process repeats, with the quantum circuit answering one fixed question at a time and the classical layer updating the threshold.

Problem Settings

In this tutorial, we apply GAS to a portfolio selection problem.

Given nn assets (stocks, bonds, etc.), we decide for each one whether to buy it — a binary choice. We want to maximize returns while minimizing risk. The tension is:

  • μ\mu tells us how profitable each asset is expected to be

  • Σ\Sigma tells us how assets move together (correlated assets amplify risk)

  • qq controls how much we care about risk vs. return

We need to pick the best subset of assets so that the portfolio earns as much as possible without taking on too much correlated risk.

min⁡x∈{0,1}n(qxTΣx−μTx)\min_{x\in \lbrace 0 , 1 \rbrace^n} \big( q x^T \Sigma x - \mu^T x \big)
Loading...

The instance below has 9 assets:

NameExpected ReturnVariance
Asset 12212
Asset 2415
Asset 31910
Asset 4318
Asset 52314
Asset 6220
Asset 7511
Asset 82516
Asset 9313

Evaluating the JijModeling problem with that data yields an OMMX instance.

Algorithm

GAS starts by setting the threshold yy to the objective value of a candidate solution and uses Grover search to find candidates with lower objective values. Candidates obtained from the quantum circuit are evaluated classically, and the threshold is updated whenever a better candidate is found. This process repeats until the stopping condition is met. Here yy is a threshold on the objective value, not a candidate solution. GAS reverses the phase of the quantum states corresponding to candidates that satisfy f(x)<yf(x) < y, distinguishing them from the other candidates. The Grover ansatz is built from three components:

  • AyA_y, the preparation operator. It associates each input with f(x)−yf(x) - y by building the quantum dictionary state ∑x∣x,f(x)−y⟩\sum_x \ket{x, f(x) - y}. The circuit implementation uses the QFT-based method given in Gilliam et al., 2021. Because the register holds f(x)−yf(x) - y in two’s complement, candidates satisfying the condition can be identified by a Most Significant Bit (MSB) of 1.

  • OyO_y, the phase oracle. It reverses the phase of candidates satisfying f(x)<yf(x) < y. The MSB indicates whether the encoded value f(x)−yf(x) - y is negative, so this can be implemented with a single ZZ gate on that MSB.

  • DD, the diffusion operator, which amplifies the amplitude of the states distinguished by their reversed phase. It is a single multi-controlled-ZZ sandwiched between XX layers.

One iteration applies OyO_y, then Ay†A_y^\dagger, DD, AyA_y. The phase flip on its own leaves every measurement probability unchanged; the reflection AyDAy†A_y D A_y^\dagger is what converts it into amplitude. Measuring the input register returns a candidate, which the classical layer evaluates. It updates yy to the candidate’s objective value only if that value is lower than the current threshold.

The remaining question is how many times to apply the Grover operator. GAS answers it by sampling the iteration count from a range that grows slowly whenever a round brings no improvement.

Implementation

Qamomile provides the quantum circuits used in GAS through GASConverter. This section introduces its usage and the operations that make up the circuit, then combines them with a classical loop to implement GAS.

GASConverter takes an OMMX instance and converts it into a problem in QUBO/HUBO form. Its transpile() method builds, for a given transpiler, the Grover circuit used by the adaptive search.

Implementing the Grover circuit

GASConverter.transpile() internally builds the sampling qkernel below and feeds it to the transpiler. For visualization we can call Transpiler.to_block and MatplotlibDrawer.draw on that same qkernel.

The output-register width is not a free parameter: it has to hold f(x)−yf(x) - y for every xx, or the two’s-complement encoding wraps around and flips the very sign bit the oracle tests. required_output_bits() reports the width transpile() would choose for a given threshold.

Output register width for y=0: 9 qubits
<Figure size 40129x1616 with 1 Axes>

Inspecting the building blocks

AyA_y prepares ∑x∣x,f(x)−y⟩\sum_x \ket{x, f(x) - y} from QFT phase encodings:

<Figure size 15426x2432 with 1 Axes>

and DD reflects the input register about the uniform superposition:

<Figure size 902.5x1192 with 1 Axes>

The classical layer

Qamomile’s GASConverter provides functionality to construct a Grover circuit for a given threshold and convert measurement results into candidate solutions. However, it does not include the classical logic that controls the overall search: evaluating candidates, updating the threshold, adjusting the number of Grover iterations, and checking the stopping condition. To perform optimization with GAS, you therefore need to implement this logic yourself. The function below is an example of such an implementation.

It implements the classical outer loop of GAS, using converter.transpile() and converter.decode() as its quantum primitives. It starts from a random candidate xx with y=f(x)y = f(x), samples the Grover circuit, and updates the incumbent whenever a sample is better. The search stops after max_no_improvement consecutive rounds without improvement.

  • converter.transpile(transpiler, y=y, num_iterations=num_iterations) builds the Grover circuit for the current threshold yy and the specified number of Grover iterations.

  • executable.sample(executor, shots=256) runs it on the backend. Even for Grover, multiple shots are needed because NISQ quantum devices are noisy.

  • converter.decode(result) maps the raw bitstring counts back to decision variable assignments.

Result

In this section, we run GAS on the portfolio selection problem and verify the result by comparing the resulting objective value with the optimum obtained by brute-force search.

lamb sets how fast the sampling range for the Grover iteration count grows, and max_no_improvement fixes the stopping criterion.

[GAS] Initialization
[GAS] n=9, lambda=1.2
[GAS] Start state: x=[0, 1, 1, 1, 0, 1, 0, 0, 1], y=57.0, k=1

[GAS] Iteration 1 | current y=57.0, k=1.000000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] Improvement found -> accepting candidate and resetting k to 1

[GAS] Iteration 2 | current y=-23.0, k=1.000000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] No improvement -> keeping current solution and scaling k: 1.000000 -> 1.200000

[GAS] Iteration 3 | current y=-23.0, k=1.200000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] No improvement -> keeping current solution and scaling k: 1.200000 -> 1.440000

[GAS] Iteration 4 | current y=-23.0, k=1.440000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] No improvement -> keeping current solution and scaling k: 1.440000 -> 1.728000

[GAS] Iteration 5 | current y=-23.0, k=1.728000, Grover iters=1
[GAS] Candidate: x=[1, 0, 0, 0, 1, 0, 0, 1, 0], y=-28.0
[GAS] Improvement found -> accepting candidate and resetting k to 1

[GAS] Iteration 6 | current y=-28.0, k=1.000000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] No improvement -> keeping current solution and scaling k: 1.000000 -> 1.200000

[GAS] Iteration 7 | current y=-28.0, k=1.200000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] No improvement -> keeping current solution and scaling k: 1.200000 -> 1.440000

[GAS] Iteration 8 | current y=-28.0, k=1.440000, Grover iters=1
[GAS] Candidate: x=[1, 0, 0, 0, 1, 0, 0, 1, 0], y=-28.0
[GAS] No improvement -> keeping current solution and scaling k: 1.440000 -> 1.728000

[GAS] Iteration 9 | current y=-28.0, k=1.728000, Grover iters=0
[GAS] Candidate: x=[1, 0, 1, 1, 1, 0, 0, 0, 0], y=-23.0
[GAS] No improvement -> keeping current solution and scaling k: 1.728000 -> 2.073600

[GAS] Iteration 10 | current y=-28.0, k=2.073600, Grover iters=2
[GAS] Candidate: x=[1, 0, 0, 0, 1, 0, 0, 1, 0], y=-28.0
[GAS] No improvement -> keeping current solution and scaling k: 2.073600 -> 2.488320

[GAS] Finished after 10 iterations. Best found: x=[1, 0, 0, 0, 1, 0, 0, 1, 0], y=-28.0
Selected assets: [1, 5, 8], objective value: -28.0

The search stops once the solution has not improved for max_no_improvement rounds, which is a heuristic rule — it does not by itself prove that the returned solution is optimal. With 9 assets, we can enumerate all 29=5122^9 = 512 assignments, so let’s verify the result against an exact brute-force reference.

GAS         : x=[1, 0, 0, 0, 1, 0, 0, 1, 0], objective=-28.0
Brute force : x=[1, 0, 0, 0, 1, 0, 0, 1, 0], objective=-28.0

GAS matched the brute-force optimum.

Summary

In this notebook, we:

  • Solved an unconstrained portfolio problem with Grover Adaptive Search, where the quantum circuit answers “which xx satisfy f(x)<yf(x) < y?” and a classical loop updates the threshold yy when a better candidate is found.

  • Used GASConverter for the quantum part of GAS: transpile() builds the Grover circuit for the current threshold and number of Grover iterations, and decode() maps bitstring counts back to decision variables through OMMX.

  • Ran GAS on a portfolio selection problem and verified that the resulting objective value matched the optimum obtained by brute-force search.

References
  1. Gilliam, A., Woerner, S., & Gonciulea, C. (2021). Grover Adaptive Search for Constrained Polynomial Binary Optimization. Quantum, 5, 428. 10.22331/q-2021-04-08-428