SQOA Solver - una Qiskit Function de JIJ
Consulta la referencia de la API.
Qiskit Functions es una función experimental disponible solo para los usuarios del Premium Plan, el Flex Plan y el On-Prem Plan (mediante la API de IBM Quantum Platform) de IBM Quantum®. Están en estado de versión preliminar y sujetas a cambios.
Descripción general
Con SQOA Solver de JIJ, puedes resolver problemas grandes de optimización combinatoria en computadoras cuánticas de IBM®. El solver se basa en el Sampling-based Quantum Optimization Algorithm with Quantum Relaxation (SQOA-QR) [1]. A diferencia de los algoritmos cuánticos que usan una correspondencia uno a uno entre variables de decisión clásicas y qubits, SQOA Solver puede manejar problemas de optimización con más variables que el número de qubits disponibles en los dispositivos cuánticos actuales. Además, su diseño basado en muestreo lo hace menos sensible al ruido físico del hardware cuántico actual. En conjunto, estas características hacen que SQOA Solver sea muy adecuado para problemas de optimización en esta etapa de la computación cuántica.
El solver maneja problemas de Quadratic Unconstrained Binary Optimization (QUBO) dados como una formulación matemática clásica, y asigna automáticamente la formulación clásica al entorno cuántico. También puedes convertir problemas con restricciones en problemas sin restricciones mediante el método de penalización.
Las siguientes secciones describen el workflow del solver, los resultados de benchmarks y ejemplos de extremo a extremo que modelan problemas con qiskit-addon-opt-mapper y, opcionalmente, Open Mathematical prograMming eXchange (OMMX). OMMX es un formato de datos abierto y un SDK que simplifica el intercambio de problemas de optimización entre herramientas de software.
Descripción
SQOA Solver implementa el Sampling-based Quantum Optimization Algorithm with Quantum Relaxation (SQOA-QR) y lo rodea con preprocesamiento y postprocesamiento clásicos. Esta sección explica el marco SQOA y en qué se diferencia del Quantum Approximate Optimization Algorithm (QAOA).
La siguiente figura muestra un esquema del workflow de SQOA Solver.
El marco SQOA
SQOA es un marco basado en muestreo para la optimización combinatoria [1]. Combina dos técnicas: Quantum Random Access Optimization (QRAO) [2], que codifica el problema en una relajación cuántica, y la diagonalización cuántica basada en muestras (SQD) [3], también conocida como interacción de configuraciones seleccionada cuánticamente (QSCI) [4], que usa la computadora cuántica solo para muestrear cadenas de bits y encuentra el estado de baja energía en una computadora clásica. SQOA es un marco en el sentido de que fija esta combinación pero deja los detalles a cada implementación, como la forma de asignar las variables a los qubits y qué circuitos generan las muestras.
-
Relajación cuántica con QRAO. QAOA y otros enfoques basados en Ising representan cada variable binaria mediante el operador de su qubit. QRAO usa también los operadores e . Con un código de acceso aleatorio cuántico (QRAC) (3,1), hasta tres variables binarias comparten un qubit, y cada variable se representa con un operador de Pauli distinto.
Entonces el QUBO se convierte en un hamiltoniano relajado que necesita menos qubits que el hamiltoniano de Ising. Las variables que interactúan en el QUBO deben colocarse en qubits distintos. Si compartieran un qubit, el producto de sus operadores de Pauli se reduciría a un solo operador sobre ese qubit (por ejemplo, ), y el hamiltoniano relajado ya no representaría su interacción.
Por lo tanto, la asignación de variables a qubits determina tanto el número de qubits como la calidad de la relajación, y SQOA Solver diseña esta asignación en su preprocesamiento.
-
Diagonalización cuántica basada en muestras (SQD). Primero, la computadora cuántica prepara un estado cuántico que tiene solapamiento con los estados de baja energía del hamiltoniano relajado y luego muestrea cadenas de bits de él. Una computadora clásica proyecta el hamiltoniano relajado sobre el subespacio generado por las cadenas de bits muestreadas y lo diagonaliza para aproximar el estado fundamental.
Como el hamiltoniano relajado no es diagonal, la diagonalización combina las cadenas de bits muestreadas en una superposición. Con un hamiltoniano de Ising diagonal, cada cadena de bits ya es un estado propio, y la diagonalización solo seleccionaría la mejor muestra. El mismo enfoque se ha aplicado a problemas a gran escala en química cuántica.
El estado fundamental aproximado obtenido por SQD no corresponde directamente a una solución clásica del problema original, por lo que la solución clásica debe decodificarse a partir de él. Dado que este estado se calcula en una computadora clásica, la decodificación, llamada redondeo, es un postprocesamiento clásico. SQOA Solver usa técnicas de decodificación diseñadas para obtener mejores soluciones clásicas de forma heurística.
La siguiente tabla compara SQOA con QAOA, un enfoque variacional común para la misma clase de problemas.
| QAOA | SQOA | |
|---|---|---|
| Qubits para variables binarias | Un qubit contiene hasta tres de las variables. El número exacto depende de la estructura del problema | |
| Uso de la QPU | Estima repetidamente la energía de circuitos parametrizados dentro de un bucle de optimización clásico | Solo muestrea cadenas de bits. No se entrena ningún parámetro de circuito en la QPU |
| De las muestras a una solución | Selecciona la mejor cadena de bits medida | Diagonaliza el hamiltoniano relajado en el subespacio muestreado en una computadora clásica y redondea el estado resultante |
Alrededor de SQOA-QR, SQOA Solver añade procesamiento clásico. Su preprocesamiento fija las variables cuyos valores óptimos se pueden determinar de antemano y diseña la asignación de variables a qubits. Su postprocesamiento refina la solución redondeada con una búsqueda local, que puedes desactivar con postprocessing_level=0.
Benchmarks
El rendimiento puede depender tanto de la instancia del problema como de los pasos de procesamiento posteriores. En algunos casos, las muestras clásicas y las muestras generadas cuánticamente pueden alcanzar una calidad de solución final similar tras un postprocesamiento equivalente. Por lo tanto, la evaluación debe considerar el workflow de optimización completo.
Las siguientes métricas de benchmark ofrecen una indicación general de la calidad de la solución, la compresión de qubits y el tiempo de ejecución para tamaños y estructuras de problema seleccionados. La tabla enumera una selección de instancias de Maximum Independent Set de la Quantum Optimization Benchmarking Library (QOBLIB) y dos instancias de MaxCut 3-regulares. Puedes cargar las instancias de QOBLIB como objetos ommx.v1.Instance, junto con sus soluciones óptimas, usando el paquete ommx-quantum-benchmarks.
Cada instancia se ejecutó 10 veces en un procesador IBM Heron r2. Number of qubits cuenta los qubits realmente usados en el hardware tras el preprocesamiento y la codificación QRAC. La razón de compresión depende de la instancia. Approximation ratio (AR) indica la calidad de la mejor solución encontrada en las 10 ejecuciones:
donde es el mejor valor objetivo encontrado y es el valor objetivo óptimo conocido. Todas las instancias son problemas de maximización con un valor óptimo positivo, por lo que AR es como máximo 100%, y un 100% en la columna Approximation ratio indica que el solver encontró una solución óptima. Total time es el tiempo medio combinado de QPU y CPU por ejecución. QPU time es el uso medio de IBM Quantum Compute por ejecución.
| Instancia | Número de variables | Número de qubits | Razón de aproximación | Tiempo total (segundos) | Tiempo de QPU (segundos) |
|---|---|---|---|---|---|
| maxcut_384_nodes | 384 | 129 | 98.5% | 62.1 | 12.0 |
| gen200_p0-9_44 | 200 | 67 | 100% | 38.0 | 12.0 |
| es60fst02 | 186 | 42 | 100% | 24.7 | 12.0 |
| es60fst04 | 162 | 29 | 100% | 17.9 | 12.0 |
| maxcut_150_nodes | 150 | 51 | 100% | 23.1 | 12.0 |
| sloane_1dc_128 | 128 | 37 | 93.8% | 19.1 | 12.0 |
| sloane_2dc_128 | 128 | 8 | 100% | 12.3 | 11.5 |
| C125-9 | 125 | 43 | 100% | 22.3 | 12.0 |
| es60fst01 | 123 | 12 | 100% | 13.6 | 12.0 |
| es60fst03 | 113 | 26 | 100% | 14.9 | 12.0 |
| sloane_1dc_64 | 64 | 16 | 100% | 13.4 | 11.9 |
| chesapeake | 39 | 13 | 100% | 13.1 | 12.0 |
| football | 35 | 6 | 100% | 12.5 | 12.0 |
| karate | 34 | 2 | 100% | 6.2 | 6.0 |
| ibm32 | 32 | 11 | 100% | 12.9 | 12.0 |
| farm | 17 | 4 | 100% | 8.5 | 8.0 |
Primeros pasos
Encuentra la información de acceso a esta Qiskit Function en el Qiskit Functions Catalog.
Esta sección muestra cómo autenticarte y cargar la función, y cómo preparar un problema en el formato de diccionario que acepta SQOA Solver usando OptimizationProblem de qiskit-addon-opt-mapper. Los ejemplos siguientes ejecutan la función y leen el resultado.
Autenticarse y cargar la función
Primero, autentícate con tu clave de API de IBM Quantum. (El siguiente código asume que ya guardaste tu cuenta en tu entorno local.) Luego, crea un cliente de catálogo de la siguiente manera:
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"jijmodeling": "jijmodeling", "networkx": "networkx", "ommx": "ommx", "qiskit_addon_opt_mapper": "qiskit-addon-opt-mapper", "qiskit_ibm_catalog": "qiskit-ibm-catalog"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit_ibm_catalog import QiskitFunctionsCatalog
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# verify that you have access to the function
catalog.list()
[QiskitFunction(jij/sqoa-solver)]
solver = catalog.load("jij/sqoa-solver")
Preparar la entrada con qiskit-addon-opt-mapper
Esta guía modela problemas con la clase OptimizationProblem de qiskit-addon-opt-mapper. SQOA Solver acepta un problema QUBO como un diccionario de coeficientes en el formato de tupla-cadena usado por las funciones de optimización de IBM, por lo que todos los ejemplos de esta guía siguen el mismo patrón de preparación en el lado del cliente:
-
Escribe el modelo. Crea un
OptimizationProblem, añade variables de decisión binarias y define el objetivo y las restricciones. -
Convierte a QUBO. Convierte el problema con
OptimizationProblemToQubo, que añade las restricciones al objetivo como términos de penalización y devuelve un problema de minimización sin restricciones equivalente. -
Convierte a un diccionario. Pasa el QUBO a la función auxiliar
to_problem_dict()definida en la siguiente sección y pasa el resultado asolver.run()comoproblem, junto convariable_type="binary".
SQOA Solver siempre minimiza un problema en formato de diccionario, lo cual coincide con el problema de minimización que devuelve OptimizationProblemToQubo. Para un problema de maximización, el objective_value devuelto es por lo tanto el negativo del objetivo original. Para leer el resultado en términos del problema original, evalúa la asignación devuelta con OptimizationProblem, como muestran los ejemplos. Para los detalles del formato de diccionario, consulta Accepted problem formats en la referencia de la API.
# %pip install qiskit-addon-opt-mapper
Define la siguiente función auxiliar una vez y reutilízala en cada ejemplo. Convierte el QUBO devuelto por OptimizationProblemToQubo al formato de diccionario. Las claves usan los índices de variable del QUBO, por lo que las claves de solución x_0, x_1, etc. del resultado corresponden a esos índices. Cada variable recibe una entrada lineal, incluso con coeficiente cero, para que la solución devuelta cubra todas las variables.
from qiskit_addon_opt_mapper import OptimizationProblem
from qiskit_addon_opt_mapper.converters import OptimizationProblemToQubo
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()}
Ejemplo 1: MaxCut en un grafo 3-regular
Muchos problemas de grafos, como MaxCut, son NP-difíciles y son candidatos naturales para la optimización cuántica. Instala networkx como dependencia adicional para ejecutar este ejemplo:
# %pip install networkx
1. Escribe el modelo
Asigna una variable binaria a cada vértice de un grafo para indicar su lado del corte. Una arista se corta cuando sus extremos están en lados distintos, por lo que el tamaño del corte es:
Este objetivo ya es un QUBO sin restricciones, por lo que OptimizationProblemToQubo solo convierte la maximización en la minimización equivalente y mantiene las variables sin cambios. La clase de aplicación Maxcut en qiskit_addon_opt_mapper.applications construye el mismo modelo con Maxcut(G).to_optimization_problem().
import networkx as nx
G = nx.random_regular_graph(3, 40, seed=42)
linear = {v: 0 for v in G.nodes}
quadratic = {}
for u, v in G.edges:
linear[u] += 1
linear[v] += 1
quadratic[(u, v)] = -2
maxcut = OptimizationProblem("MaxCut")
maxcut.binary_var_list(G.number_of_nodes(), name="x")
maxcut.maximize(linear=linear, quadratic=quadratic)
2. Ejecuta el solver
Convierte el modelo a un QUBO y luego a un diccionario, y envíalo. problem y variable_type son obligatorios. backend_name es opcional. Cuando se omite o es None, la función selecciona la QPU operativa menos ocupada disponible para tu instancia.
El diccionario options acepta shots, seed, session, postprocessing_level y job_tags. Para ver las descripciones y los valores predeterminados, consulta la referencia de la API.
qubo = OptimizationProblemToQubo().convert(maxcut)
job = solver.run(
problem=to_problem_dict(qubo),
variable_type="binary",
backend_name="ibm_fez",
options={
"shots": 4096,
"seed": 42,
},
)
3. Recupera el resultado
job.result() devuelve un diccionario con cuatro claves:
-
solutiones la asignación completa de variables, con clavesx_i, dondeies el índice de la variable (por ejemplo,x_3). -
objective_valueyfeasibleson el objetivo y la factibilidad de esa asignación, evaluados sobre el diccionario enviado. -
metadatacontienesolver_infoyresource_usage.
Dado que el QUBO es la forma de minimización de MaxCut, objective_value es el negativo del tamaño del corte. Evalúa la asignación sobre el OptimizationProblem original para leer directamente el tamaño del corte.
result = job.result()
x = [result["solution"][f"x_{i}"] for i in range(maxcut.get_num_vars())]
print(f"Cut value: {maxcut.objective.evaluate(x)}")
print(f"Nodes on side 1: {[v for v, value in enumerate(x) if value > 0.5]}")
Cut value: 55.0
Nodes on side 1: [5, 7, 9, 11, 14, 16, 17, 19, 21, 23, 24, 26, 30, 31, 33, 34, 36, 37, 38]
4. Comprueba el uso de recursos
result["metadata"]["resource_usage"] informa del tiempo empleado en cada fase de ejecución. Las fases distintas de RUNNING: EXECUTING_QPU informan de CPU_TIME, el tiempo real de la fase en segundos, cuando está disponible.
La fase RUNNING: EXECUTING_QPU informa de QPU_TIME, el uso de QPU de todos los trabajos de Quantum Compute de la ejecución, si el uso final está disponible para cada trabajo. Para más detalles, consulta la referencia de la API.
for phase, metrics in result["metadata"]["resource_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)
RUNNING: MAPPING CPU 3.2 s
RUNNING: OPTIMIZING_FOR_HARDWARE CPU 5.0 s
RUNNING: WAITING_FOR_QPU CPU 1.1 s
RUNNING: EXECUTING_QPU QPU 3.0 s
RUNNING: POST_PROCESSING CPU 0.9 s
Ejemplo 2: Convertir un problema con restricciones a QUBO
SQOA Solver acepta problemas sin restricciones. Este ejemplo muestra cómo convertir un problema con restricciones en un QUBO con OptimizationProblemToQubo y volver a asignar el resultado a las variables del problema original.
selection = OptimizationProblem("ConstrainedExample")
selection.binary_var_list(2, name="x")
selection.maximize(linear={"x0": 1, "x1": 1})
selection.linear_constraint({"x0": 1, "x1": 1}, "<=", 1, name="at_most_one")
print(selection.prettyprint())
Problem name: ConstrainedExample
Maximize
x0 + x1
Subject to
Linear constraints (1)
x0 + x1 <= 1 'at_most_one'
Binary variables (2)
x0 x1
OptimizationProblemToQubo añade las restricciones al objetivo como términos de penalización y convierte el problema a minimización. El peso de penalización adecuado depende del problema.
Tras la ejecución, interpret() vuelve a asignar la asignación del QUBO a las variables del problema original.
Evalúa el objetivo y la factibilidad en el problema original. Para un problema en formato de diccionario, el campo feasible del resultado se refiere solo al dominio de las variables, porque el diccionario no tiene restricciones.
converter = OptimizationProblemToQubo(penalty=4.0)
qubo = converter.convert(selection)
job = solver.run(
problem=to_problem_dict(qubo),
variable_type="binary",
backend_name="ibm_fez",
)
result = job.result()
x_qubo = [result["solution"][f"x_{i}"] for i in range(qubo.get_num_vars())]
x = converter.interpret(x_qubo)
print("Objective:", selection.objective.evaluate(x))
print("Feasible:", selection.is_feasible(x))
print("x values:", x)
Objective: 1.0
Feasible: True
x values: [1. 0.]
Ejemplo 3: Enviar una instancia de OMMX
SQOA Solver también acepta un ommx.v1.Instance del SDK de OMMX, que mantiene juntos los nombres de variables, los subíndices y los metadatos de conversión con el problema. En comparación con un diccionario, una entrada OMMX tiene las siguientes ventajas:
-
El resultado también contiene el
ommx.v1.Solutioncanónico ensolver_info["ommx_solution"]. -
objective_valuese evalúa en el sentido de la instancia, por lo que el objetivo de un problema de maximización no se niega. -
Para una instancia convertida en el lugar con
Instance.to_qubo(),feasiblese evalúa respecto a las restricciones originales.
Este ejemplo construye el modelo MaxCut del Ejemplo 1 con JijModeling, un modelador de optimización matemática que compila modelos en instancias de OMMX. Instala JijModeling, que también instala el paquete ommx:
# %pip install "jijmodeling~=2.8.0"
SQOA Solver intercambia datos OMMX como cadenas Base64 estándar con relleno. Define las siguientes funciones auxiliares para esta conversión:
-
to_problem_payload()serializa unommx.v1.Instanceen la cadena que pasas comoproblem. -
to_ommx_solution()decodifica elommx.v1.Solutioncanónico del resultado.
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))
1. Escribe el modelo con JijModeling
La formulación de espín habitual de MaxCut en un grafo asigna a cada vértice. Una arista se corta cuando , por lo que el tamaño del corte es:
JijModeling no proporciona una variable de decisión de espín nativa, y SQOA Solver acepta variables binarias de QUBO. Por lo tanto, declara y define la expresión con valores de espín dentro del modelo. Con esta expresión, el objetivo se mantiene en la forma de espín estándar y la instancia compilada sigue siendo un QUBO binario.
import jijmodeling as jm
@jm.Problem.define("MaxCut", sense=jm.ProblemSense.MAXIMIZE)
def maxcut_model(problem: jm.DecoratedProblem):
V = problem.Natural() # number of nodes
E = problem.Graph(dtype=V) # edge list
b = problem.BinaryVar(shape=(V,))
s = 1 - 2 * b # spin-valued expression
problem += jm.sum((1 - s[u] * s[v]) / 2 for u, v in E)
Si muestras maxcut_model en un notebook, aparece la siguiente formulación:
2. Compila la instancia y ejecuta el solver
Enlaza el grafo del Ejemplo 1 para compilar un ommx.v1.Instance y envíalo con variable_type="binary". Una instancia de OMMX siempre se envía como un problema binario.
instance = maxcut_model.eval(
{"V": G.number_of_nodes(), "E": [list(edge) for edge in G.edges]}
)
job = solver.run(
problem=to_problem_payload(instance),
variable_type="binary",
backend_name="ibm_fez",
)
3. Recupera el resultado
Para una entrada OMMX, objective_value es el tamaño del corte, y to_ommx_solution() decodifica la solución canónica para que puedas leer la asignación por nombre de variable.
result = job.result()
print(f"Cut value: {result['objective_value']}")
solution = to_ommx_solution(result)
b_values = solution.extract_decision_variables("b")
print(
f"Nodes on side 1: {sorted(v for (v,), value in b_values.items() if value > 0.5)}"
)
Cut value: 55.0
Nodes on side 1: [5, 7, 9, 11, 14, 16, 17, 19, 21, 23, 24, 26, 30, 31, 33, 34, 36, 37, 38]
Obtener soporte
Para obtener soporte, escribe un correo a info@j-ij.com.
Próximos pasos
- Solicita una prueba gratuita de SQOA Solver completando este formulario.
- Visita la referencia de la API de esta Qiskit Function.
- Prueba un ejemplo de extremo a extremo en el tutorial del problema del conjunto independiente máximo.
- Aprende a modelar problemas de optimización con qiskit-addon-opt-mapper.
- Aprende a modelar e intercambiar problemas de optimización con JijModeling y OMMX.
- Lee el artículo de SQOA-QR [1] para saber más sobre el marco SQOA.
Referencias
[1] 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
[2] 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
[3] 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
[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