Skip to main content
IBM Quantum Platform

Optimization Solver: una funzione Qiskit di Q-CTRL Fire Opal

Consulta la documentazione dell'API

Note

Le funzioni Qiskit sono una funzione sperimentale disponibile solo per gli utenti di IBM Quantum® Premium Plan, Flex Plan e On-Prem (tramite IBM Quantum Platform API) Plan. Sono in stato di anteprima e sono soggetti a modifiche.

  • Il codice presente in questa pagina è stato sviluppato sulla base dei seguenti requisiti. Si consiglia di utilizzare queste versioni o quelle più recenti.

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

Panoramica

Con il Fire Opal Optimization Solver, è possibile risolvere problemi di ottimizzazione su scala industriale su hardware quantistico senza richiedere competenze in materia. È sufficiente inserire la definizione del problema di alto livello e il risolutore si occuperà del resto. L'intero flusso di lavoro è consapevole del rumore e sfrutta il Performance Management di Fire Opal. Il Solver fornisce costantemente soluzioni accurate a problemi classici, anche su scala full-device sulle più grandi QPU IBM®.

Il Solver è flessibile e può essere utilizzato per risolvere problemi di ottimizzazione combinatoria definiti come funzioni obiettivo o grafi arbitrari. Non è necessario associare i problemi alla topologia dei dispositivi. È possibile risolvere sia i problemi senza vincoli che quelli con vincoli, applicando questi ultimi come vincoli rigidi di tipo “ Hamming-weight-1 ” anziché come termini di penalità. Gli esempi riportati in questa guida illustrano come risolvere un problema di ottimizzazione su larga scala, sia senza vincoli che con vincoli, utilizzando diversi tipi di input per il Solver. Il primo esempio riguarda un problema di taglio massimo definito su un grafo a 156 nodi e di grado 3, mentre il secondo esempio affronta un problema di partizionamento di un grafo a 50 nodi definito da una funzione di costo.

Per accedere al risolutore di ottimizzazione, contattare Q-CTRL.


Descrizione funzione

Il risolutore ottimizza e automatizza completamente l'intero algoritmo, dalla soppressione degli errori a livello hardware all'efficiente mappatura del problema e all'ottimizzazione classica ad anello chiuso. Dietro le quinte, la pipeline del risolutore riduce gli errori in ogni fase, consentendo di migliorare le prestazioni necessarie per scalare in modo significativo. Il flusso di lavoro sottostante si ispira al Quantum Approximate Optimization Algorithm (QAOA), che è un algoritmo ibrido quantistico-classico. Per un riepilogo dettagliato dell'intero flusso di lavoro di Optimization Solver, consultare il manoscritto pubblicato.

Visualizzazione del flusso di lavoro del risolutore di ottimizzazione

Per risolvere un problema generico con il Risolutore di ottimizzazione:

  1. Definire il problema come una funzione obiettivo, un grafico o una catena di spin SparsePauliOp .
  2. Collegarsi alla funzione attraverso il Catalogo delle funzioni di Qiskit.
  3. Eseguire il problema con il Risolutore e recuperare i risultati.

Formati di problema accettati

  • Rappresentazione dell'espressione polinomiale di una funzione obiettivo. Idealmente creato in Python con un oggetto Poly esistente in SymPy e formattato in una stringa usando sympy.srepr.
  • Rappresentazione grafica di un tipo specifico di problema. Il grafico dovrebbe essere creato utilizzando la libreria networkx disponibile all'indirizzo Python. Dovrebbe quindi essere convertito in una stringa utilizzando la funzione di NetworkX nx.readwrite.json_graph.adjacency_data.
  • Rappresentazione a catena di spin di un problema specifico. La catena di spin deve essere rappresentata come un oggetto SparsePauliOp ; si veda la documentazione per maggiori dettagli.
Questa funzione supporta tutti i backend di IBM?

Se desideri utilizzare un backend che questa funzione non supporta al momento, contatta Q-CTRL per richiedere l'aggiunta del supporto.


Benchmark

I risultati dei benchmark pubblicati dimostrano che il Solver risolve con successo problemi con oltre 120 qubit, superando persino i risultati precedentemente pubblicati sull'annealing quantistico e sui dispositivi a ioni intrappolati. Le seguenti metriche di benchmark forniscono un'indicazione approssimativa dell'accuratezza e della scalabilità dei tipi di problemi sulla base di alcuni esempi. Le metriche effettive possono variare in base a varie caratteristiche del problema, come il numero di termini della funzione obiettivo (densità) e la loro localizzazione, il numero di variabili e l'ordine polinomiale.

Il "Numero di qubit" indicato non è un limite rigido, ma rappresenta una soglia approssimativa in cui ci si può aspettare una precisione di soluzione estremamente costante. Sono stati risolti con successo problemi di dimensioni maggiori e si incoraggia la sperimentazione oltre questi limiti.

La connettività arbitraria dei qubit è supportata in tutti i tipi di problemi.

Tipo di problema
Numero di qubit
Esempio
Accuratezza
Tempo totale
Utilizzo del tempo di esecuzione (s)
Numero di iterazioni
Problemi quadratici scarsamente connessi1563-regolare max-cut100%176429316
Ottimizzazione binaria di ordine superiore156Modello Ising spin-glass100%146127216
Problemi quadratici densamente connessi50Taglio massimo con connessione completa100%175826812
Problema con vincoli rigidi50Partizionamento di grafi ponderati con una densità di archi dell'8%100%107421510

Introduzione

Innanzitutto, autenticati utilizzando la tua chiave API IBM Quantum. Quindi, selezionare la funzione Qiskit come segue. (Questo frammento di codice presuppone che tu abbia già salvato il tuo account nell'ambiente locale.)

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")

Esempio: Ottimizzazione senza vincoli

Esegui il problema del taglio massimo (max-cut). L'esempio seguente illustra le funzionalità del Solver su un problema di taglio massimo in un grafo non ponderato a 3 regolari con 156 nodi, ma è possibile risolvere anche problemi relativi a grafi ponderati.

Oltre a qiskit-ibm-catalog, per eseguire questo esempio si useranno anche i seguenti pacchetti: networkx e numpy. Potete installare questi pacchetti decommentando la seguente cella se state eseguendo questo esempio in un notebook che utilizza il kernel IPython.

# %pip install networkx numpy

1. Definire il problema

È possibile risolvere un problema di taglio massimo definendo un problema grafico 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
)

Output:

Output of the previous code cell

Il risolutore 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 si utilizza il metodo di input basato sul grafico, specificare il tipo di problema.

# Solve the problem
maxcut_job = solver.run(
    problem=problem_as_str,
    problem_type="maxcut",
    backend_name=backend_name,  # E.g. "ibm_fez"
)

Per verificare lo stato del carico di lavoro della funzione Qiskit o per ottenere i risultati, procedere 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())

Output:

34b53970-d95a-4e24-8763-fc6f3d112843
QUEUED

3. Recupera il risultato

Recupera il valore di taglio ottimale dal dizionario dei risultati.

Note

La mappatura delle variabili alla stringa di bit potrebbe essere cambiata. Il dizionario di output contiene un variables_to_bitstring_index_map sottodizionario 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}")

Output:

Optimal cut value: 210.0

È possibile verificare l'accuratezza del risultato risolvendo il problema in modo classico con solutori open-source come PuLP se il grafo non è densamente connesso. I problemi ad alta densità possono richiedere solutori classici avanzati per convalidare la soluzione.


Esempio: Ottimizzazione vincolata

L'esempio precedente relativo al metodo max-cut è un comune problema di ottimizzazione binaria quadratica senza vincoli. Il risolutore di ottimizzazione di Q-CTRL è in grado di risolvere anche problemi di ottimizzazione con vincoli, passando i vincoli rigidi direttamente al risolutore tramite l'input constraint , anziché codificarli come termini di penalità nella funzione obiettivo. Attualmente il Solver supporta i vincoli di tipo " Hamming-weight-1 ": ciascun vincolo specifica un gruppo di variabili in cui esattamente una variabile deve essere uguale a 1 e le altre devono essere uguali a 0.

L'esempio seguente illustra come costruire una funzione di costo e un insieme di vincoli rigidi per un problema di ottimizzazione con vincoli, ovvero la partizionamento di un grafo, assegnando ogni nodo di un grafo esattamente a uno dei diversi gruppi e minimizzando al contempo il peso totale degli archi i cui estremi appartengono allo stesso gruppo.

Oltre ai pacchetti qiskit-ibm-catalog e qiskit , per eseguire questo esempio si utilizzeranno anche i seguenti pacchetti: numpy, networkx, e sympy. Potete installare questi pacchetti decommentando la seguente cella se state eseguendo questo esempio in un notebook che utilizza il kernel IPython.

# %pip install numpy networkx sympy

1. Definire il problema

Definire un problema di partizionamento casuale di un grafo generando un grafo con nodi ponderati in modo casuale.

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 modello standard di ottimizzazione per la partizionamento dei grafi ponderati può essere formulato come segue. Suddividiamo i nodi del grafo in tre gruppi g{0,1,2}g \in \{0, 1, 2\} e poniamo che ni,g=1n_{i,g} = 1 se il nodo ii è assegnato al gruppo gg, e ni,g=0n_{i,g} = 0 in caso contrario. L'obiettivo è ridurre al minimo il peso totale dei bordi i cui estremi appartengono allo stesso gruppo, dove il peso di un bordo (i,j)(i,j) è dato dalla somma dei pesi dei suoi due estremi, ω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)]
        )

Ogni nodo deve essere assegnato esattamente a uno dei tre gruppi. Si tratta di un vincolo di tipo “ Hamming-weight-1 ”: per ogni nodo ii, esattamente uno tra ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} deve essere pari a 1, mentre gli altri devono essere pari 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

Anziché codificare questo requisito come termine di penalità nella funzione di costo, inseriscilo direttamente nel Solver come vincolo rigido utilizzando il campo di immissione 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}
Problemi parzialmente vincolati

Non è necessario aggiungere tutte le variabili a constraint. Qualsiasi variabile non inclusa nel dizionario rimane non vincolata; è quindi possibile combinare, nello stesso problema, gruppi di variabili con vincoli rigidi e variabili libere.

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"
)

Per verificare lo stato del carico di lavoro della funzione Qiskit o per ottenere i risultati, procedere 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())

Output:

b8085944-f313-444e-be39-ea61b1b47ebd
QUEUED

3. Ottieni il risultato

Recuperare la soluzione e analizzare i risultati. Il costo della soluzione rappresenta il peso totale dei bordi i cui estremi sono finiti nello stesso gruppo; pertanto, un costo inferiore indica una migliore partizionamento 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

Ottenere supporto

Per qualsiasi domanda o problema, contattate Q-CTRL.


Log di modifica

  • 10 agosto 2026: è stato aggiunto il supporto per i vincoli rigidi (peso di Hamming pari a 1) tramite il parametro di input constraint , ed è stato aggiornato l'esempio di ottimizzazione con vincoli per utilizzarli.
  • 11 febbraio 2026: ora supportiamo ibm_miami

Passi successivi

Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.