Skip to main content
IBM Quantum Platform

Esperimento su scala industriale I

Note

Tamiya Onodera (5 luglio 2024)

Scarica il pdf della lezione originale. Si noti che alcuni frammenti di codice potrebbero diventare deprecati, poiché si tratta di immagini statiche.

Il tempo approssimativo di esecuzione di questo esperimento da parte della QPU è di 45 secondi.


1. Introduzione al documento di utilità

In questa lezione, eseguiamo un circuito su scala di utilità che appare in quello che chiamiamo informalmente "il documento dell'utilità", pubblicato su Nature Vol. 618, 15 giugno 2023. L'articolo tratta l'evoluzione temporale del modello di Ising a campo trasverso 2D. In particolare, si considera la dinamica temporale dell'hamiltoniana,

H=HZZ+HX=J(i,j)ZiZj+hiXiH = H_{ZZ} + H_X = - J \sum_{(i,j)} Z_i Z_j + h \sum_{i} X_i

in cui J>0J > 0 è l'accoppiamento degli spin più vicini con i<ji < j e hh è il campo trasversale globale.

Simulano la dinamica degli spin a partire da uno stato iniziale mediante una decomposizione di Trotter del primo ordine dell'operatore di evoluzione temporale,

exp(iHZZδt)=(i,j)exp(iJδtZiZj)=(i,j)RZiZj(2Jδt)exp(iHXδt)=iexp(ihδtXi)=iRXi(2hδt)\begin{aligned} \exp(-i H_{ZZ} \delta t) &= \prod_{(i,j)} \exp (i J \delta t Z_i Z_j) = \prod_{(i,j)} \mathrm{R}_{Z_i Z_j} ( - 2 J \delta t) \\ \exp(-i H_X \delta t) &= \prod_{i} \exp (-i h \delta t X_i ) = \prod_{i} \mathrm{R}_{X_i} ( 2 h \delta t) \end{aligned}

in cui il tempo di evoluzione TT è discretizzato in T/δtT / \delta t passi di Trotter e RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J) e RXi(θh)\mathrm{R}_{X_i}(\theta_h) sono rispettivamente le porte di rotazione ZZZZ e XX.

Gli esperimenti sono stati condotti su un processore IBM Quantum® Eagle, un dispositivo a 127 qubit con connettività heavy-hex, applicando le interazioni XX a tutti i qubit e le interazioni ZZZZ a tutti i bordi della mappa di accoppiamento. Si noti che tutte le interazioni di ZZZZ non possono essere applicate contemporaneamente a causa della "dipendenza dai dati". Per questo motivo, colorano la mappa di accoppiamento per raggrupparli in strati. A quelli di un livello viene assegnato lo stesso colore e possono essere applicati in parallelo.

Inoltre, per semplicità sperimentale, si sono concentrati sul caso θJ=π/2\theta_J=-\pi /2.

Il contributo innovativo dell'articolo consiste nell'aver costruito circuiti quantistici a una scala superiore alla simulazione di vettori di stato, nell'averli eseguiti su computer quantistici rumorosi e nell'essere riusciti a estrarre risultati affidabili. Hanno cioè dimostrato l'utilità dei computer quantistici rumorosi. Nel farlo, hanno applicato l'estrapolazione a rumore zero (ZNE) con l'amplificazione probabilistica degli errori (PEA) per mitigare gli errori dei dispositivi rumorosi.

Da quel momento in poi, abbiamo chiamato tali esperimenti e circuiti "utility-scale".

1.1 Il tuo obiettivo

L'obiettivo di questa lezione è quello di costruire un circuito per la produzione di energia elettrica e di farlo funzionare su un processore Eagle. È al di là dello scopo di questo quaderno estrarre risultati affidabili, in parte perché la PEA è una funzione sperimentale di Qiskit al momento della stesura del presente documento e in parte perché l'applicazione di ZNE con la PEA richiederà una discreta quantità di tempo.

Concretamente, vi viene chiesto di costruire e far funzionare il circuito corrispondente alla Figura 4b del documento, e di tracciare i punti "non mitigati" del vostro. Come vedete, si tratta di un circuito a 127-qubit ×\times a 60 strati (20 passi di Trotter) con il Z62\langle Z_{62} \rangle come osservabile.

image.png

Vi sembra una cosa importante? Non preoccuparti. Le ultime tre lezioni di questo corso forniscono delle pietre miliari. Per iniziare, dimostreremo un esperimento su scala ridotta che consiste nel costruire ed eseguire su un dispositivo finto un circuito a 27 qubit ×\times a 6 strati (2 passi di Trotter) con il Z13\langle Z_{13} \rangle come osservabile.

Questo è tutto per l'introduzione. Partiamo per un'avventura su scala di utilità!

import qiskit

qiskit.__version__

Output:

'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    fake_provider,
    EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()

2. Preparazione

2.1 Costruisci RZZ(- π\pi /2)

Innanzitutto, osserviamo che la porta RZZ in generale richiede due porte CXCX.

from qiskit.circuit.library import RZZGate

θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")

Output:

Output of the previous code cell

Come già detto, per questo esperimento ci concentriamo sul cancello RZZ con un angolo specifico, - π\pi / 2. Come mostrato nel documento, può essere realizzato con un solo gate CXCX.

qc2 = QuantumCircuit(2)

qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])

qc2.draw("mpl")

Output:

Output of the previous code cell

Definiamo un gate in termini di questo circuito per riferimento futuro.

rzz = qc2.to_gate(label="RZZ")

Facciamo un uso casuale della nuova definizione di rzz.

qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))

Output:

Output of the previous code cell

Prima di utilizzarlo ulteriormente, verifichiamo l'equivalenza logica di qc1 (la porta RZZ) per -pi/2 e la nostra nuova porta rzz o qc2 :

from qiskit.quantum_info import Operator

op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)

op1.equiv(op2)

Output:

True

2.2 Colora la mappa di accoppiamento

Studiamo come colorare la mappa di accoppiamento di un backend. Questo è necessario per raggruppare le interazioni di ZZZZ in strati.

Per cominciare, visualizziamo la mappa di accoppiamento di un backend. Si noti che le mappe di accoppiamento sono pesantemente esagonali per tutti gli attuali dispositivi IBM Quantum.

backend = service.least_busy(operational=True, simulator=False)

backend.coupling_map.draw()

Output:

Output of the previous code cell

Per colorare una mappa di accoppiamento, utilizziamo rustworkx, un pacchetto di Python per lavorare con grafi e reti complesse. Fornisce diversi algoritmi di colorazione, che sono tutti euristici e quindi non garantiscono di trovare una colorazione minima.

Detto questo, dato che i grafi heavy-hex sono bipartiti, si può prendere il grafo_bipartite_edge_color, che dovrebbe trovare una colorazione minima per questi grafi.

def color_coupling_map(backend):
    graph = backend.coupling_map.graph
    undirected_graph = graph.to_undirected(multigraph=False)
    edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
    if edge_color_map is None:
        edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
    # build a map from color to a list of edges
    edge_index_map = undirected_graph.edge_index_map()
    color_edges_map = {color: [] for color in edge_color_map.values()}
    for edge_index, color in edge_color_map.items():
        color_edges_map[color].append(
            (edge_index_map[edge_index][0], edge_index_map[edge_index][1])
        )
    return edge_color_map, color_edges_map

I grafici esagonali pesanti devono essere dipinti in tre colori. Verifichiamo questo aspetto per la mappa di accoppiamento di cui sopra.

edge_color_map, color_edges_map = color_coupling_map(backend)
print(
    f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)

Output:

ibm_strasbourg, 127-qubit device, 3 colors assigned.

Sì, è così!

Per divertimento, dipingiamo la mappa di accoppiamento alla colorazione ottenuta, utilizzando la funzione di visualizzazione di rustworks.

color_str_map = {0: "green", 1: "red", 2: "blue"}

undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
    undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
        edge_color_map[i]
    ]

rx.visualization.graphviz_draw(
    undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

Output:

Output of the previous code cell

3. Risolvere l'evoluzione temporale trotterizzata di un modello Ising dell' 2D.

Definiamo una routine per costruire un circuito della carta di utilità per l'evoluzione temporale di un modello Ising 2D. La routine prende tre parametri, un backend, un intero che indica il numero di passi di Trotter e un booleano che controlla l'inserimento della barriera.

def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
    num_qubits = backend.num_qubits
    _, color_edges_map = color_coupling_map(backend)
    θ_h = Parameter("$\\theta_h$")
    qc = QuantumCircuit(num_qubits)

    for i in range(num_steps):
        qc.rx(θ_h, range(num_qubits))

        for _, edge_list in color_edges_map.items():
            for edge in edge_list:
                qc.append(rzz, edge)

        if barrier:
            qc.barrier()
    return qc

Si noti che abbiamo già eseguito manualmente la mappatura e l'instradamento dei qubit per il circuito costruito. Pertanto, quando in seguito transpiliamo il circuito, non chiediamo (non dovremmo chiedere) al transpilatore di eseguire la mappatura e l'instradamento dei qubit. Come si vedrà tra poco, lo invochiamo con il livello di ottimizzazione pari a 1 e il metodo di layout "banale".

Quindi definiamo una semplice routine per ottenere le informazioni sul circuito costruito per un rapido controllo.

def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
    qc0 = qc.decompose(reps=reps)
    return (
        f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
        + ",  "
        + f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
    )

Esercitiamo queste routine. Si dovrebbe vedere un circuito di 27 qubit ×\times 15 strati (5 passi Trotter). Poiché il dispositivo falso ha 28 spigoli, ci dovrebbero essere 28*5 gag che si aggrovigliano.

backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)

display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

Output:

Output of the previous code cell
27 qubits × 15 layers (20-depth),  Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth),  Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5

4. Risolvere la versione a 27 qubit del problema.

Dimostriamo ora una versione su scala ridotta dell'esperimento di utilità. Costruiamo un circuito a 27-qubit ×\times a 6 strati (2 passi di Trotter) con Z13\langle Z_{13} \rangle come osservabile, e lo facciamo funzionare sia su AerSimulator che su un dispositivo falso.

Naturalmente, seguiamo il nostro flusso di lavoro in quattro fasi, il "modello Qiskit", che consiste in Map, Optimize, Execute e Post-Process. Più concretamente,

  • Mappare gli input classici in una computazione quantistica.
  • Ottimizzare i circuiti per il calcolo quantistico.
  • Eseguire circuiti utilizzando primitive.
  • Postelaborazione e restituzione dei risultati in formato classico.

Di seguito, viene presentata la fase della mappa per la creazione di un circuito per un esperimento su scala ridotta. Abbiamo quindi un set di Ottimizzazione ed Esecuzione per AerSimulator e un altro per un dispositivo falso. Infine, abbiamo la fase di Post-Process per tracciare i risultati.

4.1 Fase 1: Mappa

backend = fake_provider.FakeTorontoV2()  # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
    [("Z", [13], 1)], num_qubits=backend.num_qubits
)  # Falcon
angles = [
    0,
    0.1,
    0.2,
    0.3,
    0.4,
    0.5,
    0.6,
    0.7,
    0.8,
    1.0,
    np.pi / 2,
]  # We try 11 angles for theta_h.

4.2 Fasi 2 e 3: Ottimizzazione ed esecuzione (simulatore)

backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
    qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))

Output:

27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth),  Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28

Un utente ha eseguito la cella successiva utilizzando un MacBook Pro con 2.3 processore quad-core Intel Core i7 dotato di 32GB 3LPDDR4X RAM, che sta eseguendo macOS 14.5. Ci sono voluti 161ms nel tempo del muro. Ogni portatile sarà leggermente diverso.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()

Output:

CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms

4.3 Passaggi 2 e 3: Ottimizzazione ed esecuzione (dispositivo falso)

backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
    qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))

Output:

27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth),  Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14

Quando lo stesso utente ha eseguito la cella successiva con lo stesso ambiente di cui sopra, ha impiegato 2min 19s in Wall Time. L'esecuzione di un circuito su un dispositivo finto richiede una simulazione rumorosa che richiede molto più tempo rispetto alla simulazione esatta. Si consiglia di non eseguire un circuito di dimensioni maggiori (come ad esempio un circuito a 27-qubit ×\times 9 strati con 3 passi di Trotter) su un dispositivo falso.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()

Output:

CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s

4.4 Fase 4: Post-elaborazione

Riportiamo i risultati delle simulazioni esatte e rumorose. Si vedono i gravi effetti del rumore su FakeToronto.

plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()

Output:

Output of the previous code cell

5. Risolvere la versione a 127 qubit del problema

Il vostro obiettivo è quello di eseguire l'esperimento su scala di utilità, come indicato all'inizio. Creerete ed eseguirete un circuito a 127 qubit e 60 strati (20 passi di Trotter) con Z62\langle Z_{62} \rangle come osservabile. Si consiglia di provare a farlo da soli, utilizzando, se necessario, il codice per la versione a 27 qubit. Ma la soluzione è fornita qui.

Soluzione:

5.1 Fase 1: Mappa

# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)

num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
    [("Z", [62], 1)], num_qubits=backend_map.num_qubits
)  # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]

5.2 Fasi 2 e 3: Ottimizzazione ed esecuzione

Notiamo che la mappa di accoppiamento del processore Eagle ha 144 bordi.

# backend = service.backend("ibm_brisbane")
backend = backend_map

transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))

Output:

156 qubits × 60 layers (221-depth),  Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth),  Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])

job_id = job.job_id()
print(f"job id={job_id}")

Output:

job id=d1479n6qf56g0081sxa0

5.3 Post-elaborazione

Forniamo i valori per i punti "mitigati" nella Figura 4b del documento di utilità. Tracciate questi dati insieme ai vostri risultati.

result_paper = [
    1.0171,
    1.0044,
    0.9563,
    0.9602,
    0.8394,
    0.8120,
    0.5466,
    0.4556,
    0.1953,
    0.0141,
    0.0117,
]

# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)

plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()

Output:

Output of the previous code cell

I risultati sono simili a quelli "non mitigati" della Figura 4b? Potrebbero essere molto diversi, a seconda del dispositivo e delle sue condizioni al momento dell'esperimento. Non preoccupatevi dei risultati in sé. Verificheremo se la codifica è stata eseguita correttamente. Se lo avete fatto, congratulazioni, avete raggiunto la linea di partenza dell'era delle utility.

Come nel caso del documento Utility, gli scienziati di tutto il mondo si sono ingegnati per estrarre risultati significativi anche in presenza di rumore. L'obiettivo finale di questo sforzo collettivo è il vantaggio quantistico: uno stato in cui i computer quantistici possono risolvere alcuni problemi di uso industriale più velocemente, con maggiore fedeltà o in modo più economico rispetto ai computer classici. Non si tratterà probabilmente di un singolo evento, ma piuttosto di un'era in cui la riproduzione classica dei risultati quantistici richiede tempi progressivamente più lunghi, finché a un certo punto il tempo di risposta quantistico diventa di importanza critica. Una cosa è chiara riguardo al vantaggio quantistico: ci si arriva solo attraverso gli esperimenti su scala pubblica. Se questo corso vi porterà ad unirvi alla ricerca, che è piena di sfide e divertimento, ne saremo più che felici.


Riferimenti

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