Skip to main content
IBM Quantum Platform

Solucionador de optimización: una función Qiskit de Q-CTRL Fire Opal

Consulte la referencia de la API

Note

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.

Visualización del flujo de trabajo del Optimization Solver

Para resolver un problema genérico con el Optimization Solver:

  1. Defina su problema como una función objetivo, un gráfico o una cadena de spin SparsePauliOp .
  2. Conéctese a la función a través del Catálogo de Funciones Qiskit.
  3. 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.
¿Esta función es compatible con todos los servidores de fondo de IBM?

Si deseas utilizar un backend que esta función no admite actualmente, ponte en contacto con Q-CTRL para que lo incorporen.


Referencias comparativas

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 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 dispersa1563-corte máximo regular100 %176429316
Optimización binaria de orden superior156Modelo Ising de espín-cristal100 %146127216
Problemas cuadráticos densamente conectados50Max-Cut totalmente conectado100 %17582686
Problema con restricciones con restricciones estrictas50Partición ponderada de grafos con una densidad de aristas del 8 %100 %107421510

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 numpy

1. 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:

Output of the previous code cell

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.

Note

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 sympy

1. 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:

Output of the previous code cell

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 g{0,1,2}g \in \{0, 1, 2\} y definimos que ni,g=1n_{i,g} = 1 si el nodo ii se asigna al grupo gg, y ni,g=0n_{i,g} = 0 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 (i,j)(i,j) el peso combinado de sus dos extremos, ωi,j=ωi+ωj\omega_{i,j} = \omega_i + \omega_j :

Minimizey=(i,j)Eωi,jgni,gnj,g\textbf{Minimize}\qquad y = \sum_{(i,j)\in E} \omega_{i,j} \sum_{g} n_{i,g}\, n_{j,g}

# 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 ii, exactamente uno de los elementos ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} debe ser igual a 1, y el resto deben ser iguales a 0:

ni,0+ni,1+ni,2=1 for all iVn_{i,0} + n_{i,1} + n_{i,2} = 1 \texttt{ for all } i \in V

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}
Problemas con restricciones parciales

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

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