Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Constrained Quantum Optimizer: una función de Qiskit de Aqarios

Consulta la referencia de la API

nota

Las Qiskit Functions son una función experimental disponible únicamente para los usuarios de los planes IBM Quantum® Premium Plan, Flex Plan y On-Prem (a través de la API de IBM Quantum Platform). Se encuentran en estado de versión preliminar y están sujetas a cambios.

Información general​

Con el Constrained Quantum Optimizer de Aqarios, puedes resolver problemas de optimización binaria con restricciones en hardware cuántico de IBM® sin necesidad de reformulación manual ni de mapeo de circuitos. Simplemente proporciona un problema como archivo LP, archivo MPS o un Luna Model. La función se encarga automáticamente de la reformulación, la síntesis de circuitos, la transpilación y la ejecución en el hardware. El solucionador utiliza el arranque en caliente iterativo [1] del algoritmo FlexQAOA, una extensión del Quantum Approximate Optimization Algorithm (QAOA) que incorpora las restricciones de forma nativa.

El optimizador se centra en problemas binarios con restricciones. En particular, las restricciones one-hot y las restricciones de set-packing pueden aplicarse de forma nativa mediante el uso de mezcladores XY. Otros tipos de restricciones se reformulan automáticamente como términos de penalización.

Tipos de variables admitidos

La función resuelve únicamente problemas discretos. Toda variable de decisión debe ser binaria, o entera con un límite inferior y superior explícitos, de modo que pueda codificarse en qubits. Las variables continuas (de valor real) no se admiten: un modelo que las contenga se rechaza con el código de error 4710.

Descripción​

Aviso

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 después de un posprocesamiento equivalente. Por lo tanto, la evaluación debe considerar el flujo de trabajo de optimización completo.

El Constrained Quantum Optimizer implementa el Iterative warm-start XY-mixer QAOA (IWS-QAOA) [1]. El algoritmo ejecuta circuitos de QAOA de ángulo fijo en hardware cuántico y actualiza las probabilidades iniciales de arranque en caliente de cada circuito utilizando los resultados de medición de la iteración anterior. No se requiere entrenamiento de parámetros variacionales; en su lugar, se elige una parametrización de rampa lineal fija.

Al utilizar mezcladores XY para aplicar restricciones one-hot, puedes mejorar drásticamente la calidad de la solución en problemas con restricciones, ya que el espacio de búsqueda se reduce prácticamente a un subespacio factible más pequeño — y no se introduce complejidad adicional, debida a los términos de penalización, en el panorama de optimización.

Flujo de trabajo​

El flujo de trabajo de la función se describe a continuación:

  1. Preprocesamiento: Antes de la síntesis del circuito, el optimizador analiza la estructura de restricciones del problema de entrada:

    • Preprocesamiento de grafos: Cuando es aplicable al problema, este se analiza como un grafo, donde las aristas determinan la relación entre las variables. Este análisis de grafos permite dos mejoras de preprocesamiento del problema. Primero, las restricciones individuales pueden agregarse en una única restricción grande mediante la fusión de cliqués (clique merging), de modo que se representen mediante un único mezclador XY. Segundo, las variables que tienen un único vecino pueden fijarse de forma determinista, reduciendo el tamaño y la complejidad del problema.

    • Resolución de restricciones: El preprocesador maneja cada restricción por separado: si la restricción es una restricción one-hot, puede aplicarse mediante mezcladores XY. Si es una restricción de set-packing, primero se transforma en una restricción one-hot. Las restricciones de igualdad se manejan mediante penalizaciones cuadráticas, y las restricciones de desigualdad mediante penalización no balanceada.

    • Derivación de penalizaciones: Las restricciones que no pueden expresarse como restricciones de igualdad one-hot se convierten en términos de penalización. El optimizador deriva automáticamente la intensidad de la penalización a partir de la estructura del problema.

  2. Arranque en caliente iterativo: El optimizador ejecuta múltiples cadenas paralelas de arranque en caliente. Cada cadena mantiene una distribución de probabilidad sobre las asignaciones de variables, que se utiliza como la probabilidad inicial del circuito de QAOA (estado de arranque en caliente). En cada iteración, una cadena muestrea el circuito cuántico, aplica posprocesamiento clásico y actualiza su distribución en función de la aparición de las muestras. A lo largo de las iteraciones, esto centra la búsqueda en soluciones de alta calidad sin un bucle de optimización variacional.

El número de cadenas (num_parallel), el presupuesto total de shots por cadena (total_shots) y el número de repeticiones de QAOA (reps) son los principales controles para equilibrar la calidad de la solución frente al tiempo de ejecución.

Workflow diagram of the Constrained Quantum Optimizer

Benchmarks​

Los siguientes benchmarks muestran que la Qiskit Function Constrained Quantum Optimizer es capaz de encontrar soluciones factibles de alta calidad para problemas de optimización con restricciones con bastante más de 100 variables binarias. La precisión (Accuracy) es la relación entre el valor objetivo encontrado y el óptimo conocido. El tiempo de ejecución (Runtime) es el tiempo real transcurrido desde la invocación de la función hasta la entrega del resultado. El uso de QPU es el tiempo de cómputo cuántico facturable.

Se examinaron dos problemas: el problema del Conjunto Independiente Máximo (MIS) y el Max-kk-Cut, que es una extensión del conocido problema MaxCut a más de dos categorías. Max-kk-Cut es también el equivalente, como problema de optimización, del problema de decisión de coloreado de grafos (cuando se cortan todas las aristas, el grafo es kk-coloreable).

ProblemaVariablesRestriccionesQubitsPrecisiónFactibilidadTiempo total de ejecución (s)Uso de QPU (s)Total de shots
MIS es60fst02186280124100.0%Verdadero940.2147.025.0k
MIS sloane_2dc_128128517374100.0%Verdadero347.6106.012.5k
Max-3-Cut Karate Club99339998.7%Verdadero279.5166.025.0k
Max-4-Cut Karate Club13233132100.0%Verdadero258.3200.025.0k

Notas de los benchmarks​

  • Los resultados anteriores se obtuvieron en el procesador Heron r2 ibm_marrakesh con las opciones predeterminadas y use_session habilitado. Para la instancia sloane_2dc_128, se utilizó total_shots=12500.

  • El número reducido de qubits para las instancias de MIS (74 y 124 frente a 128 y 186 variables) refleja la fijación de variables durante el preprocesamiento.

  • Las instancias del problema para MIS se toman de la Quantum Optimization Benchmarking Library. - La instancia para el problema Max-kk-Cut es el grafo del Zachary Karate Club, que tiene un número cromático de 5.

Primeros pasos​

Esta guía demuestra el uso básico del Constrained Quantum Optimizer mostrando cómo cargar la función y ejecutar problemas simples. Para un tutorial más detallado, consulta Encuentra el conjunto independiente máximo con el Constrained Quantum Optimizer de Aqarios.

Autentícate con tu clave de API de IBM Quantum y el CRN de la instancia, luego carga la función desde el catálogo.

nota

El siguiente código supone que ya has guardado tus credenciales. Para configurarlas, sigue las instrucciones en Guarda las credenciales de tu cuenta de IBM Cloud.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"luna_model": "luna-model", "networkx": "networkx", "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(aqarios/constrained-quantum-optimizer)]
# Load the function
optimizer = catalog.load("aqarios/constrained-quantum-optimizer")
# Check the list of backends you have access to
catalog.backends()
[<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_boston')>,
<IBMBackend('ibm_phoenix')>,
<IBMBackend('ibm_fez')>,
<IBMBackend('ibm_miami')>,
<IBMBackend('ibm_marrakesh')>,
<IBMBackend('ibm_kingston')>]
# Select the backend you want to use
backend = catalog.backend("ibm_phoenix")

Ejemplo 1: Un problema simple con restricciones​

El primer ejemplo muestra cómo usar la función con archivos LP, una especificación común de problemas de optimización utilizada en investigación operativa. Formularás el siguiente problema de optimización arbitrario de cinco variables binarias xi∈{0,1}x_i \in \{0,1\} sujeto a dos restricciones:

min⁡x1x2+2x2x3−x3x4−2x4x5−2x1x3−4x2x5s.t.x1+x2+x3≤1x3−x4+x5=0 \min x_1 x_2 + 2 x_2 x_3 - x_3 x_4 - 2 x_4 x_5 - 2 x_1 x_3 - 4 x_2 x_5 \\ \text{s.t.}\quad x_1 + x_2 + x_3 \leq 1 \\ \quad x_3 - x_4 + x_5 = 0

1. Formula el problema​

Indica directamente la expresión matemática con el siguiente formato de cadena LP:

lp_str = """\\Problem name: Example
Minimize
obj: [ 2 x_1 x_2 + 4 x_2 x_3 - 2 x_3 x_4 - 4 x_4 x_5 - 4 x_1 x_3 - 8 x_2 x_5 ] / 2
Subject To
c1: x_1 + x_2 + x_3 <= 1
c2: x_3 - x_4 + x_5 = 0
Binaries
x_1 x_2 x_3 x_4 x_5
End
"""

Ten en cuenta que [ ... ] / 2 es una notación requerida para los objetivos cuadráticos según el estándar LP.

2. Ejecuta el optimizador​

Resuelve el problema con el optimizador.

# Set up low-resource options for the simple problem
options = {"total_shots": 2000, "num_parallel": 5, "use_session": True}

# Dispatch the job
job_1 = optimizer.run(
problem=lp_str, backend_name=backend.name, options=options
)

print(f"Job ID: {job_1.job_id}")
Job ID: 2e06a3a8-7c29-4c76-94d7-194293363f4a
# Monitor the job status
job_1.status()
'RUNNING: WAITING_FOR_QPU'

3. Obtén el resultado​

El resultado proporciona la solución, que asigna a cada nombre de variable su valor asociado.

result = job_1.result()

result
{'solutions': [{'x_1': 0, 'x_2': 1, 'x_3': 0, 'x_4': 1, 'x_5': 1}],
'solution_bitstrings': ['01011'],
'raw_energy': -6.0,
'objective_value': -6.0,
'feasible': True,
'metadata': {'resource_usage': {'RUNNING: MAPPING': {'CPU': 5.405},
'RUNNING: OPTIMIZING_FOR_HARDWARE': {'CPU': 9.902},
'RUNNING: WAITING_FOR_QPU': {'CPU': 25.456},
'RUNNING: EXECUTING_QPU': {'CPU': 25.202},
'RUNNING: POST_PROCESSING': {'CPU': 6.481}},
'circuit_metrics': {'before_transpilation': {'num_qubits': 6,
'gate_count': 42,
'depth': 15,
'two_qubit_gate_count': 12,
'two_qubit_gate_depth': 7,
'operations': {'rz': 12,
'p': 8,
'ry': 7,
'rzz': 4,
'cx': 3,
'xx_plus_yy': 3,
'global_phase': 2,
'cry': 2,
'x': 1}},
'after_transpilation': {'num_qubits': 6,
'gate_count': 254,
'depth': 100,
'two_qubit_gate_count': 48,
'two_qubit_gate_depth': 33,
'operations': {'rz': 78, 'sx': 122, 'cz': 48, 'measure': 6, 'x': 0}}},
'solver_info': {'variable_mapping': {'x_1': 0,
'x_2': 1,
'x_3': 2,
'x_4': 3,
'x_5': 4}}}}

El campo solutions del diccionario devuelto contiene todas las mejores soluciones degeneradas encontradas, como diccionarios que asignan a cada nombre de variable su valor. Las mismas soluciones también se devuelven como cadenas de bits en solution_bitstrings; metadata.solver_info.variable_mapping te indica qué posición de carácter de una cadena de bits pertenece a cada variable. Junto con los datos de la solución, se proporcionan el objective_value, un indicador feasible y metadata adicionales.

Ejemplo 2: Conjunto independiente máximo​

El problema del Conjunto Independiente Máximo (MIS) busca el subconjunto más grande de vértices de un grafo tal que ningún par de vértices del subconjunto comparta una arista. Este ejemplo resuelve el MIS en un grafo regular de 12 nodos. Para más información sobre el problema y un ejemplo más extenso, consulta el tutorial.

El problema se construye con qiskit-addon-opt-mapper. Instala los paquetes necesarios si es preciso:

# %pip install networkx qiskit-addon-opt-mapper

Paso 1: Define el problema​

Construye un grafo aleatorio 3-regular y crea el problema de optimización MIS. Cada nodo del grafo se convierte en una variable binaria, y cada arista introduce una restricción que garantiza que como máximo se seleccione uno de sus extremos.

import networkx as nx
from qiskit_addon_opt_mapper.applications import IndependentSet
from qiskit_addon_opt_mapper.translators import to_docplex_mp

# 12-node random 3-regular graph
G = nx.random_regular_graph(3, 12, seed=7)

# Build MIS problem and export as LP string
mis = IndependentSet(G)
mis_problem = mis.to_optimization_problem()
lp_str = to_docplex_mp(mis_problem).export_as_lp_string()

print(lp_str)
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: Independent set

Maximize
obj: x_0 + x_1 + x_2 + x_3 + x_4 + x_5 + x_6 + x_7 + x_8 + x_9 + x_10 + x_11
Subject To
c0: x_0 + x_4 <= 1
c1: x_0 + x_9 <= 1
c2: x_0 + x_8 <= 1
c3: x_1 + x_2 <= 1
c4: x_1 + x_7 <= 1
c5: x_1 + x_3 <= 1
c6: x_2 + x_4 <= 1
c7: x_2 + x_11 <= 1
c8: x_3 + x_10 <= 1
c9: x_3 + x_11 <= 1
c10: x_4 + x_5 <= 1
c11: x_5 + x_10 <= 1
c12: x_5 + x_6 <= 1
c13: x_6 + x_8 <= 1
c14: x_6 + x_7 <= 1
c15: x_7 + x_9 <= 1
c16: x_8 + x_11 <= 1
c17: x_9 + x_10 <= 1

Bounds
0 <= x_0 <= 1
0 <= x_1 <= 1
0 <= x_2 <= 1
0 <= x_3 <= 1
0 <= x_4 <= 1
0 <= x_5 <= 1
0 <= x_6 <= 1
0 <= x_7 <= 1
0 <= x_8 <= 1
0 <= x_9 <= 1
0 <= x_10 <= 1
0 <= x_11 <= 1

Binaries
x_0 x_1 x_2 x_3 x_4 x_5 x_6 x_7 x_8 x_9 x_10 x_11
End

Paso 2: Ejecuta el optimizador​

Resuelve el problema con el optimizador.

options = {"total_shots": 2000, "use_session": True}

# Dispatch the job
job_2 = optimizer.run(
problem=lp_str, backend_name=backend.name, options=options
)

print(f"Job ID: {job_2.job_id}")
Job ID: f041431c-b42e-4627-afe5-522fea61faf6
# Monitor the job status
job_2.status()
'QUEUED'

Paso 3: Obtén e interpreta los resultados​

Por último, obtenemos la solución y comprobamos que es correcta.

result = job_2.result()

print(f"Feasible: {result['feasible']}")
print(f"Independent set size: {int(result['objective_value'])}")

# Extract the selected nodes
solution = result["solutions"][0]
independent_set = [
int(k.split("_")[1]) for k, v in solution.items() if v == 1
]
print(f"Selected nodes: {sorted(independent_set)}")

# Verify no two selected nodes share an edge
conflicts = [
(u, v)
for u, v in G.edges()
if u in independent_set and v in independent_set
]
print(f"Edge conflicts: {conflicts}")
Feasible: True
Independent set size: 5
Selected nodes: [1, 4, 6, 9, 11]
Edge conflicts: []

Ejemplo 3: Coloreado de grafos con 3 colores (Max-3-Cut)​

El problema Max-kk-Cut divide los vértices de un grafo en kk grupos para maximizar el número de aristas cuyos extremos pertenecen a grupos diferentes. Para k=3k = 3, esto equivale a encontrar un coloreado de 3 colores que maximice las aristas entre colores distintos.

Formalmente, se asigna cada nodo i∈Vi \in V a uno de los kk grupos mediante variables binarias xi,cx_{i,c} para el color c∈{1,…,k}c \in \{1, \ldots, k\}. La restricción one-hot ∑cxi,c=1\sum_c x_{i,c} = 1 para cada nodo garantiza una asignación única. El objetivo maximiza las aristas entre grupos:

min⁡∑(u,v)∈E∑cxu,c xv,cs.t.∑cxi,c=1∀i∈V\min \sum_{(u,v) \in E} \sum_c x_{u,c} \, x_{v,c}\\ \text{s.t.} \sum_{c} x_{i,c} = 1 \quad \forall i \in V

Las restricciones one-hot son ideales para los mezcladores XY, que el optimizador maneja de forma nativa.

Este ejemplo utiliza el grafo del Zachary Karate Club (34 nodos, 78 aristas) y construye el modelo con el Luna Model. Instala los paquetes necesarios si es preciso:

# %pip install networkx matplotlib luna-model

Paso 1: Define el problema​

Primero, genera las variables binarias con el paquete luna-model. Luego, define el objetivo y agrega las restricciones al modelo.

import networkx as nx
from luna_model import Model, quicksum

k = 3
G = nx.karate_club_graph()

# Instantiate the Model object
model = Model(name="Max-3-Cut")

# Add binary variables
x = model.add_variables("x", shape=(G.number_of_nodes(), k))

# Add the objective by using the numpy dot abstraction
model.objective = quicksum(x[i].dot(x[j]) for i, j in G.edges)

# Bulk-add the one-hot constraints
model.add_constraints(x.sum(axis=1) == 1)

# Encode the model to send via API
model_enc = model.encode_b64()

Paso 2: Ejecuta el optimizador​

Resuelve el problema con el optimizador.

options = {"use_session": True}

# Dispatch the job
job_3 = optimizer.run(
problem=model_enc, backend_name=backend.name, options=options
)

print(f"Job ID: {job_3.job_id}")
Job ID: df911d83-9df7-44a1-bfad-5d66e08676c3
# Monitor the job status
job_3.status()
'RUNNING: WAITING_FOR_QPU'

Paso 3: Obtén y visualiza los resultados​

Obtén y visualiza los resultados.

result = job_3.result()
solution = result["solutions"][0]

# Map each node to its assigned color group
node_group = {}
for i in G.nodes:
for c in range(k):
if solution[f"x{i},{c}"] == 1:
node_group[i] = c

print(f"Feasible: {result['feasible']}")
print(f"Violations: {int(result['objective_value'])}")

# Visualize the partition
colors = ["tab:blue", "tab:orange", "tab:green"]
node_colors = [colors[node_group[i]] for i in G.nodes()]
nx.draw(G, nx.kamada_kawai_layout(G), node_color=node_colors, node_size=200)
Feasible: True
Violations: 4

Output of the previous code cell

Registro de cambios​

[0.4.0] - 2026-09-22​

Cambios incompatibles

  • Se renombró el parámetro de entrada model a problem.
  • Se renombró el campo de salida obj_value a objective_value.
  • Se reemplazó la opción postprocessing por postprocessing_level, que toma un entero en el rango [0, 3] en lugar de una cadena. Los valores anteriores se corresponden con "off" -> 0, "weak" -> 1, "medium" -> 2 y "strong" -> 3, y el valor predeterminado no cambia y sigue siendo 3.
  • Se reestructuró metadata.circuit_metrics. Ahora informa num_qubits, gate_count, depth, two_qubit_gate_count, two_qubit_gate_depth y operations por separado para before_transpilation y after_transpilation, en lugar de un único diccionario plano de recuentos promedio de compuertas. El campo es None cuando el problema se resolvió durante el preprocesamiento y no se ejecutó ningún circuito (código de error 4712).

Características

  • Se añadió la opción seed_transpiler para hacer que la síntesis y el enrutamiento de circuitos sean reproducibles entre ejecuciones.
  • Se añadió la opción job_tags para adjuntar tus propias etiquetas a cada trabajo de Qiskit Runtime que envía la función.
  • Se añadió el campo de salida solution_bitstrings, que devuelve las mismas soluciones que solutions en forma de cadenas de bits.
  • Se añadió metadata.solver_info.variable_mapping, que asigna a cada nombre de variable su posición de carácter en las entradas de solution_bitstrings.

Documentación

  • Se documentó que las variables continuas (de valor real) no se admiten y que las variables enteras requieren límites explícitos. Dichos modelos fallan con el código de error 4710.

Obtén ayuda​

Para preguntas o problemas, contacta con support@aqarios.com indicando tu ID de trabajo.

Próximos pasos​

Recomendaciones

Referencias​

  1. Bucher et al. (2026), Constrained Quantum Optimization via Iterative Warm-Start XY-Mixers 10.1088/1367-2630/ae8ea2