Skip to main content
IBM Quantum Platform
Información sobre la traducción

Próximamente habrá traducciones a otros idiomas de esta página.

Solve a maximum independent set problem with more variables than qubits using JIJ's SQOA Solver

Note

Qiskit Functions are an experimental feature available only to IBM Quantum® Premium Plan, Flex Plan, and On-Prem (via IBM Quantum Platform API) Plan users. They are in preview release status and subject to change.

Usage estimate: 10 seconds on a Heron r2 processor. (NOTE: This is an estimate only. Your runtime might vary.)


Background

This tutorial shows how to use SQOA Solver, a Qiskit Function by JIJ, to solve a maximum independent set (MIS) problem with more binary variables than the selected QPU has qubits. The basic steps are to load an MIS instance from the Quantum Optimization Benchmarking Library (QOBLIB) [1], formulate it in quadratic unconstrained binary optimization (QUBO) form with qiskit-addon-opt-mapper, submit it to SQOA Solver for execution on an IBM® QPU, and validate the returned solution.

The maximum independent set problem

An independent set is a subset of nodes in a graph such that no two selected nodes share an edge. A maximum independent set (MIS) is an independent set with the largest possible number of nodes. Applications of MIS include scheduling, resource allocation, and network design.

MIS is NP-hard. In an exhaustive search, every additional node doubles the number of subsets to consider, so this approach is impractical even for moderately sized graphs. QOBLIB includes MIS as one of its ten problem classes, so it is a useful benchmark for evaluating quantum optimization workflows on real hardware.

Mathematical formulation

Given an undirected graph G=(V,E)G = (V, E), assign a binary variable xvx_v to each node vv:

  • xv=1x_v = 1 if node vv is selected.
  • xv=0x_v = 0 otherwise.

The binary linear formulation maximizes the number of selected nodes while preventing both endpoints of any edge from being selected:

max⁡x∑v∈Vxvsubject toxv+xw≤1∀(v,w)∈E,xv∈{0,1}∀v∈V.\begin{aligned} \max_{x} \quad & \sum_{v \in V} x_v \\ \text{subject to} \quad & x_v + x_w \leq 1 && \forall (v,w) \in E, \\ & x_v \in \{0,1\} && \forall v \in V. \end{aligned}

The objective counts the selected nodes. Together, the edge constraints ensure that the selected nodes form an independent set.

This tutorial uses QOBLIB's binary unconstrained formulation, which replaces the edge constraints with quadratic penalties:

max⁡x∈{0,1}∣V∣∑v∈Vxv−2∑(v,w)∈Exvxw.\max_{x \in \{0,1\}^{|V|}} \quad \sum_{v \in V} x_v - 2 \sum_{(v,w) \in E} x_v x_w.

Whenever both endpoints of an edge are selected, the second term subtracts two from the objective. This penalty makes a conflicting selection less favorable than keeping either endpoint alone. The resulting objective is a QUBO, which is the input format that SQOA Solver accepts.

Why use SQOA Solver?

Many quantum optimization approaches assign one qubit to each binary variable, so the capacity of the quantum hardware can directly limit the problem size. SQOA Solver combines several reduction and hybrid-computing techniques to ease this restriction:

  • It can tackle problems with more binary variables than the QPU has qubits. Presolve applies reduction rules that can fix variables and simplify the remaining QUBO while preserving at least one optimal solution. Empirically, presolve can also make the remaining QUBO sparser, which helps the QRAC-based encoding that follows it.

    The solver then uses a QRAC-based encoding derived from Quantum Random Access Optimization (QRAO) [3] to represent multiple variables (up to three) with no direct QUBO interaction on the same qubit.

    The reductions from presolve and encoding depend on the problem structure. For example, in this tutorial, a 200-variable problem uses only 67 qubits.

  • It can be less sensitive to hardware noise. Sample-based quantum diagonalization [4], [5] uses the QPU to sample bitstrings but performs the diagonalization classically. This avoids repeated QPU evaluations for variational parameter updates and can reduce the impact of noise on the optimization workflow.

  • It automates the workflow from presolve through post-processing. You provide a correctly formatted QUBO instance and select a backend. SQOA Solver then runs the whole workflow automatically. It applies presolve, maps the classical variables to a relaxed quantum problem, constructs and transpiles the sampling circuits, performs quantum sampling and classical diagonalization, rounds and refines the assignment, and reconstructs and evaluates the classical solution.


Requirements

Before you start this tutorial, make sure that you have the following packages installed:

  • Qiskit Functions Catalog client (pip install qiskit-ibm-catalog).
  • Qiskit addon optimization mapper, used to formulate the problem (pip install qiskit-addon-opt-mapper).
  • NetworkX and Matplotlib, used to handle and plot the graph (pip install networkx matplotlib).

You also need access to SQOA Solver from the Qiskit Functions Catalog. Fill out the form to request access to the function.

# %pip install qiskit-ibm-catalog qiskit-addon-opt-mapper networkx matplotlib

Setup

First, import the required packages and tools.

import urllib.request

import matplotlib.pyplot as plt
import networkx as nx
from qiskit_addon_opt_mapper import OptimizationProblem
from qiskit_addon_opt_mapper.converters import OptimizationProblemToQubo
from qiskit_ibm_catalog import QiskitFunctionsCatalog

SQOA Solver accepts a QUBO problem as a coefficient dictionary in the tuple-string format used by IBM® optimization functions. Define a helper function that converts the QUBO returned by OptimizationProblemToQubo into this format.

The converter always returns a minimization problem, which matches how SQOA Solver treats a dictionary problem. The keys use the variable indices of the QUBO, and every variable receives a linear entry, even with a zero coefficient, so that the returned solution covers all variables.

def to_problem_dict(qubo: OptimizationProblem) -> dict[str, float]:
    """Convert a QUBO from OptimizationProblemToQubo to the SQOA Solver dictionary format."""
    objective = qubo.objective
    payload = {"()": objective.constant}
    # Keep every variable in the problem, even with a zero linear coefficient.
    payload.update({f"({i},)": 0.0 for i in range(qubo.get_num_vars())})
    for i, coefficient in objective.linear.to_dict().items():
        payload[f"({i},)"] += coefficient
    for (i, j), coefficient in objective.quadratic.to_dict().items():
        # Fold a binary diagonal term x_i * x_i = x_i into the linear term.
        key = f"({i},)" if i == j else f"({i}, {j})"
        payload[key] = payload.get(key, 0.0) + coefficient
    return {key: float(value) for key, value in payload.items()}

Step 1: Map classical inputs to a quantum problem

SQOA Solver accepts a QUBO problem and performs the quantum mapping inside the function. In this tutorial, you download an MIS instance from QOBLIB and formulate it as a QUBO with qiskit-addon-opt-mapper. This step covers the following:

  • Connect to SQOA Solver.
  • Understand the SQOA framework.
  • Load the problem instance.
  • Formulate the QUBO and convert it for submission.

Connect to SQOA Solver

Connect to the Qiskit Functions Catalog with your IBM Quantum Platform credentials, then load SQOA Solver. The following code assumes that you already saved your account to your local environment.

If the connection fails, confirm that your IBM Quantum Platform account administrator has granted the selected service instance access to SQOA Solver.

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
solver = catalog.load("jij/sqoa-solver")

Understand the SQOA framework

The Sampling-based Quantum Optimization Algorithm with Quantum Relaxation (SQOA-QR) [2] combines Quantum Random Access Optimization (QRAO) with sample-based quantum diagonalization:

  1. Quantum relaxation and encoding: SQOA Solver converts the QUBO to a minimization Ising objective of the form c+∑ihizi+∑i<jJijzizjc + \sum_i h_i z_i + \sum_{i<j} J_{ij} z_i z_j, where zi∈{−1,+1}z_i \in \{-1, +1\}. QRAO [3] then maps each spin variable to a Pauli observable. For the (3,1)(3,1)-QRAC encoding used here, the relaxed Hamiltonian is

    H~=cI+∑ikihiPf(i)+∑i<jkikjJijPf(i)Pf(j),\widetilde{H} = cI + \sum_i \sqrt{k_i} h_i P_{f(i)} + \sum_{i<j} \sqrt{k_i k_j} J_{ij} P_{f(i)} P_{f(j)},

    where II is the identity operator, f(i)f(i) assigns variable ii to a qubit and one of XX, YY, or ZZ, and ki∈{1,2,3}k_i \in \{1,2,3\} is the number of variables mapped to the same qubit as ii. When both relevant qubits contain three variables, the linear and quadratic scaling factors become 3\sqrt{3} and 33, respectively.

    Variables that share a qubit are assigned different noncommuting Pauli observables. Variables that interact in the QUBO are kept on different qubits, while variables with no direct QUBO interaction can share a qubit. The reduction in qubit count therefore depends on the structure of the problem.

  2. Quantum sampling: A quantum processor samples bitstrings from circuits designed to explore low-energy states of the relaxed Hamiltonian.

  3. Sample-based diagonalization: The solver projects the relaxed Hamiltonian onto the subspace spanned by basis states obtained from the sampled bitstrings and diagonalizes the projected Hamiltonian classically [4], [5]. For a fixed set of sampled bitstrings, let Sr\mathcal{S}_r be the subspace spanned by rr distinct basis states, so dim⁡(Sr)≤r\dim(\mathcal{S}_r) \leq r. Its estimated ground-state energy is:

    Er=min⁡∣ψ⟩∈Sr⟨ψ∣ψ⟩=1⟨ψ∣H~∣ψ⟩,Sr⊆Sr′⟹Eexact≤Er′≤Er(r′≥r),E_r = \min_{\substack{|\psi\rangle \in \mathcal{S}_r \\ \langle\psi|\psi\rangle=1}} \langle\psi|\widetilde{H}|\psi\rangle, \qquad \mathcal{S}_r \subseteq \mathcal{S}_{r'} \Longrightarrow E_{\mathrm{exact}} \leq E_{r'} \leq E_r \quad (r' \geq r),

    where EexactE_{\mathrm{exact}} is the ground-state energy of the full relaxed Hamiltonian. Therefore, a larger retained subspace cannot increase the estimated ground-state energy.

    This ordering assumes the same sampling result and concerns the relaxed-Hamiltonian energy before rounding. It does not theoretically guarantee a better returned classical solution. However, in QRAO, a lower-energy state is heuristically expected to provide a better classical solution.

  4. Rounding: The relaxed state obtained from the diagonalization is converted into a classical binary assignment.

SQOA Solver surrounds this framework with classical processing. Before the quantum mapping, presolve applies reduction rules that can fix variables and simplify the remaining QUBO while preserving at least one optimal solution. After the diagonalization and rounding, a classical local search refines the assignment, and OMMX reconstructs and evaluates the complete solution.

Load the instance

QOBLIB distributes each MIS instance as a graph in the DIMACS .gph format, a line-based format in which the line starting with p gives the numbers of nodes and edges and each line starting with e defines an edge.

QOBLIB also publishes an optimal or best-known reference solution for each instance. The following functions download both from the QOBLIB repository. DIMACS labels nodes starting from 1, so the graph is relabeled to start from 0.

If you want to start from your own graph instead, skip the download and formulate the problem from your NetworkX graph in the same way.

QOBLIB_URL = "https://raw.githubusercontent.com/ZIB-AOPT/QOBLIB/refs/heads/main/07-independentset"


def fetch_qoblib_graph(name: str) -> nx.Graph:
    """Download a QOBLIB independent-set instance as a NetworkX graph."""
    with urllib.request.urlopen(
        f"{QOBLIB_URL}/instances/{name}.gph"
    ) as response:
        lines = response.read().decode().splitlines()
    graph = nx.Graph()
    for fields in map(str.split, lines):
        if fields and fields[0] == "p":
            # Header: p edge <number of nodes> <number of edges>
            graph.add_nodes_from(range(int(fields[2])))
        elif fields and fields[0] == "e":
            # DIMACS labels nodes from 1; relabel them to start from 0.
            graph.add_edge(int(fields[1]) - 1, int(fields[2]) - 1)
    return graph


def fetch_reference_objective(name: str) -> float:
    """Read the objective value of the QOBLIB reference solution."""
    with urllib.request.urlopen(
        f"{QOBLIB_URL}/solutions/{name}.opt.sol"
    ) as response:
        for line in response.read().decode().splitlines():
            if line.startswith("# Objective value"):
                return float(line.split("=")[1])
    raise ValueError(f"No objective value found for {name}.")


INSTANCE_NAME = "gen200_p0-9_44"
G = fetch_qoblib_graph(INSTANCE_NAME)
reference_objective = fetch_reference_objective(INSTANCE_NAME)

You use the objective value of the reference solution later to assess the result from SQOA Solver. This tutorial solves this problem on a device with 156 qubits.

print(
    f"{INSTANCE_NAME}: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges"
)
print(f"Reference objective: {reference_objective:g}")

Output:

gen200_p0-9_44: 200 nodes, 1990 edges
Reference objective: 44

Visualize the graph. Later, you also use it to verify that the nodes returned by SQOA Solver form an independent set.

layout = nx.forceatlas2_layout(
    G, max_iter=500, scaling_ratio=3.0, gravity=1.0, seed=7
)
node_size = max(8, min(60, 4000 / max(G.number_of_nodes(), 1)))

fig, ax = plt.subplots(figsize=(9, 7))
nx.draw_networkx_edges(G, layout, ax=ax, alpha=0.2, width=0.6)
nx.draw_networkx_nodes(
    G, layout, node_size=node_size, node_color="lightgray", ax=ax
)
ax.set_title(
    f"{INSTANCE_NAME}: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges"
)
ax.set_axis_off()
plt.show()

Output:

Output of the previous code cell

Formulate the QUBO

Build the binary linear formulation from the Background section as an OptimizationProblem with one binary variable per node, an objective that counts the selected nodes, and one constraint per edge. Then convert it with OptimizationProblemToQubo.

With a penalty weight of 2, the converter replaces each edge constraint with the penalty term 2xvxw2 x_v x_w and turns the maximization into a minimization, so the converted problem is QOBLIB's binary unconstrained formulation with its sign reversed. The QUBO's variable indices are the graph's node labels. The IndependentSet application class in qiskit_addon_opt_mapper.applications builds the same constrained model with IndependentSet(G).to_optimization_problem().

Finally, convert the QUBO to the dictionary format with to_problem_dict(). Because the problem is a binary QUBO, the request in Step 2 declares its variable domain as "binary". The function performs presolve, quantum mapping, and circuit construction after submission.

mis = OptimizationProblem(INSTANCE_NAME)
mis.binary_var_list(G.number_of_nodes(), name="x")
mis.maximize(linear={v: 1 for v in G.nodes})
for u, v in G.edges:
    mis.linear_constraint({u: 1, v: 1}, "<=", 1)

converter = OptimizationProblemToQubo(penalty=2)
qubo = converter.convert(mis)
problem_dict = to_problem_dict(qubo)

print(f"QUBO variables: {qubo.get_num_vars()}")
print(f"Dictionary entries: {len(problem_dict)}")

Output:

QUBO variables: 200
Dictionary entries: 2191

Step 2: Optimize the problem for quantum hardware execution

SQOA Solver performs the quantum mapping and hardware preparation inside the function. It first applies presolve to the QUBO, then maps the remaining variables to a relaxed Hamiltonian by using a QRAC-based encoding.

For quantum sampling, it constructs circuits based on the relaxed Hamiltonian and transpiles them for the selected backend. The problem-size and qubit reductions from presolve and encoding depend on the problem, so configure the solver without assuming a fixed reduction factor.

Configure the solver inputs

SQOA Solver requires two inputs:

  • problem, the dictionary from Step 1.
  • variable_type, which is "binary" for this QUBO.

It also accepts two optional inputs:

  • backend_name selects the QPU. When it is omitted or None, the function selects the least busy operational QPU that is accessible to your instance.
  • options is a dictionary that controls the sampling budget (shots), the classical seed (seed), session usage (session), the classical local search (postprocessing_level), and job tags (job_tags). If you increase shots, the sampling statistics can improve.

For all inputs, their default values, and validation rules, see the API reference.

This tutorial names the backend explicitly, uses the default values for shots, session, and postprocessing_level, and sets the classical seed. Collect the inputs in a dictionary so that you can pass them to solver.run() in Step 3.

# ibm_fez has 156 qubits
backend_name = (
    "ibm_fez"  # Omit this key or pass None for automatic selection.
)

sqoa_input = {
    "problem": problem_dict,
    "variable_type": "binary",
    "backend_name": backend_name,
}
sqoa_input["options"] = {
    "shots": 4096,
    "seed": 42,
    "session": False,
    "postprocessing_level": 1,
}

Step 3: Execute using primitives

Submit the job with the SQOA Solver connection created in Step 1. The function runs the IBM Quantum Sampler primitive on the selected backend, collects the measured bitstrings, and continues with the classical diagonalization and post-processing. You interact with a single function job.

job = solver.run(**sqoa_input)
print(f"Job ID: {job.job_id}")

Output:

Job ID: dcee8e26-b8ac-4dd7-a76e-5adb5bc388e5

To check the status of the workload, see Check job status. Internally, the function proceeds through mapping, hardware optimization, queue waiting, QPU execution, and post-processing phases.

job.status()

Output:

'INITIALIZING'

job.result() blocks until the job completes and returns a dictionary with the solution, its objective value and feasibility, and metadata that holds solver information and resource usage. For the full definition of the inputs and outputs, see the API reference.

result = job.result()

Step 4: Post-process the result and convert it to a classical format

Inspect the complete function output, map the returned assignment back to the graph, verify the selected nodes, and visualize the result.

Inspect the result

The result dictionary contains four top-level fields:

  • solution is the complete variable assignment, keyed by x_i, where i is the variable index (for example, x_17). In this tutorial, the variable index is the node label.
  • objective_value is the objective of that assignment evaluated on the submitted dictionary, that is, on the minimization QUBO.
  • feasible refers only to the variable domain, because the dictionary has no constraints.
  • metadata contains solver_info and resource_usage. solver_info records the effective seed of the run. If the classical local search ran, solver_info also records the rounded assignment from before that refinement in solution_before_postprocessing.
solver_info = result["metadata"]["solver_info"]

print(f"Result fields: {list(result)}")
print(f"Solution entries: {len(result['solution'])}")
print(f"First entries: {dict(list(result['solution'].items())[:3])}")
print(f"Objective value: {result['objective_value']}")
print(f"Feasible: {result['feasible']}")
print(f"Solver-info fields: {list(solver_info)}")
print(f"Effective seed: {solver_info['seed']}")
print(f"Resource-usage phases: {list(result['metadata']['resource_usage'])}")

Output:

Result fields: ['solution', 'objective_value', 'feasible', 'metadata']
Solution entries: 200
First entries: {'x_0': 0.0, 'x_1': 0.0, 'x_2': 1.0}
Objective value: -35.0
Feasible: True
Solver-info fields: ['seed', 'solution_before_postprocessing']
Effective seed: 42
Resource-usage phases: ['RUNNING: MAPPING', 'RUNNING: OPTIMIZING_FOR_HARDWARE', 'RUNNING: WAITING_FOR_QPU', 'RUNNING: EXECUTING_QPU', 'RUNNING: POST_PROCESSING']

Map the solution to the graph

Collect the returned values in the order of the variable indices, and map them back to the variables of the original problem with interpret(). The selected nodes are the variables with the value 1, and is_feasible() checks the assignment against the edge constraints of the original problem.

x_qubo = [result["solution"][f"x_{i}"] for i in range(qubo.get_num_vars())]
x = converter.interpret(x_qubo)
selected_nodes = [node for node, value in enumerate(x) if value > 0.5]

print(f"Selected nodes: {len(selected_nodes)}")
print(f"Satisfies every edge constraint: {mis.is_feasible(x)}")

Output:

Selected nodes: 35
Satisfies every edge constraint: True

Compare with the solution before post-processing

Because postprocessing_level is 1, solver_info also records the rounded assignment from before the classical local search in solution_before_postprocessing. It uses the same keys as solution.

Evaluate it on the QUBO to see how much the refinement improved the result. Both values are objectives of the submitted minimization QUBO, so a lower value is better. The field is omitted when the local search does not run, for example, when presolve fixes every variable.

before = solver_info.get("solution_before_postprocessing")
if before is None:
    print("The classical local search did not run for this problem.")
else:
    x_before = [before[f"x_{i}"] for i in range(qubo.get_num_vars())]
    changed = sum(
        before[key] != value for key, value in result["solution"].items()
    )
    print(
        f"Objective before local search: {qubo.objective.evaluate(x_before)}"
    )
    print(f"Objective after local search: {result['objective_value']}")
    print(f"Variables changed by local search: {changed}")

Output:

Objective before local search: -12.0
Objective after local search: -35.0
Variables changed by local search: 19

Verify the independence constraint

Because the QUBO represents adjacency through penalty terms rather than explicit constraints, a heuristic result can still select adjacent nodes. Check the returned nodes directly against the graph. For the comparison and visualization in the following sections, copy the returned set and greedily remove one endpoint of each detected conflict.

returned_set = set(selected_nodes)
conflicts = [
    (u, v) for u, v in G.edges if u in returned_set and v in returned_set
]
print(f"Conflicting edges in the returned set: {len(conflicts)}")

independent_set = returned_set.copy()
for u, v in conflicts:
    if u in independent_set and v in independent_set:
        # Drop the endpoint that has more selected neighbors.
        drop = max(
            (u, v),
            key=lambda node: sum(nb in independent_set for nb in G[node]),
        )
        independent_set.remove(drop)

assert all(
    not (u in independent_set and v in independent_set) for u, v in G.edges
)
print(f"Independent set size after repair: {len(independent_set)}")

Output:

Conflicting edges in the returned set: 0
Independent set size after repair: 35

Compare with the reference solution

In Step 1, you downloaded the objective value of the QOBLIB reference solution. QOBLIB reference solutions can be optimal or best-known, so compare against the reference objective without assuming that it is always a certified optimum.

For this maximization problem, a ratio of 1.0 means that the repaired result matches the reference objective.

ratio_to_reference = len(independent_set) / reference_objective

print(f"Reference objective: {reference_objective:g}")
print(f"Ratio to reference: {ratio_to_reference:.3f}")

Output:

Reference objective: 44
Ratio to reference: 0.795

Visualize the result

Plot the graph again with the repaired independent set highlighted. No two highlighted nodes share an edge.

fig, ax = plt.subplots(figsize=(9, 7))
nx.draw_networkx_edges(G, layout, ax=ax, alpha=0.2, width=0.6)
nx.draw_networkx_nodes(
    G,
    layout,
    nodelist=[v for v in G if v not in independent_set],
    node_size=node_size,
    node_color="lightgray",
    ax=ax,
)
nx.draw_networkx_nodes(
    G,
    layout,
    nodelist=sorted(independent_set),
    node_size=node_size * 1.4,
    node_color="tab:blue",
    ax=ax,
)
ax.set_title(
    f"Independent set size: {len(independent_set)} "
    f"(reference: {reference_objective:g})"
)
ax.set_axis_off()
plt.show()

Output:

Output of the previous code cell

Read the resource usage

The result metadata reports CPU_TIME for phases other than RUNNING: EXECUTING_QPU, when it is available. The RUNNING: EXECUTING_QPU phase can report QPU_TIME, the QPU usage returned by Quantum Compute, if the final usage is available for every Quantum Compute job.

usage = result["metadata"]["resource_usage"]

for phase, metrics in usage.items():
    line = f"{phase:36s}"
    if "CPU_TIME" in metrics:
        line += f" CPU {metrics['CPU_TIME']:8.1f} s"
    if "QPU_TIME" in metrics:
        line += f" QPU {metrics['QPU_TIME']:8.1f} s"
    print(line)

Output:

RUNNING: MAPPING                     CPU     11.8 s
RUNNING: OPTIMIZING_FOR_HARDWARE     CPU     15.4 s
RUNNING: WAITING_FOR_QPU             CPU     16.5 s
RUNNING: EXECUTING_QPU               QPU      3.0 s
RUNNING: POST_PROCESSING             CPU     55.8 s

Conclusion

In this tutorial, you downloaded an MIS instance from QOBLIB, formulated it as a QUBO with qiskit-addon-opt-mapper, connected to SQOA Solver, configured and submitted the workload, mapped the returned assignment back to the graph, and checked the selected nodes against the original graph.

You also compared the result with the QOBLIB reference solution, examined the contribution of the classical post-processing, and inspected the resource usage of each execution phase.

SQOA Solver combines presolve, QRAO with a QRAC-based encoding, sampling-circuit construction, hardware-aware transpilation, sample-based diagonalization, rounding, and classical refinement. This workflow can map more binary variables than a one-variable-per-qubit encoding would allow. The amount of reduction from presolve and encoding depends on the problem structure, and solution quality depends on the samples obtained.

The shots and postprocessing_level settings control the quantum sampling budget and the classical refinement, respectively, without changing the submission workflow.


Appendix: Submit the problem as an OMMX instance

SQOA Solver also accepts an ommx.v1.Instance from the OMMX SDK. The OMMX Quantum Benchmarks package distributes the QOBLIB problems as OMMX instances together with their reference solutions, so you can load the same QUBO without formulating it yourself.

With an OMMX input, objective_value is evaluated in the sense of the instance, and the result also contains the canonical ommx.v1.Solution. Install the package, which also installs the OMMX SDK:

# %pip install ommx-quantum-benchmarks

Load the binary_unconstrained model of the same instance. The dataset loader prints progress messages, so the code captures them to keep the output compact.

%%capture
from ommx_quantum_benchmarks.qoblib import IndependentSet

ommx_instance, _ = IndependentSet()("binary_unconstrained", INSTANCE_NAME)

SQOA Solver receives an OMMX instance as a standard padded Base64 string and returns the canonical solution in the same format. Define two helper functions for this conversion:

  • to_problem_payload() serializes an ommx.v1.Instance into the string that you pass as problem.
  • to_ommx_solution() decodes the canonical ommx.v1.Solution from the result.
import base64

from ommx.v1 import Instance, Solution


def to_problem_payload(instance: Instance) -> str:
    """Serialize an OMMX instance into the `problem` string for SQOA Solver."""
    return base64.b64encode(instance.to_bytes()).decode()


def to_ommx_solution(result: dict) -> Solution:
    """Decode the canonical OMMX solution from a SQOA Solver result."""
    payload = result["metadata"]["solver_info"]["ommx_solution"]
    return Solution.from_bytes(base64.b64decode(payload, validate=True))

Submit the instance with the same inputs as before, replacing only problem. Then decode the canonical solution and read the selected nodes by variable name.

ommx_job = solver.run(
    **{**sqoa_input, "problem": to_problem_payload(ommx_instance)}
)
ommx_result = ommx_job.result()

ommx_solution = to_ommx_solution(ommx_result)
x_values = ommx_solution.extract_decision_variables("x")
ommx_nodes = sorted(
    node for (node,), value in x_values.items() if value > 0.5
)

print(f"Objective value: {ommx_result['objective_value']}")
print(f"Selected nodes: {len(ommx_nodes)}")

Output:

Objective value: 35.0
Selected nodes: 35

Next steps

Recommendations

References

[1] Koch, T., Bernal Neira, D. E., Chen, Y., Cortiana, G., Egger, D. J., Heese, R., Hegade, N. N., Gomez Cadavid, A., Huang, R., Itoko, T., et al. (2026). Quantum optimization benchmarking library: The intractable decathlon. Nature Computational Science, 6, 653–671. DOI, arXiv:2504.03832

[2] Matsuyama, H., & Yamashiro, Y. (2025). Sampling-based quantum optimization algorithm with quantum relaxation. 2025 IEEE International Conference on Quantum Computing and Engineering (QCE), 1, 2323–2333. DOI, arXiv:2504.12629

[3] Fuller, B., Hadfield, C., Glick, J. R., Imamichi, T., Itoko, T., Thompson, R. J., Jiao, Y., Kagele, M. M., Blom-Schieber, A. W., Raymond, R., et al. (2024). Approximate solutions of combinatorial problems via quantum relaxations. IEEE Transactions on Quantum Engineering, 5, 1–15. DOI, arXiv:2111.03167

[4] Kanno, K., Kohda, M., Imai, R., Koh, S., Mitarai, K., Mizukami, W., & Nakagawa, Y. O. (2026). Quantum-selected configuration interaction: Classical diagonalization of Hamiltonians in subspaces selected by quantum computers. Physical Review Research, 8(2), 023268. DOI, arXiv:2302.11320

[5] Robledo-Moreno, J., Motta, M., Haas, H., Javadi-Abhari, A., Jurcevic, P., Kirby, W., et al. (2025). Chemistry beyond the scale of exact diagonalization on a quantum-centric supercomputer. Science Advances, 11(25), eadu9991. DOI, arXiv:2405.05068

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.