Codifica di correlazione di Pauli per ridurre i requisiti del taglio massimo
Stima della durata: 35 minuti su un processore Eagle r3 (NOTA: si tratta solo di una stima). (La durata potrebbe variare.)
Risultati di apprendimento
- Comprendere i principi teorici alla base della codifica di correlazione di Pauli (PCE), compreso il modo in cui le stringhe di Pauli a più corpi consentono una compressione polinomiale dei problemi di ottimizzazione classici.
- Applicare il PCE nella pratica per codificare e risolvere problemi di ottimizzazione su larga scala su hardware quantistico di prossima generazione.
Prerequisiti
Sfondo
Questo tutorial presenta la codifica di correlazione di Pauli (PCE ) [1], un approccio progettato per codificare i problemi di ottimizzazione in qubit con una maggiore efficienza per il calcolo quantistico. Il PCE mappa le variabili classiche dei problemi di ottimizzazione in correlazioni multi-corpo della matrice di Pauli, ottenendo una compressione polinomiale dei requisiti di spazio del problema. L'impiego della PCE riduce il numero di qubit necessari per la codifica, rendendola particolarmente vantaggiosa per i dispositivi quantistici di prossima generazione con risorse di qubit limitate. Inoltre, viene dimostrato analiticamente che il PCE mitiga intrinsecamente i plateau sterili, offrendo una resilienza super-polinomiale contro questo fenomeno. Questa caratteristica integrata consente di ottenere prestazioni senza precedenti nei solutori di ottimizzazione quantistica.
Panoramica
L'approccio PCE si articola in tre fasi principali, come illustrato nella Figura 1 tratta da [1] riportata di seguito:
- Codifica del problema di ottimizzazione in uno spazio di correlazione di Pauli.
- Risolvere il problema utilizzando un risolutore di ottimizzazione quantistico-classico.
- Decodifica della soluzione nello spazio di ottimizzazione originale. L'approccio PCE è adattabile a qualsiasi solutore di ottimizzazione quantistica in grado di elaborare matrici di correlazione di Pauli.
Nella Figura 1 tratta da [1], il problema del taglio massimo viene utilizzato come esempio per illustrare l'approccio PCE. Il problema del max-cut con nodi è codificato in uno spazio di correlazione di Pauli, che rappresenta il problema di ottimizzazione come una matrice di correlazione — in particolare, correlazioni della matrice di Pauli a due corpi tra i qubit . I colori dei nodi indicano la stringa di Pauli utilizzata per ciascun nodo codificato. Ad esempio, il nodo 1, che corrisponde alla variabile binaria , è codificato dal valore atteso di , mentre è codificato da . Ciò equivale a comprimere le variabili del problema in qubit. Più in generale, le correlazioni a n-corpi di tipo " " consentono compressioni polinomiali dell'ordine di , con . L'insieme di Pauli scelto comprende tre sottoinsiemi di stringhe di Pauli reciprocamente commutanti, consentendo di stimare sperimentalmente tutte le correlazioni di tipo " " con sole tre configurazioni di misura.
Viene costruita una funzione di perdita basata sui valori attesi di Pauli che riproduce la funzione obiettivo originale del max-cut. La funzione di perdita viene quindi ottimizzata utilizzando un risolutore di ottimizzazione quantistico-classico, come il Variational Quantum Eigensolver (VQE).
Una volta completata l'ottimizzazione, la soluzione viene ricodificata nello spazio di ottimizzazione originale, fornendo la soluzione ottimale del problema del taglio massimo.
Requisiti
Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:
- Qiskit SDK v1.0 o versioni successive, con supporto alla visualizzazione
- Qiskit Runtime v0.22 o successivamente (
pip install qiskit-ibm-runtime) - Componente aggiuntivo "Optimization Mapper" per Qiskit (
pip install qiskit-addon-opt-mapper)
Configura
from itertools import combinations
import numpy as np
import rustworkx as rx
import networkx as nx
from scipy.optimize import minimize, OptimizeResult
from qiskit.circuit.library import efficient_su2
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session
from rustworkx.visualization import mpl_draw
from qiskit_aer import AerSimulator
from qiskit_addon_opt_mapper.applications import Maxcutdef make_cut_evaluator(problem):
"""Return a function that maps a 0/1 node assignment to its cut value.
The objective of the max-cut ``OptimizationProblem`` is the cut value
itself, and ``problem.objective.evaluate`` computes it for one
assignment. The swap search in Step 4 evaluates one candidate
assignment per edge, so pull the objective's coefficients out once
and evaluate them as a sparse matrix product instead.
"""
quadratic = problem.objective.quadratic.coefficients.tocsr()
linear = problem.objective.linear.to_array()
constant = problem.objective.constant
def evaluate_cut(bits):
x = np.asarray(bits, dtype=float)
return int(round(constant + linear @ x + x @ (quadratic @ x)))
return evaluate_cutEsempio di simulatore su piccola scala
service = QiskitRuntimeService()
real_backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=156
)
backend = AerSimulator.from_backend(real_backend)
print(f"We are using the {backend.name}")Output:
We are using the aer_simulator_from(ibm_pittsburgh)
Fase 1: mappare gli input classici su un problema quantistico
Il problema del taglio massimo
Il problema del taglio massimo è un problema di ottimizzazione combinatoria definito su un grafo , dove è l'insieme dei vertici e è l'insieme dei bordi. L'obiettivo è quello di suddividere i vertici in due insiemi, e , in modo tale da massimizzare il numero di spigoli tra i due insiemi. Per una descrizione dettagliata del problema del taglio massimo, si prega di consultare il tutorial sull'algoritmo di ottimizzazione approssimativa quantistica. Il problema del taglio massimo viene utilizzato anche come esempio nel tutorial " Tecniche avanzate per il QAOA ". In quei tutorial, l'algoritmo QAOA viene utilizzato per risolvere il problema del taglio massimo.
Grafico → problema di ottimizzazione
Consideriamo innanzitutto un grafico casuale con 100 nodi.
num_nodes = 100 # Number of nodes in graph
seed = 42
graph = rx.undirected_gnp_random_graph(num_nodes, 0.1, seed=seed)
mpl_draw(graph)Output:
Modellare il problema del max-cut utilizzando la classe di applicazione Maxcut dell 'add-on Optimization Mapper per Qiskit. Restituisce un oggetto con OptimizationProblem una variabile binaria per ogni nodo, il cui obiettivo è il valore del taglio da massimizzare. Nei tutorial QAOA, questo problema viene convertito in un QUBO e tradotto in un hamiltoniano di costo con un qubit per ogni variabile. Il PCE, invece, codifica ciascuna variabile binaria nel segno di un valore atteso di Pauli; in questo caso, quindi, il problema definisce e valuta l’obiettivo, mentre la codifica in qubit segue di seguito.
maxcut = Maxcut(graph)
maxcut_problem = maxcut.to_optimization_problem()
evaluate_cut = make_cut_evaluator(maxcut_problem)
print(f"Binary variables: {maxcut_problem.get_num_vars()}")
print(
"Quadratic terms (one per edge):",
len(maxcut_problem.objective.quadratic.to_dict()),
)Output:
Binary variables: 100
Quadratic terms (one per edge): 508
nx_graph = nx.Graph()
nx_graph.add_nodes_from(range(num_nodes))for edge in graph.edge_list():
nx_graph.add_edge(edge[0], edge[1])curr_cut_size, partition = nx.approximation.one_exchange(nx_graph, seed=1)
print(f"Initial cut size: {curr_cut_size}")Output:
Initial cut size: 345
Codifichiamo il grafico con 100 nodi in correlazioni a matrice di Pauli a due corpi su nove qubit (vedi la spiegazione qui sotto). Il grafico è rappresentato come una matrice di correlazione, in cui ogni nodo è codificato da una stringa di Pauli. Il segno del valore atteso della stringa di Pauli indica la partizione del nodo. Ad esempio, il nodo 0 è rappresentato da una stringa di Pauli, . Il segno del valore atteso di questa stringa di Pauli indica la partizione del nodo 0. Definiamo una codifica a correlazione di Pauli (PCE) relativa a un e come
dove è la partizione del nodo e è il valore di aspettativa della stringa di Pauli che codifica il nodo su uno stato quantistico .
Ora codifichiamo il grafo in un'hamiltoniana usando PCE. Dividiamo i nodi in tre insiemi: , , e . Quindi, codifichiamo i nodi di ciascun insieme utilizzando le stringhe di Pauli con , , e , rispettivamente.
Dobbiamo individuare una relazione tra il numero di nodi e i qubit necessari per codificare tutti i nodi. Utilizzando tutte le possibili permutazioni per la codifica si ottiene:
In questo esempio consideriamo un e, quindi,
Pertanto, il numero di qubit necessario per rappresentare un determinato numero di nodi si legge come:
Si noti che il simbolo rappresenta la funzione tetto, che arrotonda per eccesso qualsiasi numero reale all'intero superiore. Ciò garantisce che il numero di qubit sia un numero intero.
num_qubits = int(np.ceil((1 + np.sqrt(1 + (8 / 3) * num_nodes)) / 2))
list_size = num_nodes // 3
node_x = [i for i in range(list_size)]
node_y = [i for i in range(list_size, 2 * list_size)]
node_z = [i for i in range(2 * list_size, num_nodes)]
print(f"Number of qubits: {num_qubits}")
print("List 1:", node_x)
print("List 2:", node_y)
print("List 3:", node_z)Output:
Number of qubits: 9
List 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]
List 2: [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65]
List 3: [66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
def build_pauli_correlation_encoding(pauli, node_list, n, k=2):
pauli_correlation_encoding = []
for idx, c in enumerate(combinations(range(n), k)):
if idx >= len(node_list):
break
paulis = ["I"] * n
paulis[c[0]], paulis[c[1]] = pauli, pauli
pauli_correlation_encoding.append(("".join(paulis)[::-1], 1))
hamiltonian = []
for pauli, weight in pauli_correlation_encoding:
hamiltonian.append(SparsePauliOp.from_list([(pauli, weight)]))
return hamiltonian
pauli_correlation_encoding_x = build_pauli_correlation_encoding(
"X", node_x, num_qubits
)
pauli_correlation_encoding_y = build_pauli_correlation_encoding(
"Y", node_y, num_qubits
)
pauli_correlation_encoding_z = build_pauli_correlation_encoding(
"Z", node_z, num_qubits
)Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico
Circuito quantistico
In questo caso, lo stato è parametrizzato con , e ottimizziamo questi parametri utilizzando un approccio variazionale.
Questo tutorial utilizza l'ansatz di efficient_su2 per il nostro algoritmo variazionale, grazie alle sue capacità espressive e alla facilità di implementazione.
Utilizziamo anche la funzione di perdita rilassata, che verrà introdotta più avanti in questo tutorial.
Di conseguenza, possiamo affrontare problemi su larga scala con un minor numero di qubit e una minore profondità dei circuiti.
# Build the quantum circuit
qc = efficient_su2(num_qubits, su2_gates=["ry", "rz"], reps=2)
qc.draw("mpl")Output:
# Optimize the circuit
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
qc = pm.run(qc)Funzione di perdita
Per la funzione di perdita , utilizziamo un rilassamento della funzione obiettivo del max-cut, come descritto in [1], definita come . Qui, indica il peso dell'arco , mentre rappresenta la partizione del nodo . La funzione di perdita è data da:
,
in cui la funzione obiettivo max-cut viene sostituita dalle tangenti iperboliche regolarizzate dei valori attesi delle stringhe di Pauli che codificano i nodi. Per migliorare le prestazioni del risolutore vengono introdotti il termine di regolarizzazione e il fattore di riscalatura , proporzionale al numero di qubit.
Il termine di regolarizzazione è definito come:
è definito come
dove , , indica il numero di spigoli, mentre indica il numero di vertici del grafo.
def loss_func_estimator(x, ansatz, hamiltonian, estimator, graph):
"""
Calculates the specified loss function for the given ansatz, Hamiltonian,
and graph.
The expectation values of each Pauli string in the Hamiltonian are first
obtained by running the ansatz on the quantum backend. These
expectation values are then passed through the nonlinear function
tanh(alpha * prod_i). The loss function is
subsequently computed from these transformed values.
"""
job = estimator.run(
[
(ansatz, hamiltonian[0], x),
(ansatz, hamiltonian[1], x),
(ansatz, hamiltonian[2], x),
]
)
result = job.result()
# calculate the loss function
node_exp_map = {}
idx = 0
for r in result:
for ev in r.data.evs:
node_exp_map[idx] = ev
idx += 1
loss = 0
alpha = num_qubits
for edge0, edge1 in graph.edge_list():
loss += np.tanh(alpha * node_exp_map[edge0]) * np.tanh(
alpha * node_exp_map[edge1]
)
regulation_term = 0
for i in range(len(graph.nodes())):
regulation_term += np.tanh(alpha * node_exp_map[i]) ** 2
regulation_term = regulation_term / len(graph.nodes())
regulation_term = regulation_term**2
beta = 1 / 2
v = len(graph.edges()) / 2 + (len(graph.nodes()) - 1) / 4
regulation_term = beta * v * regulation_term
loss = loss + regulation_term
global experiment_result
print(f"Iter {len(experiment_result)}: {loss}")
experiment_result.append({"loss": loss, "exp_map": node_exp_map})
return lossPassaggio 3: eseguire utilizzando Qiskit primitives
In questo tutorial, a scopo dimostrativo, abbiamo impostato max_iter=50 nel ciclo di ottimizzazione. Se aumentiamo il numero di iterazioni, possiamo aspettarci risultati migliori.
pce = []
pce.append(
[op.apply_layout(qc.layout) for op in pauli_correlation_encoding_x]
)
pce.append(
[op.apply_layout(qc.layout) for op in pauli_correlation_encoding_y]
)
pce.append(
[op.apply_layout(qc.layout) for op in pauli_correlation_encoding_z]
)max_iter = 50
counter = {"i": 0}
last_x = {"value": None}
last_fun = {"value": None}
with Session(backend=backend) as session:
estimator = Estimator(mode=session)
experiment_result = []
def loss_func(x):
last_x["value"] = x.copy()
if counter["i"] + 1 > max_iter:
return last_fun["value"]
counter["i"] += 1
val = loss_func_estimator(
x, qc, [pce[0], pce[1], pce[2]], estimator, graph
)
last_fun["value"] = val
return val
np.random.seed(seed)
initial_params = np.random.rand(qc.num_parameters)
result = minimize(
loss_func, initial_params, method="COBYLA", options={"rhobeg": 1.0}
)
if counter["i"] >= max_iter:
result = OptimizeResult(
message=f"Return from COBYLA because the objective function "
f"has been evaluated {max_iter} times.",
success=False,
status=3,
fun=last_fun["value"],
x=last_x["value"],
nfev=counter["i"],
)
print(result)Output:
Iter 0: 152.06331779109445
Iter 1: 126.93132569244693
Iter 2: 55.00651109478942
Iter 3: 33.21667439735082
Iter 4: 19.167189700765054
Iter 5: 30.426875553389127
Iter 6: 13.906354614925815
Iter 7: 27.668686100172614
Iter 8: 28.194600411380772
Iter 9: 24.899677141564936
Iter 10: 22.10858041608358
Iter 11: 8.096210209030176
Iter 12: 8.761240650022913
Iter 13: 9.43332962472133
Iter 14: 7.559669820210062
Iter 15: 13.023860357384214
Iter 16: 11.953653163007987
Iter 17: 10.434221292269214
Iter 18: 4.871246922225549
Iter 19: 10.303826533395856
Iter 20: 16.40225843511339
Iter 21: 10.852469442638652
Iter 22: 9.314666066331633
Iter 23: 6.082358010011717
Iter 24: 10.982893700414476
Iter 25: 4.575556036893407
Iter 26: 6.844995727798197
Iter 27: 8.695229718011293
Iter 28: 3.730227705075576
Iter 29: 4.285479467758721
Iter 30: 4.219714169314187
Iter 31: 0.21125608131726548
Iter 32: 4.22820465672552
Iter 33: 13.67513152473414
Iter 34: 10.388838701884863
Iter 35: -0.5400153834163355
Iter 36: 5.310281742761159
Iter 37: -0.5430772019249082
Iter 38: -4.47820293096989
Iter 39: -3.4832056790567467
Iter 40: -0.5087341672330172
Iter 41: -2.488777859669888
Iter 42: 0.34945469121089356
Iter 43: -0.8544299675507805
Iter 44: -3.5902371208579344
Iter 45: 2.6609573847203216
Iter 46: -4.336467834641085
Iter 47: 0.10158504073828034
Iter 48: -1.3506458813942084
Iter 49: -2.1794894170194192
message: Return from COBYLA because the objective function has been evaluated 50 times.
success: False
status: 3
fun: -2.1794894170194192
x: [ 1.375e+00 1.951e+00 ... 9.395e-01 8.948e-01]
nfev: 50
Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato
Le partizioni dei nodi vengono determinate calcolando il segno dei valori attesi delle stringhe di Pauli che codificano i nodi. Un valore atteso non negativo assegna al nodo il valore 1, mentre un valore atteso negativo assegna 0. Il metodo interpret decodifica quindi Maxcut questa assegnazione nei due insiemi di nodi.
# Decode the node assignment from the iteration with the lowest loss.
# If the expectation value is non-negative, the node is assigned 1,
# otherwise it is assigned 0.
def get_bits(experiment_result):
best_index = min(
range(len(experiment_result)),
key=lambda i: experiment_result[i]["loss"],
)
exp_map = experiment_result[best_index]["exp_map"]
bits = [1 if exp_map[i] >= 0 else 0 for i in range(len(exp_map))]
return bits, best_index
cur_bits, best_index = get_bits(experiment_result)
partition = maxcut.interpret(np.array(cur_bits))
print("Partition 0:", partition[0])
print("Partition 1:", partition[1])Output:
Partition 0: [0, 1, 3, 5, 6, 7, 9, 11, 12, 14, 15, 17, 22, 24, 25, 28, 31, 32, 33, 35, 37, 38, 39, 40, 41, 43, 49, 53, 54, 56, 59, 61, 63, 65, 67, 68, 69, 77, 78, 79, 80, 88, 92, 93, 94, 95]
Partition 1: [2, 4, 8, 10, 13, 16, 18, 19, 20, 21, 23, 26, 27, 29, 30, 34, 36, 42, 44, 45, 46, 47, 48, 50, 51, 52, 55, 57, 58, 60, 62, 64, 66, 70, 71, 72, 73, 74, 75, 76, 81, 82, 83, 84, 85, 86, 87, 89, 90, 91, 96, 97, 98, 99]
Possiamo calcolare la dimensione del taglio nel problema del taglio massimo valutando l'obiettivo della funzione su OptimizationProblem questa assegnazione.
cut_size = evaluate_cut(cur_bits)
print(f"Cut size: {cut_size}")Output:
Cut size: 278
Una volta completato l'addestramento, eseguiamo un ciclo di ricerca con scambio di un singolo bit per migliorare la soluzione, come una classica fase di post-elaborazione. In questo processo, scambiamo le partizioni di due nodi e valutiamo la dimensione del taglio. Se le dimensioni del taglio migliorano, manteniamo lo scambio. Ripetiamo questo processo per tutte le possibili coppie di nodi collegati da un bordo.
# Swap the partitions and calculate the cut size
def swap_partitions(graph, cur_bits, evaluate_cut):
best_cut = 0
best_bits = []
for edge0, edge1 in graph.edge_list():
swapped_bits = cur_bits.copy()
swapped_bits[edge0], swapped_bits[edge1] = (
swapped_bits[edge1],
swapped_bits[edge0],
)
cut_size = evaluate_cut(swapped_bits)
if best_cut < cut_size:
best_cut = cut_size
best_bits = swapped_bits
return best_cut, best_bits
best_cut, best_bits = swap_partitions(graph, cur_bits, evaluate_cut)
print(best_cut, best_bits)Output:
287 [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]
Esempio di hardware su larga scala
# -------------------------Step 1-------------------------
num_nodes = 1500 # Number of nodes in graph
graph = rx.undirected_gnp_random_graph(num_nodes, 0.1, seed=seed)
nx_graph = nx.Graph()
nx_graph.add_nodes_from(range(num_nodes))
for edge in graph.edge_list():
nx_graph.add_edge(edge[0], edge[1])
maxcut = Maxcut(graph)
maxcut_problem = maxcut.to_optimization_problem()
evaluate_cut = make_cut_evaluator(maxcut_problem)
num_qubits = int(np.ceil((1 + np.sqrt(1 + (8 / 3) * num_nodes)) / 2))
list_size = num_nodes // 3
node_x = [i for i in range(list_size)]
node_y = [i for i in range(list_size, 2 * list_size)]
node_z = [i for i in range(2 * list_size, num_nodes)]
pauli_correlation_encoding_x = build_pauli_correlation_encoding(
"X", node_x, num_qubits
)
pauli_correlation_encoding_y = build_pauli_correlation_encoding(
"Y", node_y, num_qubits
)
pauli_correlation_encoding_z = build_pauli_correlation_encoding(
"Z", node_z, num_qubits
)
print(f"We are using {num_qubits} qubits")
# -------------------------Step 2-------------------------
backend = real_backend
print(f"We are using the {backend.name}")
qc = efficient_su2(num_qubits, ["ry", "rz"], reps=2)
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
qc = pm.run(qc)
# -------------------------Step 3-------------------------
pce = []
pce.append(
[op.apply_layout(qc.layout) for op in pauli_correlation_encoding_x]
)
pce.append(
[op.apply_layout(qc.layout) for op in pauli_correlation_encoding_y]
)
pce.append(
[op.apply_layout(qc.layout) for op in pauli_correlation_encoding_z]
)
# Run the optimization using a session.
max_iter = 50
counter = {"i": 0}
with Session(backend=backend) as session:
estimator = Estimator(mode=session)
estimator.options.environment.job_tags = ["TUT_PCEFQ"]
experiment_result = []
def loss_func(x):
last_x["value"] = x.copy()
if counter["i"] + 1 > max_iter:
return last_fun["value"]
counter["i"] += 1
val = loss_func_estimator(
x, qc, [pce[0], pce[1], pce[2]], estimator, graph
)
last_fun["value"] = val
return val
np.random.seed(seed)
initial_params = np.random.rand(qc.num_parameters)
result = minimize(
loss_func, initial_params, method="COBYLA", options={"rhobeg": 1.0}
)
if counter["i"] >= max_iter:
result = OptimizeResult(
message=f"Return from COBYLA because the objective function "
f"has been evaluated {max_iter} times.",
success=False,
status=3,
fun=last_fun["value"],
x=last_x["value"],
nfev=counter["i"],
)
print(result)
# -------------------------Step 4-------------------------
cur_bits, best_index = get_bits(experiment_result)
cut_size = evaluate_cut(cur_bits)
print(f"Cut size: {cut_size}")
best_cut, best_bits = swap_partitions(graph, cur_bits, evaluate_cut)
# Print final solution
print(
f"The best max-cut value achieved for a graph with {num_nodes} nodes "
f"on {num_qubits} qubits is {best_cut}"
)
print(f"and the specific partition we obtained is {best_bits}")Output:
We are using 33 qubits
We are using the ibm_pittsburgh
Iter 0: 62192.56098951446
Iter 1: 54325.76340818571
Iter 2: 42933.215216568526
Iter 3: 35721.92960658595
Iter 4: 35947.69927402131
Iter 5: 38563.731543582166
Iter 6: 32552.97070835923
Iter 7: 34730.314616527576
Iter 8: 27032.920190652876
Iter 9: 27126.107795624957
Iter 10: 25268.853826648774
Iter 11: 23521.1489346676
Iter 12: 21658.585394613096
Iter 13: 15154.765728388778
Iter 14: 14826.985321939152
Iter 15: 16199.811009055386
Iter 16: 14328.589985020852
Iter 17: 13542.67789354434
Iter 18: 11737.341026421902
Iter 19: 10986.431644208033
Iter 20: 9780.61657788984
Iter 21: 8555.757154943312
Iter 22: 10465.489221259657
Iter 23: 8577.158776664453
Iter 24: 8950.176436175909
Iter 25: 7395.8803219317215
Iter 26: 7228.1129297581765
Iter 27: 6881.076354027118
Iter 28: 6626.8983738084835
Iter 29: 7302.433279572058
Iter 30: 6995.735525608492
Iter 31: 6176.828001182083
Iter 32: 6299.8061884979725
Iter 33: 6498.921256598082
Iter 34: 6184.684072713035
Iter 35: 5707.720358527108
Iter 36: 5414.328928670104
Iter 37: 5771.80573980932
Iter 38: 5620.64903695431
Iter 39: 6285.735849503706
Iter 40: 5894.367571492991
Iter 41: 6079.275064349491
Iter 42: 5267.448536682269
Iter 43: 4789.7281459863325
Iter 44: 5109.106057200384
Iter 45: 5060.248865782273
Iter 46: 5064.786713246322
Iter 47: 5474.433264995371
Iter 48: 4675.6138719238015
Iter 49: 5081.660754434205
message: Return from COBYLA because the objective function has been evaluated 50 times.
success: False
status: 3
fun: 5081.660754434205
x: [ 1.375e+00 1.951e+00 ... 7.260e-01 8.971e-01]
nfev: 50
Cut size: 55507
The best max-cut value achieved for a graph with 1500 nodes on 33 qubits is 55567
and the specific partition we obtained is [1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1]
Passi successivi
Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:
Riferimenti
[1] Sciorilli, M., Borges, L., Patti, T. L., García-Martín, D., Camilo, G., Anandkumar, A., & Aolita, L. (2024). Verso risolutori di ottimizzazione quantistica su larga scala con pochi qubit. arXiv preprint arXiv:2401.09421.
Sondaggio tutorial
Vi invitiamo a partecipare a questo breve sondaggio per fornire un feedback su questa esercitazione. Le vostre indicazioni ci aiuteranno a migliorare la nostra offerta di contenuti e l'esperienza degli utenti.
© IBM Corp. 2024-2026