Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Gate Cutting per Ridurre la Profondità del Circuit

In questo tutorial, ridurremo la profondità di un circuito tagliando gate distanti, evitando i gate di swap che altrimenti verrebbero introdotti dal routing.

Questi sono i passaggi che seguiremo in questo pattern Qiskit:

  • Passo 1: Mappa il problema su circuit quantistici e operatori:

    • Mappa l'hamiltoniano su un circuit quantistico.
  • Passo 2: Ottimizza per l'hardware di destinazione [Usa l'addon di cutting]:

    • Taglia il circuito e l'osservabile.
    • Esegui la transpilazione dei sottosperimenti per l'hardware.

  • Passo 3: Esegui sull'hardware di destinazione:

    • Esegui i sottosperimenti ottenuti nel Passo 2 usando la primitiva Sampler.
  • Passo 4: Post-elabora i risultati [Usa l'addon di cutting]:

    • Combina i risultati del Passo 3 per ricostruire il valore di aspettazione dell'osservabile in questione.

Passo 1: Mappa​

Crea un circuito da eseguire sul Backend​

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_cutting": "qiskit-addon-cutting", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_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.circuit.library import efficient_su2

circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
circuit.draw("mpl", scale=0.8)

Diagramma del circuit quantistico

Specifica un osservabile​

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])

Passo 2: Ottimizza​

Specifica un Backend​

Puoi fornire un Backend simulato (fake) oppure un Backend hardware di Qiskit Runtime.

from qiskit_ibm_runtime.fake_provider import FakeManilaV2

backend = FakeManilaV2()

Esegui la transpilazione del circuito, visualizza gli swap e prendi nota della profondità​

Scegliamo un layout che richiede due swap per eseguire i gate tra i qubit 3 e 0 e altri due swap per riportare i qubit nelle loro posizioni iniziali.

from qiskit.transpiler import generate_preset_pass_manager

pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=[0, 1, 2, 3]
)

transpiled_qc = pass_manager.run(circuit)
print(f"Transpiled circuit depth: {transpiled_qc.depth(lambda x: len(x.qubits) >= 2)}")
Transpiled circuit depth: 30
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, fold=-1)

Diagramma del circuit quantistico

Sostituisci i gate distanti con TwoQubitQPDGate specificando i loro indici​

cut_gates sostituirà i gate agli indici specificati con TwoQubitQPDGate e restituirà anche una lista di istanze QPDBasis -- una per ciascuna decomposizione di gate.

from qiskit_addon_cutting import cut_gates

# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]

# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)

qpd_circuit.draw("mpl", scale=0.8)

Diagramma del circuit quantistico

Genera i sottosperimenti da eseguire sul Backend​

generate_cutting_experiments accetta un circuito contenente istanze TwoQubitQPDGate e osservabili come PauliList.

Per simulare il valore di aspettazione del circuito di dimensione completa, molti sottosperimenti vengono generati dalla distribuzione di quasiprobabilità congiunta dei gate decomposti e poi eseguiti su uno o più Backend. Il numero di campioni prelevati dalla distribuzione è controllato da num_samples, e viene fornito un coefficiente combinato per ciascun campione distinto. Per ulteriori informazioni su come vengono calcolati i coefficienti, consulta il materiale esplicativo.

Nota: Il parametro observables di generate_cutting_experiments è di tipo PauliList. I coefficienti dei termini dell'osservabile e le fasi vengono ignorati durante la decomposizione del problema e l'esecuzione dei sottosperimenti. Possono essere riapplicati durante la ricostruzione del valore di aspettazione.

import numpy as np
from qiskit_addon_cutting import generate_cutting_experiments

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)

Calcola il sovraccarico di campionamento per i tagli scelti​

Qui tagliamo tre gate CNOT, ottenendo un sovraccarico di campionamento pari a 939^3.

Per ulteriori informazioni sul sovraccarico di campionamento causato dal circuit cutting, consulta il materiale esplicativo.

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0

Dimostra che i sottosperimenti QPD saranno meno profondi dopo il taglio dei gate distanti​

Ecco un esempio di un sottosperimento scelto arbitrariamente generato dal Circuit QPD. La sua profondità è stata ridotta di oltre la metà. Molti di questi sottosperimenti probabilistici devono essere generati e valutati per ricostruire un valore di aspettazione del circuito più profondo.

# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pass_manager.run(subexperiments[100])

print(
f"Original circuit depth after transpile: {transpiled_qc.depth(lambda x: len(x.qubits) >= 2)}"
)
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth(lambda x: len(x.qubits) >= 2)}"
)
transpiled_qpd_circuit.draw("mpl", scale=0.8, idle_wires=False, fold=-1)
Original circuit depth after transpile: 30
QPD subexperiment depth after transpile: 7

Diagramma del circuit quantistico

Prepara i sottosperimenti per il Backend​

# Transpile the subeperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pass_manager.run(subexperiments)

Passo 3: Esegui​

Esegui i sottosperimenti usando la primitiva Sampler di Qiskit Runtime​

from qiskit_ibm_runtime import SamplerV2

# Set up the Qiskit Runtime Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)

# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()

Passo 4: Post-elabora​

Ricostruisci il valore di aspettazione​

Ricostruisci i valori di aspettazione per ciascun termine dell'osservabile e combinali per ricostruire il valore di aspettazione per l'osservabile originale.

from qiskit_addon_cutting import reconstruct_expectation_values

reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)
# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

Confronta il valore di aspettazione ricostruito con il valore di aspettazione esatto del circuito e dell'osservabile originali​

from qiskit_aer.primitives import EstimatorV2

estimator = EstimatorV2()
exact_expval = estimator.run([(circuit, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.44018555
Exact expectation value: 0.50497603
Error in estimation: -0.06479049
Relative error in estimation: -0.12830408