Solucionador de optimización: una función Qiskit de Q-CTRL Fire Opal
Consulte la referencia de la API
Las funciones Qiskit 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 IBM Quantum Platform API). Se trata de versiones preliminares sujetas a cambios.
El código de esta página se ha desarrollado teniendo en cuenta los siguientes requisitos. Recomendamos utilizar estas versiones o posteriores.
qiskit-ibm-runtime~=0.47.0 sympy~=1.14.0
Visión general
Con el solucionador de optimización Fire Opal, puede resolver problemas de optimización a gran escala en hardware cuántico sin necesidad de conocimientos cuánticos. Basta con introducir la definición del problema de alto nivel y el Solver se encarga del resto. Todo el flujo de trabajo tiene en cuenta el ruido y aprovecha la gestión del rendimiento de Fire Opal. El Solver ofrece sistemáticamente soluciones precisas a los problemas clásicos más complejos, incluso a escala de dispositivo completo en las mayores QPU de IBM®.
El Solver es flexible y puede utilizarse para resolver problemas de optimización combinatoria definidos como funciones objetivo o grafos arbitrarios. No es necesario asignar los problemas a la topología del dispositivo. Se pueden resolver tanto problemas sin restricciones como con restricciones, y estas últimas se aplican como restricciones « Hamming-weight-1 » (de tipo «todo o nada») en lugar de como términos de penalización. Los ejemplos incluidos en esta guía muestran cómo resolver un problema de optimización a escala de red, tanto sin restricciones como con restricciones, utilizando diferentes tipos de datos de entrada del Solver. El primer ejemplo se refiere a un problema de corte máximo definido en un grafo de 156 nodos y regularidad 3, mientras que el segundo ejemplo aborda un problema de partición de grafos de 50 nodos definido mediante una función de coste.
Para acceder al Optimization Solver, póngase en contacto con Q-CTRL.
Descripción de la función
El Solver optimiza y automatiza por completo todo el algoritmo, desde la supresión de errores a nivel de hardware hasta la asignación eficaz de problemas y la optimización clásica en bucle cerrado. Entre bastidores, la canalización del Solver reduce los errores en cada etapa, lo que permite mejorar el rendimiento necesario para escalar de forma significativa. El flujo de trabajo subyacente se inspira en el Algoritmo de Optimización Cuántica Aproximada (QAOA), que es un algoritmo híbrido cuántico-clásico. Para un resumen detallado del flujo de trabajo completo del Optimization Solver, consulte el manuscrito publicado.
Para resolver un problema genérico con el Optimization Solver:
- Defina su problema como una función objetivo, un gráfico o una cadena de spin
SparsePauliOp. - Conéctese a la función a través del Catálogo de Funciones Qiskit.
- Ejecute el problema con el Solver y obtenga los resultados.
Formatos de problemas admitidos
- Representación de la expresión polinómica de una función objetivo. Idealmente creado en Python con un objeto SymPy Poly existente y formateado en una cadena utilizando sympy.srepr.
- Representación gráfica de un tipo concreto de problema. El gráfico debe crearse utilizando la biblioteca networkx, disponible en Python. A continuación, debería convertirse en una cadena utilizando la función de networkx
nx.readwrite.json_graph.adjacency_data. - Representación en cadena de un problema concreto. La cadena de giro debe representarse como un objeto
SparsePauliOp; consulte la documentación para obtener más detalles.
Si deseas utilizar un backend que esta función no admite actualmente, ponte en contacto con Q-CTRL para que lo incorporen.
Referencias comparativas
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 similar tras un posprocesamiento equivalente. Por lo tanto, la evaluación debería tener en cuenta todo el flujo de trabajo de optimización.
Los resultados de pruebas comparativas publicadas demuestran que el Solver resuelve con éxito problemas con más de 120 qubits, superando incluso resultados publicados anteriormente sobre recocido cuántico y dispositivos de iones atrapados. Las siguientes métricas de referencia proporcionan una indicación aproximada de la precisión y el escalado de los tipos de problemas basándose en algunos ejemplos. Las métricas reales pueden diferir en función de varias características del problema, como el número de términos de la función objetivo (densidad) y su localización, el número de variables y el orden polinómico.
El "Número de qubits" indicado no es una limitación rígida, sino que representa umbrales aproximados en los que se puede esperar una precisión de solución extremadamente consistente. Se han resuelto con éxito problemas de mayor envergadura, por lo que se recomienda realizar pruebas más allá de estos límites.
Se admite la conectividad arbitraria de qubits en todos los tipos de problemas.
Tipo de problema | Número de qubits | Ejemplo | Precisión | Tiempo total (s) | Tiempo de ejecución (s) | Número de iteraciones |
|---|---|---|---|---|---|---|
| Problemas cuadráticos de conexión dispersa | 156 | 3-corte máximo regular | 100 % | 1764 | 293 | 16 |
| Optimización binaria de orden superior | 156 | Modelo Ising de espín-cristal | 100 % | 1461 | 272 | 16 |
| Problemas cuadráticos densamente conectados | 50 | Max-Cut totalmente conectado | 100 % | 1758 | 268 | 6 |
| Problema con restricciones con restricciones estrictas | 50 | Partición ponderada de grafos con una densidad de aristas del 8 % | 100 % | 1074 | 215 | 10 |
Cómo empezar
Primero, autentíquese utilizando su clave API IBM Quantum. A continuación, selecciona la función de Qiskit de la siguiente manera. (En este fragmento de código se da por hecho que ya has guardado tu cuenta en tu entorno local.)
from qiskit_ibm_catalog import QiskitFunctionsCatalog
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# Verify that you have access to the function
catalog.list()Output:
[QiskitFunction(qunova/hivqe-chemistry),
QiskitFunction(global-data-quantum/quantum-portfolio-optimizer),
QiskitFunction(algorithmiq/tem),
QiskitFunction(qedma/qesem),
QiskitFunction(multiverse/singularity),
QiskitFunction(ibm/circuit-function),
QiskitFunction(q-ctrl/optimization-solver),
QiskitFunction(colibritd/quick-pde),
QiskitFunction(q-ctrl/performance-management),
QiskitFunction(kipu-quantum/iskay-quantum-optimizer)]
# Access Function
solver = catalog.load("q-ctrl/optimization-solver")Ejemplo: Optimización sin restricciones
Resuelve el problema del corte máximo (max-cut). El siguiente ejemplo muestra las capacidades del Solver en un problema de corte máximo en un grafo no ponderado de 156 nodos y 3 aristas regulares, aunque también se pueden resolver problemas en grafos ponderados.
Además de qiskit-ibm-catalog, también utilizará los siguientes paquetes para ejecutar este ejemplo: networkx y numpy. Puedes instalar estos paquetes descomentando la siguiente celda si estás ejecutando este ejemplo en un notebook usando el kernel IPython.
# %pip install networkx numpy1. Definir el problema
Puedes resolver un problema de corte máximo definiendo un problema de grafos y especificando problem_type='maxcut'.
import networkx as nx
import numpy as np
# Generate a random graph with 156 nodes
maxcut_graph = nx.random_regular_graph(d=3, n=156, seed=8)# Optionally, visualize the graph
nx.draw_networkx(
maxcut_graph, nx.kamada_kawai_layout(maxcut_graph), node_size=100
)Output:
El Solver acepta una cadena como entrada de definición del problema.
# Convert graph to string
problem_as_str = nx.readwrite.json_graph.adjacency_data(maxcut_graph)2. Ejecuta el problema
Cuando utilice el método de entrada basado en gráficos, especifique el tipo de problema.
# Solve the problem
maxcut_job = solver.run(
problem=problem_as_str,
problem_type="maxcut",
backend_name=backend_name, # E.g. "ibm_fez"
)Comprueba el estado de tu carga de trabajo de Qiskit Function o obtén los resultados de la siguiente manera:
# Print the ID so you can use it later, if necessary
print(maxcut_job.job_id)
# Get job status
print(maxcut_job.status())Output:
34b53970-d95a-4e24-8763-fc6f3d112843
QUEUED
3. Recuperar el resultado
Recuperar el valor de corte óptimo del diccionario de resultados.
Es posible que haya cambiado la asignación de las variables a la cadena de bits. El diccionario de salida contiene un variables_to_bitstring_index_map subdiccionario, que ayuda a verificar el orden.
# Poll for results
maxcut_result = maxcut_job.result()
# Take the absolute value of the solution since the cost function is minimized
qctrl_maxcut = abs(maxcut_result["solution_bitstring_cost"])
# Print the optimal cut value found by the Optimization Solver
print(f"Optimal cut value: {qctrl_maxcut}")Output:
Optimal cut value: 210.0
Puede verificar la exactitud del resultado resolviendo el problema de forma clásica con solucionadores de código abierto como PuLP si el grafo no está densamente conectado. Los problemas de alta densidad pueden requerir solucionadores clásicos avanzados para validar la solución.
Ejemplo: Optimización con restricciones
El ejemplo anterior de «max-cut» es un problema común de optimización binaria cuadrática sin restricciones. El solucionador de optimización de Q-CTRL también puede resolver problemas de optimización con restricciones pasando dichas restricciones directamente al solucionador a través de la entrada constraint , en lugar de codificarlas como términos de penalización en la función objetivo. Actualmente, el Solver admite restricciones de tipo « Hamming-weight-1 »: cada restricción especifica un grupo de variables en el que exactamente una variable debe ser igual a 1 y el resto deben ser iguales a 0.
El siguiente ejemplo muestra cómo construir una función de coste y un conjunto de restricciones duras para un problema de optimización con restricciones —la partición de grafos—, asignando cada nodo de un grafo a exactamente uno de varios grupos, al tiempo que se minimiza el peso total de los arcos cuyos extremos pertenecen al mismo grupo.
Además de los paquetes qiskit-ibm-catalog y qiskit , también utilizará los siguientes paquetes para ejecutar este ejemplo: numpy, networkx, y sympy. Puedes instalar estos paquetes descomentando la siguiente celda si estás ejecutando este ejemplo en un notebook usando el kernel IPython.
# %pip install numpy networkx sympy1. Definir el problema
Define un problema de partición de grafos aleatorios generando un grafo con nodos ponderados aleatoriamente.
import networkx as nx
from sympy import Symbol, Poly, srepr
# To change the weights, change the seed to any integer.
rng_seed = 18
_rng = np.random.default_rng(rng_seed)
node_count = 50
edge_probability = 0.08
graph = nx.erdos_renyi_graph(
node_count, edge_probability, seed=rng_seed, directed=False
)
# add node weights
min_weight = -1.0
max_weight = 1.0
for i in graph.nodes:
weight = (max_weight - min_weight) * _rng.random() + min_weight
graph.add_node(i, weight=weight)
# Optionally, visualize the graph
nx.draw_networkx(graph, nx.kamada_kawai_layout(graph), node_size=200)Output:
Un modelo de optimización estándar para la partición de grafos ponderados puede formularse de la siguiente manera. Dividimos los nodos del grafo en tres grupos y definimos que si el nodo se asigna al grupo , y en caso contrario. El objetivo es minimizar el peso total de los arcos cuyos extremos pertenecen al mismo grupo, siendo el peso de un arco el peso combinado de sus dos extremos, :
# Construct the cost function.
group_count = 3
variables = [
Symbol(f"n[{i},{g}]")
for i in range(node_count)
for g in range(group_count)
]
node_group_var = {
(i, g): variables[i * group_count + g]
for i in range(node_count)
for g in range(group_count)
}
cost_function = Poly(0, *variables)
for i, j in graph.edges():
edge_weight = graph.nodes[i]["weight"] + graph.nodes[j]["weight"]
for g in range(group_count):
cost_function += (
edge_weight * node_group_var[(i, g)] * node_group_var[(j, g)]
)Cada nodo debe asignarse a uno y solo uno de los tres grupos. Se trata de una restricción de tipo « Hamming-weight-1 »: para cada nodo , exactamente uno de los elementos debe ser igual a 1, y el resto deben ser iguales a 0:
En lugar de incorporar este requisito como un término de penalización en la función de coste, pásalo directamente al Solver como una restricción rígida mediante el parámetro de entrada constraint .
# Build the hard constraint: exactly one group per node.
constraint_dict = {
str(tuple(f"n[{i},{g}]" for g in range(group_count))): 1
for i in range(node_count)
}
print(f"Problem constraints: {constraint_dict}")Output:
Problem constraints: {"('n[0,0]', 'n[0,1]', 'n[0,2]')": 1, "('n[1,0]', 'n[1,1]', 'n[1,2]')": 1, "('n[2,0]', 'n[2,1]', 'n[2,2]')": 1, "('n[3,0]', 'n[3,1]', 'n[3,2]')": 1, "('n[4,0]', 'n[4,1]', 'n[4,2]')": 1, "('n[5,0]', 'n[5,1]', 'n[5,2]')": 1, "('n[6,0]', 'n[6,1]', 'n[6,2]')": 1, "('n[7,0]', 'n[7,1]', 'n[7,2]')": 1, "('n[8,0]', 'n[8,1]', 'n[8,2]')": 1, "('n[9,0]', 'n[9,1]', 'n[9,2]')": 1, "('n[10,0]', 'n[10,1]', 'n[10,2]')": 1, "('n[11,0]', 'n[11,1]', 'n[11,2]')": 1, "('n[12,0]', 'n[12,1]', 'n[12,2]')": 1, "('n[13,0]', 'n[13,1]', 'n[13,2]')": 1, "('n[14,0]', 'n[14,1]', 'n[14,2]')": 1, "('n[15,0]', 'n[15,1]', 'n[15,2]')": 1, "('n[16,0]', 'n[16,1]', 'n[16,2]')": 1, "('n[17,0]', 'n[17,1]', 'n[17,2]')": 1, "('n[18,0]', 'n[18,1]', 'n[18,2]')": 1, "('n[19,0]', 'n[19,1]', 'n[19,2]')": 1, "('n[20,0]', 'n[20,1]', 'n[20,2]')": 1, "('n[21,0]', 'n[21,1]', 'n[21,2]')": 1, "('n[22,0]', 'n[22,1]', 'n[22,2]')": 1, "('n[23,0]', 'n[23,1]', 'n[23,2]')": 1, "('n[24,0]', 'n[24,1]', 'n[24,2]')": 1, "('n[25,0]', 'n[25,1]', 'n[25,2]')": 1, "('n[26,0]', 'n[26,1]', 'n[26,2]')": 1, "('n[27,0]', 'n[27,1]', 'n[27,2]')": 1, "('n[28,0]', 'n[28,1]', 'n[28,2]')": 1, "('n[29,0]', 'n[29,1]', 'n[29,2]')": 1, "('n[30,0]', 'n[30,1]', 'n[30,2]')": 1, "('n[31,0]', 'n[31,1]', 'n[31,2]')": 1, "('n[32,0]', 'n[32,1]', 'n[32,2]')": 1, "('n[33,0]', 'n[33,1]', 'n[33,2]')": 1, "('n[34,0]', 'n[34,1]', 'n[34,2]')": 1, "('n[35,0]', 'n[35,1]', 'n[35,2]')": 1, "('n[36,0]', 'n[36,1]', 'n[36,2]')": 1, "('n[37,0]', 'n[37,1]', 'n[37,2]')": 1, "('n[38,0]', 'n[38,1]', 'n[38,2]')": 1, "('n[39,0]', 'n[39,1]', 'n[39,2]')": 1, "('n[40,0]', 'n[40,1]', 'n[40,2]')": 1, "('n[41,0]', 'n[41,1]', 'n[41,2]')": 1, "('n[42,0]', 'n[42,1]', 'n[42,2]')": 1, "('n[43,0]', 'n[43,1]', 'n[43,2]')": 1, "('n[44,0]', 'n[44,1]', 'n[44,2]')": 1, "('n[45,0]', 'n[45,1]', 'n[45,2]')": 1, "('n[46,0]', 'n[46,1]', 'n[46,2]')": 1, "('n[47,0]', 'n[47,1]', 'n[47,2]')": 1, "('n[48,0]', 'n[48,1]', 'n[48,2]')": 1, "('n[49,0]', 'n[49,1]', 'n[49,2]')": 1}
No es necesario añadir todas las variables a constraint. Cualquier variable que no se incluya en el diccionario queda sin restricciones, por lo que se pueden combinar grupos de variables con restricciones estrictas con variables libres en el mismo problema.
2. Ejecuta el problema
# Solve the problem
partition_job = solver.run(
problem=srepr(cost_function),
constraint=constraint_dict,
backend_name="ibm_marrakesh", # E.g. "ibm_marrakesh"
)Comprueba el estado de tu carga de trabajo de Qiskit Function o obtén los resultados de la siguiente manera:
# Print the ID so you can use it later, if necessary
print(partition_job.job_id)
# Get job status
print(partition_job.status())Output:
b8085944-f313-444e-be39-ea61b1b47ebd
QUEUED
3. Obtenga el resultado
Recupera la solución y analiza los resultados. El coste de la solución representa el peso total de los arcos cuyos extremos han acabado en el mismo grupo, por lo que un coste menor indica una mejor partición del grafo.
partition_result = partition_job.result()
qctrl_cost = partition_result["solution_bitstring_cost"]
solution_bitstring = partition_result["solution_bitstring"]
# Print results
print(f"Total weight of same-group edges: {qctrl_cost}")
print(f"Solution bitstring: {solution_bitstring}")Output:
Total weight of same-group edges: -36.5539
Solution bitstring: 100100100100100001100100100100100100100100100100100001010100010100100100100010001001100100100001100001100001010001001010100100100100100010100100100100
Obtener soporte
Si tiene alguna pregunta o problema, póngase en contacto con Q-CTRL.
Registro de cambios
- 10 de agosto de 2026: Se ha añadido compatibilidad con restricciones duras (peso de Hamming 1) a través del parámetro de entrada
constraint, y se ha actualizado el ejemplo de optimización con restricciones para que las utilice. - 11/02/2026: Ahora ofrecemos asistencia para
ibm_miami
Próximos pasos
- Solicite acceso al solucionador de optimización Q-CTRL.
- Consulta la referencia de la API de esta función de Qiskit.
- Pruebe el tutorial Resolver problemas de optimización binaria de orden superior con el Solucionador de Optimización de Q-CTRL.
- Revisión Sachdeva, N., et al. (2024). La optimización cuántica mediante un ordenador cuántico de 127 qubits con modelo de puerta IBM puede superar a los annealers cuánticos en problemas de optimización binaria no triviales. arXiv Preimpresión arXiv:2406.01743.
- Reseña Loco, D., et al. (2026). Predicción práctica de sitios de hidratación en bolsillos proteicos para el descubrimiento de fármacos en un ordenador cuántico. arXiv Preimpresión arXiv:2512.08390.
- Revise el caso práctico de Mazda.
- Revise el caso práctico de Network Rail.
- Revise el estudio de caso del Ejército Australiano.
- Revise el estudio de caso de Transport for New South Wales.