Optimization Solver: una Qiskit Function di Q-CTRL Fire Opal
Vedi il riferimento API
Le Qiskit Functions sono una funzionalità sperimentale disponibile esclusivamente per gli utenti del piano IBM Quantum® Premium, del piano Flex e del piano On-Prem (tramite IBM Quantum Platform API). Sono in stato di anteprima e soggette a modifiche.
Package versions
Il codice in questa pagina è stato sviluppato utilizzando i seguenti requisiti. Consigliamo di utilizzare queste versioni o versioni più recenti.
qiskit-ibm-runtime~=0.47.0
sympy~=1.14.0
Panoramica
Con il Fire Opal Optimization Solver puoi risolvere problemi di ottimizzazione su scala utility su hardware quantistico senza dover possedere competenze specifiche nel campo quantistico. Inserisci semplicemente la definizione del problema ad alto livello e il Solver si occupa del resto. L'intero flusso di lavoro è noise-aware e sfrutta internamente il Performance Management di Fire Opal. Il Solver fornisce in modo costante soluzioni accurate a problemi classicamente difficili, anche alla scala completa del dispositivo sui più grandi QPU IBM®.
Il Solver è flessibile e può essere utilizzato per risolvere problemi di ottimizzazione combinatoria definiti come funzioni obiettivo o come grafi arbitrari. I problemi non devono necessariamente essere mappati alla topologia del dispositivo. Sia i problemi non vincolati che quelli vincolati sono risolvibili, con i vincoli applicati come vincoli di Hamming-peso-1 rigidi anziché come termini di penalità. Gli esempi inclusi in questa guida mostrano come risolvere un problema di ottimizzazione su scala utility, sia non vincolato che vincolato, utilizzando diversi tipi di input per il Solver. Il primo esempio riguarda un problema Max-Cut definito su un grafo 3-regolare a 156 nodi, mentre il secondo affronta un problema di partizionamento di grafi a 50 nodi definito da una funzione di costo.
Per ottenere l'accesso all'Optimization Solver, contatta Q-CTRL.
Descrizione della funzione
Il Solver ottimizza e automatizza completamente l'intero algoritmo, dalla soppressione degli errori a livello hardware alla mappatura efficiente del problema e all'ottimizzazione classica a ciclo chiuso. Dietro le quinte, la pipeline del Solver riduce gli errori a ogni fase, abilitando le prestazioni avanzate necessarie per scalare in modo significativo. Il flusso di lavoro sottostante è ispirato al Quantum Approximate Optimization Algorithm (QAOA), un algoritmo ibrido quantistico-classico. Per un riepilogo dettagliato dell'intero flusso di lavoro dell'Optimization Solver, consulta il manoscritto pubblicato.
Per risolvere un problema generico con l'Optimization Solver:
- Definisci il tuo problema come funzione obiettivo, come grafo o come catena di spin
SparsePauliOp. - Collegati alla funzione tramite il Qiskit Functions Catalog.
- Esegui il problema con il Solver e recupera i risultati.
Formati di problema accettati
-
Rappresentazione in espressione polinomiale di una funzione obiettivo. Idealmente creata in Python con un oggetto SymPy Poly esistente e formattata in stringa utilizzando sympy.srepr.
-
Rappresentazione tramite grafo di un tipo di problema specifico. Il grafo deve essere creato utilizzando la libreria networkx in Python e poi convertito in stringa tramite la funzione networkx
nx.readwrite.json_graph.adjacency_data. -
Rappresentazione in catena di spin di un problema specifico. La catena di spin deve essere rappresentata come oggetto
SparsePauliOp; consulta la documentazione per maggiori dettagli.
Se vuoi usare un backend che questa funzione non supporta attualmente, contatta Q-CTRL per aggiungere il supporto.
Benchmark
Le prestazioni possono dipendere sia dall'istanza del problema sia dalle fasi di elaborazione successive. In alcuni casi, i campioni classici e i campioni generati quantisticamente potrebbero raggiungere una qualità della soluzione finale simile dopo un post-processing equivalente. La valutazione dovrebbe quindi considerare l'intero flusso di lavoro di ottimizzazione.
I risultati di benchmarking pubblicati mostrano che il Solver risolve con successo problemi con oltre 120 qubit, superando persino i risultati precedentemente pubblicati su dispositivi a quantum annealing e ad ioni intrappolati. Le seguenti metriche di benchmark forniscono un'indicazione approssimativa dell'accuratezza e della scalabilità dei tipi di problema sulla base di alcuni esempi. Le metriche effettive possono variare in base a diverse caratteristiche del problema, come il numero di termini nella funzione obiettivo (densità) e la loro località, il numero di variabili e l'ordine polinomiale.
Il "Numero di qubit" indicato non è un limite assoluto, ma rappresenta soglie indicative in corrispondenza delle quali ci si può aspettare un'accuratezza della soluzione estremamente costante. Problemi di dimensioni maggiori sono stati risolti con successo e si incoraggia la sperimentazione oltre questi limiti.
La connettività arbitraria dei qubit è supportata per tutti i tipi di problema.
| Tipo di problema | Numero di qubit | Esempio | Accuratezza | Tempo totale (s) | Utilizzo runtime (s) | Numero di iterazioni |
|---|---|---|---|---|---|---|
| Problemi quadratici con connettività sparsa | 156 | Max-Cut 3-regolare | 100% | 1764 | 293 | 16 |
| Ottimizzazione binaria di ordine superiore | 156 | Modello di spin-glass di Ising | 100% | 1461 | 272 | 16 |
| Problemi quadratici con connettività densa | 50 | Max-Cut completamente connesso | 100% | 1758 | 268 | 12 |
| Problema vincolato con vincoli rigidi | 50 | Partizionamento di grafi pesato con densità di archi 8% | 100% | 1074 | 215 | 10 |
Inizia a usarlo
Prima di tutto, autenticati usando la tua chiave API di IBM Quantum. Poi seleziona la Qiskit Function come segue. (Questo snippet presuppone che tu abbia già salvato il tuo account nell'ambiente locale.)
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"networkx": "networkx", "numpy": "numpy", "qiskit_ibm_catalog": "qiskit-ibm-catalog", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "sympy": "sympy"}
_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(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")
Esempio: ottimizzazione non vincolata
Esegui il problema del taglio massimo (Max-Cut). Il seguente esempio dimostra le capacità del Solver su un problema Max-Cut su un grafo 3-regolare non pesato a 156 nodi, ma puoi risolvere anche problemi su grafi pesati.
Oltre a qiskit-ibm-catalog, per eseguire questo esempio avrai bisogno anche dei seguenti pacchetti: networkx e numpy. Puoi installare questi pacchetti decommentando la cella seguente se stai eseguendo questo esempio in un notebook con il kernel IPython.
# %pip install networkx numpy
1. Definisci il problema
Puoi eseguire un problema Max-Cut definendo un problema su grafo e specificando 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
)

Il Solver accetta una stringa come input per la definizione del problema.
# Convert graph to string
problem_as_str = nx.readwrite.json_graph.adjacency_data(maxcut_graph)
2. Esegui il problema
Quando usi il metodo di input basato su grafo, specifica il tipo di 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"
)
Controlla lo stato del workload della tua Qiskit Function o recupera i risultati come segue:
# Print the ID so you can use it later, if necessary
print(maxcut_job.job_id)
# Get job status
print(maxcut_job.status())
34b53970-d95a-4e24-8763-fc6f3d112843
QUEUED
3. Recupera il risultato
Recupera il valore del taglio ottimale dal dizionario dei risultati.
variables_to_bitstring_index_map che aiuta a verificare l'ordinamento.# 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
Puoi verificare l'accuratezza del risultato risolvendo il problema classicamente con solver open-source come PuLP se il grafo non è densamente connesso. I problemi ad alta densità potrebbero richiedere solver classici avanzati per validare la soluzione.
Esempio: ottimizzazione vincolata
Il precedente esempio di max-cut è un comune problema di ottimizzazione binaria quadratica non vincolata. L'Optimization Solver di Q-CTRL può anche risolvere problemi di ottimizzazione vincolata passando vincoli rigidi direttamente al Solver tramite l'input constraint, invece di codificarli come termini di penalità nella funzione obiettivo. Il Solver attualmente supporta vincoli di peso di Hamming pari a 1: ogni vincolo specifica un gruppo di variabili in cui esattamente una variabile deve essere uguale a 1 e le restanti devono essere uguali a 0.
Il seguente esempio mostra come costruire una funzione di costo e un insieme di vincoli rigidi per un problema di ottimizzazione vincolata, il partizionamento di grafi, assegnando ogni nodo di un grafo a esattamente uno tra diversi gruppi minimizzando il peso totale degli archi i cui estremi ricadono nello stesso gruppo.
Oltre ai pacchetti qiskit-ibm-catalog e qiskit, per eseguire questo esempio avrai bisogno anche dei seguenti pacchetti: numpy, networkx e sympy. Puoi installare questi pacchetti decommentando la cella seguente se stai eseguendo questo esempio in un notebook con il kernel IPython.
# %pip install numpy networkx sympy
1. Definisci il problema
Definisci un problema di partizionamento di grafi casuale generando un grafo con nodi pesati casualmente.
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)

Un modello standard di ottimizzazione per il partizionamento di grafi pesati può essere formulato come segue. Dividi i nodi del grafo in tre gruppi , e sia se il nodo è assegnato al gruppo , e altrimenti. L'obiettivo è minimizzare il peso totale degli archi i cui estremi sono assegnati allo stesso gruppo, dove il peso di un arco è il peso combinato dei suoi due estremi, :
# 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)]
)
Ogni nodo deve essere assegnato a esattamente uno dei tre gruppi. Questo è un vincolo di peso di Hamming pari a 1: per ogni nodo , esattamente uno tra deve essere uguale a 1, e i restanti devono essere uguali a 0:
Invece di codificare questo requisito come termine di penalità nella funzione di costo, passalo direttamente al Solver come vincolo rigido tramite l'input 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}")
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}
Non è necessario aggiungere ogni variabile a constraint. Qualsiasi variabile lasciata fuori dal dizionario rimane non vincolata, quindi puoi combinare gruppi di variabili rigidamente vincolati con variabili libere nello stesso problema.
2. Esegui il problema
# Solve the problem
partition_job = solver.run(
problem=srepr(cost_function),
constraint=constraint_dict,
backend_name="ibm_marrakesh", # E.g. "ibm_marrakesh"
)
Controlla lo stato del workload della tua Qiskit Function o recupera i risultati come segue:
# Print the ID so you can use it later, if necessary
print(partition_job.job_id)
# Get job status
print(partition_job.status())
b8085944-f313-444e-be39-ea61b1b47ebd
QUEUED
3. Ottieni il risultato
Recupera la soluzione e analizza i risultati. Il costo della soluzione rappresenta il peso totale degli archi i cui estremi sono finiti nello stesso gruppo, quindi un costo più basso indica una migliore partizione 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}")
Total weight of same-group edges: -36.5539
Solution bitstring: 100100100100100001100100100100100100100100100100100001010100010100100100100010001001100100100001100001100001010001001010100100100100100010100100100100
Supporto
Per qualsiasi domanda o problema, contatta Q-CTRL.
Registro delle modifiche
-
2026-08-10: Aggiunto il supporto per i vincoli rigidi (peso di Hamming 1) tramite l'input
constraint, e aggiornato l'esempio di ottimizzazione vincolata per utilizzarli. -
2026-02-11: Ora è disponibile il supporto per
ibm_miami
Passi successivi
-
Richiedi l'accesso all'Q-CTRL Optimization Solver.
-
Consulta il riferimento API di questa Qiskit Function.
-
Prova il tutorial Risolvi problemi di ottimizzazione binaria di ordine superiore con l'Optimization Solver di Q-CTRL.
-
Consulta il case study Mazda.
-
Consulta il case study Network Rail.
-
Consulta il case study Australian Army.
-
Consulta il case study Transport for New South Wales.