Skip to main content
IBM Quantum Platform

Optimizador cuántico con restricciones: una función de Qiskit desarrollada por Aqarios

Consulta la referencia de la API

Note

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 fase de versión preliminar y están sujetas a cambios.


Visión general

Con el «Constrained Quantum Optimizer» de Aqarios, puedes resolver problemas de optimización binaria con restricciones en hardware cuántico d IBM®, sin necesidad de reformulación manual ni mapeo de circuitos. Solo tienes que proporcionar un problema en formato de archivo LP, MPS o 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 hardware. El solucionador utiliza el método iterativo de «warm-starting» [1] del algoritmo « FlexQAOA », una extensión nativa de restricciones del algoritmo cuántico de optimización aproximada (QAOA).

El optimizador está diseñado para resolver problemas binarios con restricciones. En concreto, las restricciones «one-hot» y las restricciones de empaquetamiento de conjuntos 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.


Descripción

Declaración de limitación de responsabilidad

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 generadas cuánticamente podrían alcanzar una calidad final de solución similar tras un posprocesamiento equivalente. Por lo tanto, la evaluación debería tener en cuenta todo el flujo de trabajo de optimización.

El optimizador cuántico con restricciones implementa el algoritmo QAOA con mezclador XY iterativo y arranque en caliente (IWS-QAOA) [1]. El algoritmo ejecuta circuitos QAOA de ángulo fijo en hardware cuántico y actualiza las probabilidades iniciales de «warm-start» de cada circuito utilizando los resultados de las mediciones de la iteración anterior. No es necesario entrenar ningún parámetro variacional; en su lugar, se elige una parametrización de rampa lineal fija.

Al utilizar mezcladores XY para aplicar restricciones «one-hot», se puede 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, sin que se introduzca ninguna complejidad adicional —debida a los términos de penalización— en el paisaje de optimización.

Flujo de trabajo

A continuación se describe el flujo de trabajo de la funció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 resulta aplicable al problema, este se analiza como un grafo, en el que las aristas determinan la relación entre las variables. Este análisis de gráficos permite introducir dos mejoras en el tratamiento previo de los problemas. En primer lugar, las restricciones individuales pueden agruparse en una sola restricción de mayor envergadura mediante la fusión de cliques, de modo que queden representadas por un único mezclador XY. En segundo lugar, las variables que solo tienen un vecino pueden fijarse de forma determinista, lo que reduce el tamaño y la complejidad del problema.
    • Resolución de restricciones: El preprocesador gestiona cada restricción por separado: si la restricción es del tipo «one-hot», puede aplicarse mediante mezcladores XY. Si se trata de una restricción de empaquetamiento de conjuntos, primero se transformará en una restricción de tipo «one-hot». Las restricciones de igualdad se gestionan mediante penalizaciones cuadráticas, y las restricciones de desigualdad, mediante penalización desequilibrada.
    • Derivación de la penalización: Las restricciones que no pueden expresarse como restricciones de igualdad «one-hot» se convierten en términos de penalización. El optimizador deduce automáticamente la intensidad de la penalización a partir de la estructura del problema.
  2. Inicio en caliente iterativo : el optimizador ejecuta varias cadenas de inicio en caliente en paralelo. Cada cadena contiene una distribución de probabilidad sobre las asignaciones de variables, que se utiliza como probabilidad inicial del circuito QAOA (estado de arranque en caliente). En cada iteración, una cadena toma muestras del circuito cuántico, aplica un 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 necesidad de un bucle de optimización variacional.

El número de cadenas (num_parallel), el presupuesto total de disparos por cadena (total_shots) y el número de repeticiones de QAOA (reps) son los principales parámetros que permiten equilibrar la calidad de la solución y el tiempo de ejecución.

Diagrama de flujo del optimizador cuántico con restricciones

Referencias comparativas

Las pruebas comparativas que se muestran a continuación demuestran que la función «Constrained Quantum Optimizer» de Qiskit es capaz de encontrar soluciones viables y de alta calidad para problemas de optimización con restricciones que cuentan con más de 100 variables binarias. La precisión es la relación entre el valor objetivo obtenido y el valor óptimo conocido. El tiempo de ejecución es el tiempo real transcurrido desde la invocación de la función hasta la entrega del resultado. El uso de la QPU es el tiempo de computación cuántica facturable.

Se analizaron 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. El problema de «Max- kk -Cut» es también el problema de optimización equivalente al problema de decisión de coloración de grafos (cuando se cortan todas las aristas, el grafo es « kk -colorable»).

Problema
Variables
Restricciones
Qubits
Precisión
Viabilidad
Tiempo total de ejecución (s)
Uso de la CPU (s)
Total de tiros
MIS es60fst02186280124100.0%940.2147.025.0k
MIS sloane_2dc_128128517374100.0%347.6106.012.5k
Max-3-Cut Club de kárate99339998.7%279.5166.025.0k
Max-4-Cut Club de kárate13233132100.0%258.3200.025.0k

Notas sobre las pruebas de referencia

  • Los resultados anteriores se obtuvieron en el procesador Heron ibm_marrakesh r2 con las opciones predeterminadas y la función use_session activada. Por ejemplo sloane_2dc_128 , total_shots=12500 se utilizó.
  • El menor número de qubits en los casos de MIS (74 y 124 frente a 128 y 186 variables) se debe a la fijación de variables en el preprocesamiento.
  • Las instancias de problemas para MIS proceden de la Biblioteca de Referencia de Optimización Cuántica. - La instancia para el problema del corte «Max- kk -Cut» es el grafo del Club de Kárate Zachary, que tiene un número cromático de 5.

Cómo empezar

Esta guía explica el funcionamiento básico del «Constrained Quantum Optimizer», mostrando cómo cargar la función y resolver problemas sencillos. Si deseas un tutorial más detallado, consulta «Cómo hallar el conjunto independiente máximo con el optimizador cuántico con restricciones de Aqarios ».

Inicia sesión con tu clave API de IBM Quantum y tu instancia CRN; a continuación, carga la función desde el catálogo.

Note

El siguiente código da por hecho que ya has guardado tus credenciales. Para configurarlas, sigue las instrucciones que figuran en «Guarda las credenciales de tu cuenta de IBM Cloud ».

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Verify that you have access to the function
catalog.list()

Output:

[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()

Output:

[<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 sencillo con restricciones

El primer ejemplo muestra cómo utilizar la función con archivos LP, un formato habitual para especificar problemas de optimización en la investigación operativa. Deberás formular el siguiente problema de optimización arbitrario de cinco variables binarias xi{0,1}x_i \in \{0,1\}, sujeto a dos restricciones:

minx1x2+2x2x3x3x42x4x52x1x34x2x5s.t.x1+x2+x31x3x4+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. Plantear el problema

Expresa directamente la expresión matemática en 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 obligatoria para los objetivos cuadráticos según la norma del PL.

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(
    model=lp_str, backend_name=backend.name, options=options
)

print(f"Job ID: {job_1.job_id}")

Output:

Job ID: 2e06a3a8-7c29-4c76-94d7-194293363f4a
# Monitor the job status
job_1.status()

Output:

'RUNNING: WAITING_FOR_QPU'

3. Obtener el resultado

La solución, que consiste en asignar los nombres de las variables a las asignaciones correspondientes, se obtiene del resultado.

result = job_1.result()

result

Output:

{'solutions': [{'x_1': 0, 'x_2': 1, 'x_3': 0, 'x_4': 1, 'x_5': 1}],
 'raw_energy': -6.0,
 'obj_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': {'depth': 100.95,
   '2Q-depth': 33.0,
   'sx': 122.25,
   'rz': 78.85,
   'cz': 48.0,
   'x': 0.4}}}

El solutions campo del diccionario de retorno contiene todas las mejores soluciones degeneradas encontradas. Junto con los datos de la solución, metadata se proporcionan el obj_value, un indicador feasible y adicionales.


Ejemplo 2: Conjunto independiente máximo

El problema del conjunto independiente máximo (MIS) consiste en hallar el mayor subconjunto de vértices de un grafo tal que ningún par de vértices del subconjunto comparta un arco. Este ejemplo resuelve el problema MIS en un grafo regular de 12 nodos. Para obtener más información sobre el problema y ver un ejemplo más extenso, consulta el tutorial.

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

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

Paso 1: Definir el problema

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

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)

Output:

\ 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(
    model=lp_str, backend_name=backend.name, options=options
)

print(f"Job ID: {job_2.job_id}")

Output:

Job ID: f041431c-b42e-4627-afe5-522fea61faf6
# Monitor the job status
job_2.status()

Output:

'QUEUED'

Paso 3: Obtener e interpretar los resultados

Por último, recuperamos la solución y comprobamos que sea correcta.

result = job_2.result()

print(f"Feasible: {result['feasible']}")
print(f"Independent set size: {int(result['obj_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}")

Output:

Feasible: True
Independent set size: 5
Selected nodes: [1, 4, 6, 9, 11]
Edge conflicts: []

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

El problema «Max- kk -Cut» divide los vértices de un grafo en grupos « kk » con el fin de maximizar el número de aristas cuyos extremos pertenecen a grupos diferentes. Para k=3k = 3, esto equivale a encontrar una coloración de 3 colores que maximice el número de aristas de colores cruzados.

Formalmente, se asigna cada nodo iVi \in V a uno de los grupos kk con 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 los enlaces entre grupos:

min(u,v)Ecxu,cxv,cs.t.cxi,c=1iV\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 gestiona de forma nativa.

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

# %pip install networkx matplotlib luna-model

Paso 1: Definir el problema

En primer lugar, genera las variables binarias con el paquete luna-model . A continuación, define el objetivo e incorpora 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(
    model=model_enc, backend_name=backend.name, options=options
)

print(f"Job ID: {job_3.job_id}")

Output:

Job ID: df911d83-9df7-44a1-bfad-5d66e08676c3
# Monitor the job status
job_3.status()

Output:

'RUNNING: WAITING_FOR_QPU'

Paso 3: Obtener y visualizar los resultados

Recupera 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['obj_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)

Output:

Feasible: True
Violations: 4
Output of the previous code cell

Obtener soporte

Si tienes alguna pregunta o problema, ponte en contacto con [email protected] indicando tu número de referencia.


Próximos pasos

Recomendaciones

Referencias

  1. Bucher et al. (2026), «Optimización cuántica con restricciones mediante mezcladores XY iterativos con inicio en caliente » 10.1088/1367-2630/ae8ea2
¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.