Przejdź do głównej treści

Cięcie przewodów do estymacji wartości oczekiwanych

Szacowane użycie: 22 sekundy na procesorze Heron (UWAGA: To jest jedynie szacunek. Twój czas wykonania może się różnić.)

Efekty kształcenia

Po ukończeniu tego samouczka użytkownicy powinni rozumieć:

  • Jak używać qiskit-addon-cutting do podziału dużego obwodu na mniejsze podobwody, zmniejszając tym samym wpływ szumu

Wymagania wstępne

Sugerujemy, aby użytkownicy zapoznali się z następującym tematem przed przystąpieniem do tego samouczka:

  • Używanie prymitywu Sampler, który jest używany w tym przepływie pracy

Tło

Circuit-knitting to termin zbiorczy obejmujący różne metody podziału obwodu na wiele mniejszych podobwodów zawierających mniej bramek lub Qubitów. Każdy z podobwodów może być wykonywany niezależnie, a końcowy wynik jest uzyskiwany przez klasyczne post-przetwarzanie wyników każdego podobwodu. Technika ta jest dostępna w dodatku Qiskit do cięcia obwodów (Circuit cutting); zapoznaj się z dokumentacją oraz innymi materiałami wprowadzającymi zawierającymi szczegółowe wyjaśnienie techniki.

Ten samouczek skupia się na metodzie zwanej cięciem przewodów, gdzie obwód jest dzielony wzdłuż przewodu [1], [2]. Należy zauważyć, że podział jest prosty w obwodach klasycznych, ponieważ wynik w miejscu podziału może być wyznaczony deterministycznie i wynosi 0 lub 1. Jednak stan Qubitu w miejscu cięcia jest, ogólnie rzecz biorąc, stanem mieszanym. Dlatego każdy podobwód musi być mierzony wielokrotnie w różnych bazach (zwykle tomograficznie kompletnej bazie, takiej jak baza Pauliego [3], [4]) i odpowiednio przygotowany w swoim stanie własnym. Poniższy rysunek (źródło: [7]) pokazuje przykład cięcia przewodów dla czterokubitowego stanu GHZ na trzy podobwody. Tutaj MjM_j oznacza zestaw baz (zwykle Pauli X, Y i Z), a PiP_i oznacza zestaw stanów własnych (zwykle 0|0\rangle, 1|1\rangle, +|+\rangle i +i|+i\rangle).

wc-1.png wc-2.png

Ponieważ każdy podobwód ma mniej Qubitów i bramek, oczekuje się, że będzie mniej podatny na szum. Ten samouczek pokazuje przykład, w którym ta metoda może być użyta do skutecznego tłumienia szumu w systemie.

Wymagania

Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane następujące elementy:

  • Qiskit SDK v2.0 lub nowszy, z obsługą wizualizacji
  • Qiskit Runtime v0.22 lub nowszy ( pip install qiskit-ibm-runtime )
  • Dodatek Qiskit do cięcia obwodów v0.10.0 lub nowszy (pip install qiskit-addon-cutting)
  • Narzędzia pomocnicze Qiskit addon 0.3 lub nowsze (pip install qiskit-addon-utils)
  • Qiskit Aer (pip install qiskit-aer )

Konfiguracja

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit.result import sampled_expectation_value

from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
cut_wires,
expand_observables,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch

Przykład małoskalowego symulatora

Ten samouczek implementuje wzorzec Qiskit do symulacji jednowymiarowego (1D) obwodu Many-Body Localization (MBL). Obwód MBL jest obwodem efektywnym sprzętowo i jest parametryzowany przez dwa parametry θ\theta i ϕ\vec{\phi}. Gdy θ\theta jest ustawione na 00, a stan początkowy jest przygotowany w 0|0\rangle dla wszystkich Qubitów, idealna wartość oczekiwana Zi\langle Z_i \rangle wynosi +1+1 dla każdego miejsca Qubitu ii niezależnie od wartości ϕ\vec{\phi}. Więcej szczegółów na temat tego obwodu jest dostępnych w tym artykule.

Należy zauważyć, że w symulatorze bezszumowym wartość oczekiwana uzyskana z cięciem obwodu i bez niego będzie taka sama.

Krok 1: Odwzorowanie klasycznych danych wejściowych na problem kwantowy

Budowa obwodu 1D MBL

Najpierw prezentujemy funkcję do budowy obwodu 1D MBL.

class MBLChainCircuit(QuantumCircuit):
def __init__(
self, num_qubits: int, depth: int, use_cut: bool = False
) -> None:
super().__init__(
num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
)
evolution = MBLChainEvolution(num_qubits, depth, use_cut)
self.compose(evolution, inplace=True)

class MBLChainEvolution(QuantumCircuit):
def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
super().__init__(
num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
)

theta = Parameter("θ")
phis = ParameterVector("φ", num_qubits)

for layer in range(depth):
layer_parity = layer % 2
# print("layer parity", layer_parity)
for qubit in range(layer_parity, num_qubits - 1, 2):
# print(qubit)
self.cz(qubit, qubit + 1)
self.u(theta, 0, np.pi, qubit)
self.u(theta, 0, np.pi, qubit + 1)
if (
use_cut
and layer_parity == 0
and (
qubit == num_qubits // 2 - 1
or qubit == num_qubits // 2
)
):
self.append(CutWire(), [num_qubits // 2])
if use_cut and layer < depth - 1 and layer_parity == 1:
if qubit == num_qubits // 2:
self.append(CutWire(), [qubit])
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)

Output of the previous code cell

Obliczamy średnią wartość oczekiwaną O=1niZiO = \frac{1}{n} \sum_i Z_i dla wszystkich Qubitów przy θ=0\theta = 0. Ponieważ idealna wartość oczekiwana Zi=1\langle Z_i \rangle = 1 \forall ii, idealna wartość oczekiwana OO wynosi również 11. Parametry ϕ\phi są wybierane losowo.

np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

Obwód musi być opatrzony adnotacją poprzez wstawienie CutWire w żądanych miejscach, aby go podzielić. W tym samouczku decydujemy się na równy podział. Obwód MBL jest zaprojektowany tak, że ustawienie use_cut=True w funkcji odpowiednio wstawia adnotację po n2\frac{n}{2} Qubitach, gdzie nn to liczba Qubitów w oryginalnym obwodzie. Przypisaliśmy również losowo wygenerowane parametry do obwodu.

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

Output of the previous code cell

Krok 2: Optymalizacja problemu do wykonania na sprzęcie kwantowym

Cięcie obwodu na mniejsze podobwody

Teraz dzielimy obwód na dwa mniejsze podobwody za pomocą qiskit-addon-cutting. qiskit-addon-cutting dołącza wirtualną bramkę Move, aby podzielić miejsce cięcia przewodu przez odpowiednie dostosowanie liczby Qubitów. Teraz tworzymy obwód z tą wirtualną bramką. Ponieważ jest jedno cięcie przewodu, liczba powiązanych Qubitów zostanie zwiększona o 1.

mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)

Output of the previous code cell

Budowa i rozszerzenie obserwowalnej

Obserwowalną, jak zdefiniowano wcześniej, będzie średnia z ZZ dla każdego Qubitu. Jednak po wstawieniu wirtualnej bramki Move efektywna liczba Qubitów w obwodzie wzrasta. Obserwowalną należy również odpowiednio rozszerzyć, aby uwzględnić tę zmianę liczby Qubitów. Należy zauważyć, że obserwowalną zawsze działa trywialnie (jako II) na dodatkowym Qubicie dodanym dla wirtualnej bramki Move.

observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])

Teraz obwód może być podzielony wzdłuż bramki Move i uzyskujemy podobwody, a także pod-obserwowalną, która jest częścią oryginalnej obserwowalnej powiązaną z każdym podobwodem.

partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

Tutaj wizualizujemy dwa podobwody:

subcircuits[0].draw("mpl", fold=-1)

Output of the previous code cell

subcircuits[1].draw("mpl", fold=-1)

Output of the previous code cell

Rozszerzenie obserwowalnej za pomocą operacji Move wymaga struktury danych PauliList. Do rekonstrukcji wartości oczekiwanej oryginalnego obwodu potrzebujemy obserwowalnej w formacie SparsePauliOp.

M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)

Jak omówiono wcześniej, dla każdego cięcia obwód wyżej w hierarchii musi być zmierzony w bazie Pauliego, a obwód niżej w hierarchii musi być przygotowany w stanie własnym tej bazy. Funkcja generate_cutting_experiments tworzy wszystkie niezbędne obwody i współczynniki powiązane z każdym obwodem wymagane do rekonstrukcji. Więcej szczegółów można znaleźć w tym artykule.

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)

Transpilacja obwodów na Backend

W pierwszym przykładzie obejmującym tylko symulację transpilujemy obwód do zestawu bramek bazowych Backendu:

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

print(backend)
<IBMBackend('ibm_fez')>

Krok 3: Wykonanie przy użyciu prymitywów Qiskit

Teraz wykonaj każdy pod-eksperyment:

pm_basis = generate_preset_pass_manager(
optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
label: pm_basis.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
sampler = SamplerV2(mode=AerSimulator())
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in basis_subexperiments.items()
}

Krok 4: Post-przetwarzanie i zwracanie wyników w żądanym formacie klasycznym

Teraz pobieramy wynik każdego uruchomionego pod-eksperymentu i rekonstruujemy wartość oczekiwaną nieprzerwanego obwodu:

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
np.float64(0.9953821063041687)
methods = [
"Uncut",
"Wire cut",
]
values = [
1,
reconstructed_expval,
] # since the ideal expectation value in noiseless simulation is +1

ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')

Output of the previous code cell

Przykład wielkoskalowego sprzętu

Teraz demonstrujemy cięcie przewodów dla 60-kubitowego obwodu MBL. Nieprzycięty, jak i przycięty obwód, zostanie wykonany na sprzęcie IBM Quantum®:

num_qubits = 60
depth = 2

# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)

# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)

# Define observable and expand to account for the wire cut
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)

# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)

# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

# Obtain subexperiments and coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)

# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
sampler.options.environment.job_tags = ["TUT_WC"]
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
results = {label: job.result() for label, job in jobs.items()}

# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real

# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]

if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])

uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)

# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]

plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()

Output of the previous code cell

uncut_expval
0.9202473958333336

Następne kroki

Zalecenia

Jeśli ta praca wydała ci się interesująca, możesz zainteresować się następującymi materiałami:

Odniesienia

[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.

[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).

[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.

[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.

[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.

[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.

[7] Majumdar, R. (2024). Efficient Reduction of Resources and Noise in Discrete Quantum Computing Circuits (Doctoral dissertation, Indian Statistical Institute - Kolkata). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y