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.

qBraid Support - QBraidExecutor

Tags: integration optimization variational

This page introduces qBraid support in Qamomile and shows how to run a Qamomile workflow with QBraidExecutor. Qamomile currently connects to qBraid through its Qiskit integration, so the usual flow is qkernel -> QiskitTranspiler -> QBraidExecutor.

What this notebook shows

This notebook is a tutorial with QBraidExecutor built around a MaxCut workflow. The main goal is to show how to configure QBraidExecutor, transpile a qkernel with QiskitTranspiler, reuse the same remote executor throughout the optimization loop, and inspect the final samples returned by qBraid.

Installation

Install the qBraid integration with the optional qbraid dependency group:

pip install "qamomile[qbraid]"

This installs Qamomile together with the qBraid package and its Qiskit integration.

qBraid Setup

qBraid provides a unified interface for running quantum programs on supported simulators and hardware backends. In Qamomile, QBraidExecutor is the piece that submits Qiskit circuits to a qBraid device, waits for the remote job to finish, and returns the results in the same format as other Qamomile executors.

In this notebook, we create the executor first and then reuse it for both parameter optimization and the final evaluation step. The warning filter below is scoped to this setup cell only, so it suppresses the known pyqir runtime warning without affecting unrelated warnings elsewhere in the notebook.

Note: Replace "YOUR_API_KEY" in the cell below with your own qBraid API key. You can find your API key on the qBraid account page.

MaxCut Example

With the qBraid executor ready, we now prepare a single parameterized QAOA program for MaxCut and use qBraid to sample it repeatedly during optimization. The overall flow is graph construction -> Ising model -> parameterized QAOA qkernel -> Qiskit transpilation -> qBraid sampling -> solution analysis.

Constructing the problem

We use networkx.random_regular_graph to create a small random 3-regular graph with 8 nodes as the MaxCut instance. This graph is the classical input data that will later be encoded into a QAOA circuit and sampled on qBraid. The figure is only a visualization of the problem structure; the optimization itself uses the graph data.

<Figure size 600x500 with 1 Axes>

Constructing the Ising Hamiltonian

MaxCut can be written as an Ising optimization problem in which an edge contributes when its two endpoints are assigned to opposite spin states. In this construction, each graph edge adds a quadratic interaction term, while the constant term shifts the objective so that minimizing the Ising energy corresponds to maximizing the cut value. After building the spin model, normalize_by_rms() rescales the coefficients to a comparable magnitude, which is often helpful before repeatedly sampling the corresponding QAOA circuit on qBraid.

BinaryExpr(vartype=<VarType.SPIN: 'SPIN'>, constant=np.float64(-12.0), coefficients={(0,): np.float64(0.0), (1,): np.float64(0.0), (2,): np.float64(0.0), (3,): np.float64(0.0), (4,): np.float64(0.0), (5,): np.float64(0.0), (6,): np.float64(0.0), (7,): np.float64(0.0), (0, 7): np.float64(1.0), (0, 3): np.float64(1.0), (0, 2): np.float64(1.0), (1, 4): np.float64(1.0), (1, 6): np.float64(1.0), (1, 3): np.float64(1.0), (2, 4): np.float64(1.0), (2, 5): np.float64(1.0), (3, 6): np.float64(1.0), (4, 7): np.float64(1.0), (5, 7): np.float64(1.0), (5, 6): np.float64(1.0)})

Constructing the QAOA circuit

qaoa_state(...) builds the layered QAOA ansatz for the Ising model, and qmc.measure(q) turns the state preparation circuit into a sampling qkernel that returns measured bits. We then transpile the qkernel with QiskitTranspiler, binding the fixed problem data once while leaving gammas and betas as variational parameters.

Optimization

The objective function evaluates one candidate parameter vector at a time. For each trial, it binds gammas and betas, sends the executable to qBraid through QBraidExecutor, converts the sampled bitstrings into binary energies, and averages those energies to estimate the objective value. scipy.optimize.minimize(...) handles the outer classical search, while qBraid sampling provides the measurement data that drives each update. The same executor is reused across all iterations, which keeps the remote execution path explicit throughout the optimization loop.

Starting QAOA optimization with p=5 layers...
Initial parameters: gammas=[ 0.35076764  0.685864   -0.23260876  0.55643368  0.13513932], betas=[-0.51593692  0.46886025 -0.72964494 -0.30084649 -0.58317504]
Return from COBYLA because the trust region radius reaches its lower bound.
Number of function values = 86   Least value of F = -8.344
The corresponding X is:
[ 1.40667878  0.5369532   0.69483197  1.6757995  -0.02714964 -0.21405772
  0.35435012 -0.60134886  0.82291545 -0.64525361]


Optimized parameters:
  gammas: [ 1.40667878  0.5369532   0.69483197  1.6757995  -0.02714964]
  betas: [-0.21405772  0.35435012 -0.60134886  0.82291545 -0.64525361]
Final energy: -8.3440
<Figure size 1000x500 with 1 Axes>

Evaluation

After the optimization step, we call qBraid one more time with the optimized parameters and collect a larger sample of measurement outcomes. This lets us inspect the final energy distribution, identify the lowest-energy bitstring observed in the remote samples, and interpret that bitstring as a MaxCut solution on the original graph.

<Figure size 1200x500 with 1 Axes>
<Figure size 600x500 with 1 Axes>

Optional Classical Comparison

The main qBraid workflow ends above. The final section is an optional comparison against a classical optimizer built with JijModeling and OMMXPySCIPOptAdapter. This provides a reference solution for the same MaxCut instance and helps us judge how good the QAOA result is.

Loading...
{'V': 8, 'E': EdgeView([(0, 7), (0, 3), (0, 2), (1, 4), (1, 6), (1, 3), (2, 4), (2, 5), (3, 6), (4, 7), (5, 7), (5, 6)])}
SCIP Solution: [0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0]
<Figure size 600x500 with 1 Axes>
<Figure size 600x500 with 1 Axes>