Saltar al contenido principal

Optimization Solver: una Qiskit Function de Q-CTRL Fire Opal

Consulta la referencia de API

nota

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

Versiones de paquetes

El código de esta página fue desarrollado con los siguientes requisitos. Recomendamos usar estas versiones o más recientes.

qiskit-ibm-runtime~=0.46.1
sympy~=1.14.0

Descripción general

Con el Fire Opal Optimization Solver, puedes resolver problemas de optimización a escala utilitaria en hardware cuántico sin necesidad de experiencia en computación cuántica. Simplemente introduce la definición del problema de alto nivel y el Solver se encarga del resto. Todo el flujo de trabajo es consciente del ruido y aprovecha la Gestión del rendimiento de Fire Opal internamente. El Solver ofrece de manera consistente soluciones precisas a problemas difíciles de resolver de forma clásica, incluso a escala de dispositivo completo en los QPUs IBM® más grandes.

El Solver es flexible y puede utilizarse para resolver problemas de optimización combinatoria definidos como funciones objetivo o grafos arbitrarios. Los problemas no tienen que ser mapeados a la topología del dispositivo. Tanto los problemas sin restricciones como los restringidos son solucionables, siempre que las restricciones puedan formularse como términos de penalización. Los ejemplos incluidos en esta guía demuestran cómo resolver un problema de optimización a escala utilitaria sin restricciones y uno con restricciones, usando diferentes tipos de entrada del Solver. El primer ejemplo involucra un problema max-cut definido en un grafo 3-regular de 156 nodos, mientras que el segundo aborda un problema de Cobertura Mínima de Vértices de 50 nodos definido por una función de costo.

Para obtener acceso al Optimization Solver, contacta a 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 el mapeo eficiente del problema y la optimización clásica en bucle cerrado. En segundo plano, el pipeline del Solver reduce los errores en cada etapa, habilitando el rendimiento mejorado necesario para escalar de manera significativa. El flujo de trabajo subyacente está inspirado en el Quantum Approximate Optimization Algorithm (QAOA), que es un algoritmo híbrido cuántico-clásico. Para un resumen detallado del flujo de trabajo completo del Optimization Solver, consulta el manuscrito publicado.

Visualización del flujo de trabajo del Optimization Solver

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

  1. Define tu problema como una función objetivo, un grafo o una cadena de espín SparsePauliOp.
  2. Conéctate a la función a través del Catálogo de Funciones de Qiskit.
  3. Ejecuta el problema con el Solver y recupera los resultados.

Formatos de problema aceptados

  • Representación en expresión polinómica de una función objetivo. Idealmente creada en Python con un objeto SymPy Poly existente y formateada como cadena de texto usando sympy.srepr.
  • Representación en grafo de un tipo de problema específico. El grafo debe crearse usando la librería networkx en Python. Luego debe convertirse a cadena de texto usando la función de networkx [nx.readwrite.json_graph.adjacency_data](http://nx.readwrite.json_graph.adjacency_data.).
  • Representación en cadena de espín de un problema específico. La cadena de espín debe representarse como un objeto SparsePauliOp; consulta la documentación para más detalles.

Backends soportados

Ejecuta el siguiente código para ver la lista de backends actualmente soportados. Si tu dispositivo no aparece en la lista, contacta a Q-CTRL para añadir soporte.

# Added by doQumentation — required packages for this notebook
!pip install -q networkx numpy qiskit-ibm-catalog qiskit-ibm-runtime sympy
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()

service.backends()
[<IBMBackend('ibm_boston')>,
<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_fez')>,
<IBMBackend('ibm_marrakesh')>,
<IBMBackend('ibm_kingston')>,
<IBMBackend('ibm_miami')>]

Benchmarks

Los resultados de benchmarking publicados muestran que el Solver resuelve con éxito problemas con más de 120 qubits, superando incluso resultados previamente publicados en recocido cuántico y dispositivos de iones atrapados. Las siguientes métricas de benchmark ofrecen una indicación aproximada de la precisión y escalabilidad de los tipos de problemas basándose en algunos ejemplos. Las métricas reales pueden variar según diversas características del problema, como el número de términos en la función objetivo (densidad) y su localidad, el número de variables y el orden polinómico.

El "Número de qubits" indicado no es una limitación estricta, sino que representa umbrales aproximados donde puedes esperar una precisión de solución extremadamente consistente. Se han resuelto con éxito problemas de mayor tamaño, y se alienta a hacer pruebas más allá de estos límites.

La conectividad arbitraria de qubits está soportada en todos los tipos de problemas.

Tipo de problemaNúmero de qubitsEjemploPrecisiónTiempo total (s)Uso de runtime (s)Número de iteraciones
Problemas cuadráticos con conectividad dispersa156max-cut 3-regular100%176429316
Optimización binaria de orden superior156Modelo de espín-vidrio de Ising100%146127216
Problemas cuadráticos con conectividad densa50max-cut completamente conectado100%175826812
Problema restringido con términos de penalización50Cobertura Mínima de Vértices ponderada con 8% de densidad de aristas100%107421510

Primeros pasos

Primero, autentícate usando tu clave API de IBM Quantum. Luego, selecciona la Qiskit Function de la siguiente manera. (Este fragmento de código asume que ya has guardado tu cuenta en tu entorno local.)

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Access Function
solver = catalog.load("q-ctrl/optimization-solver")

Ejemplo: Optimización sin restricciones

Ejecuta el problema de corte máximo (max-cut). El siguiente ejemplo demuestra las capacidades del Solver en un problema max-cut de grafo 3-regular no ponderado de 156 nodos, aunque también puedes resolver problemas de grafos ponderados. Además de qiskit-ibm-catalog, también necesitarás los siguientes paquetes para ejecutar este ejemplo: networkx y numpy. Puedes instalarlos descomentando la siguiente celda si estás ejecutando este ejemplo en un notebook con el kernel de IPython.

# %pip install networkx numpy

1. Define el problema

Puedes ejecutar un problema max-cut definiendo un problema de grafo 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
)

Salida de la celda de código anterior

El Solver acepta una cadena de texto como entrada para la definición del problema.

# Convert graph to string
problem_as_str = nx.readwrite.json_graph.adjacency_data(maxcut_graph)

2. Ejecuta el problema

Cuando uses el método de entrada basado en grafos, especifica el tipo de problema.

# This cell is hidden from users
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend_name = service.least_busy(n_qubits=156).name
# 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 la carga de trabajo de tu Qiskit Function o recupera los resultados de la siguiente manera:

# Get job status
print(maxcut_job.status())
QUEUED

3. Recupera el resultado

Recupera el valor de corte óptimo del diccionario de resultados.

nota

El mapeo de las variables a la cadena de bits puede haber cambiado. El diccionario de salida contiene un subdicionario variables_to_bitstring_index_map 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}")
Optimal cut value: 210.0

Puedes verificar la precisión del resultado resolviendo el problema de forma clásica con solvers de código abierto como PuLP si el grafo no tiene conectividad densa. Los problemas de alta densidad pueden requerir solvers clásicos avanzados para validar la solución.

Ejemplo: Optimización con restricciones

El ejemplo anterior de max-cut es un problema de optimización binaria cuadrática sin restricciones común. El Optimization Solver de Q-CTRL puede usarse para distintos tipos de problemas, incluida la optimización con restricciones. Puedes resolver tipos de problemas arbitrarios introduciendo la definición del problema representada como un polinomio donde las restricciones se modelan como términos de penalización.

El siguiente ejemplo demuestra cómo construir una función de costo para un problema de optimización con restricciones: la cobertura mínima de vértices (MVC, por sus siglas en inglés). Además de los paquetes qiskit-ibm-catalog y qiskit, también necesitarás los siguientes paquetes para ejecutar este ejemplo: numpy, networkx y sympy. Puedes instalarlos descomentando la siguiente celda si estás ejecutando este ejemplo en un notebook con el kernel de IPython.

# %pip install numpy networkx sympy

1. Define el problema

Define un problema MVC aleatorio generando un grafo con nodos de peso aleatorio.

import networkx as nx
from sympy import symbols, 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
mvc_graph = nx.erdos_renyi_graph(
node_count, edge_probability, seed=rng_seed, directed=False
)

# add node weights
for i in mvc_graph.nodes:
mvc_graph.add_node(i, weight=_rng.random())

# Optionally, visualize the graph
nx.draw_networkx(mvc_graph, nx.kamada_kawai_layout(mvc_graph), node_size=200)

Salida de la celda de código anterior

Un modelo de optimización estándar para MVC ponderado puede formularse de la siguiente manera. Primero, debe añadirse una penalización para cualquier caso en que una arista no esté conectada a un vértice del subconjunto. Por lo tanto, sea ni=1n_i = 1 si el vértice ii está en la cobertura (es decir, en el subconjunto) y ni=0n_i = 0 en caso contrario. Segundo, el objetivo es minimizar el número total de vértices en el subconjunto, lo cual puede representarse mediante la siguiente función:

Minimizey=iVωini\textbf{Minimize}\qquad y = \sum_{i\in V} \omega_i n_i

# Construct the cost function.
variables = symbols([f"n[{i}]" for i in range(node_count)])
cost_function = Poly(0, variables)

for i in mvc_graph.nodes():
weight = mvc_graph.nodes[i].get("weight", 0)
cost_function += variables[i] * weight

Ahora, cada arista del grafo debe incluir al menos un extremo de la cobertura, lo cual puede expresarse como la desigualdad:

ni+nj1 for all (i,j)En_i + n_j \ge 1 \texttt{ for all } (i,j)\in E

Cualquier caso en que una arista no esté conectada al vértice de la cobertura debe ser penalizado. Esto puede representarse en la función de costo añadiendo una penalización de la forma P(1ninj+ninj)P(1-n_i-n_j+n_i n_j) donde PP es una constante de penalización positiva. Por lo tanto, una alternativa sin restricciones a la desigualdad restringida para MVC ponderado es:

Minimizey=iVωini+P((i,j)E(1ninj+ninj))\textbf{Minimize}\qquad y = \sum_{i\in V}\omega_i n_i + P(\sum_{(i,j)\in E}(1 - n_i - n_j + n_i n_j))

# Add penalty term.
penalty_constant = 2
for i, j in mvc_graph.edges():
cost_function += penalty_constant * (
1 - variables[i] - variables[j] + variables[i] * variables[j]
)

2. Ejecuta el problema

# Solve the problem
mvc_job = solver.run(
problem=srepr(cost_function),
backend_name=backend_name, # E.g. "ibm_fez"
)

Comprueba el estado de la carga de trabajo de tu Qiskit Function o recupera los resultados de la siguiente manera:

print(mvc_job.status())
QUEUED

3. Obtén el resultado

Recupera la solución y analiza los resultados. Como este problema tiene nodos ponderados, la solución no es simplemente el número mínimo de nodos cubiertos. En cambio, el costo de la solución representa la suma de los pesos de los vértices incluidos en la cobertura de vértices. Representa el "costo" o "peso" total de cubrir todas las aristas del grafo utilizando los vértices seleccionados.

mvc_result = mvc_job.result()
qctrl_cost = mvc_result["solution_bitstring_cost"]

# Print results
print(f"Solution cost: {qctrl_cost}")
Solution cost: 10.248198273708624

Obtener soporte

Para cualquier pregunta o problema, contacta a Q-CTRL.

Historial de cambios

  • 2026-02-11: Ahora se admite ibm_miami.

Próximos pasos