Przejdź do głównej treści

Cięcie obwodów dla periodycznych warunków brzegowych

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

Tło

W tym notebooku rozważamy symulację periodycznego łańcucha Qubitów, w którym między każdymi dwoma sąsiednimi Qubitami istnieje operacja dwu-qubitowa, w tym między pierwszym a ostatnim Qubitem. Periodyczne łańcuchy są często spotykane w problemach z fizyki i chemii, takich jak modele Isinga i symulacje molekularne.

Obecne urządzenia IBM Quantum® są planarne. Możliwe jest bezpośrednie osadzenie niektórych periodycznych łańcuchów na topologii, gdzie pierwszy i ostatni Qubit są sąsiadami. Jednak dla wystarczająco dużych problemów pierwszy i ostatni Qubit mogą być od siebie daleko, co wymaga wielu bramek SWAP dla operacji dwu-qubitowej między tymi Qubitami. Taki problem periodycznego warunku brzegowego był badany w tej pracy.

W tym notebooku pokazujemy użycie cięcia obwodów do rozwiązania problemu periodycznego łańcucha w skali użytkowej, gdzie pierwszy i ostatni Qubit nie są sąsiadami. Przecięcie tego dalekosiężnego połączenia pozwala uniknąć dodatkowych bramek SWAP kosztem wykonania wielu instancji obwodu oraz pewnego klasycznego post-processingu. Podsumowując, cięcie można zastosować do logicznego obliczania dalekosiężnych operacji dwu-qubitowych. Innymi słowy, podejście to prowadzi do efektywnego zwiększenia łączności mapy sprzężeń, co skutkuje mniejszą liczbą bramek SWAP.

Warto zauważyć, że istnieją dwa rodzaje cięć — cięcie przewodu obwodu (zwane wire cutting) lub zastąpienie bramki dwu-qubitowej wieloma operacjami jednoQubitowymi (zwane gate cutting). W tym notebooku skupimy się na gate cutting. Więcej szczegółów na temat gate cutting znajdziesz w materiałach wyjaśniających w qiskit-addon-cutting oraz w odpowiednich źródłach. Więcej szczegółów na temat wire cutting znajdziesz w samouczku Cięcie przewodów do estymacji wartości oczekiwanych lub w samouczkach w qiskit-addon-cutting.

Wymagania

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

  • Qiskit SDK v1.2 lub nowszy (pip install qiskit)
  • Qiskit Runtime v0.3 lub nowszy (pip install qiskit-ibm-runtime)
  • Circuit cutting Qiskit addon w wersji 9.0 lub nowszej (pip install qiskit-addon-cutting)

Konfiguracja

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

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal

from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, Batch

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

Tutaj wygenerujemy obwód TwoLocal i zdefiniujemy pewne obserwable.

  • Dane wejściowe: Parametry do stworzenia obwodu
  • Dane wyjściowe: Abstrakcyjny obwód i obserwable

Rozważamy sprzętowo wydajną entangler map dla obwodu TwoLocal z periodyczną łącznością między ostatnim a pierwszym Qubitem w entangler map. Ta dalekosiężna interakcja może prowadzić do dodatkowych bramek SWAP podczas transpilacji, zwiększając tym samym głębokość obwodu.

Wybierz Backend i układ początkowy

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

W tym notebooku rozważamy periodyczny łańcuch 1D złożony z 109 Qubitów, który jest najdłuższym łańcuchem 1D w topologii 127-qubitowego urządzenia IBM Quantum. Nie jest możliwe ułożenie 109-qubitowego periodycznego łańcucha na 127-qubitowym urządzeniu tak, aby pierwszy i ostatni Qubit były sąsiadami bez wprowadzania dodatkowych bramek SWAP.

init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]

# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubits
109

Zbuduj entangler map dla obwodu TwoLocal

coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivity

Obwód TwoLocal umożliwia wielokrotne powtarzanie rotation_blocks i entangler map. W tym przypadku liczba powtórzeń określa liczbę periodycznych bramek, które należy przeciąć. Ponieważ narzut próbkowania rośnie wykładniczo wraz z liczbą cięć (więcej szczegółów znajdziesz w samouczku Cięcie przewodów do estymacji wartości oczekiwanych), w tym notebooku ustalimy liczbę powtórzeń na 2.

num_reps = 2
entangler_map = []

for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)

for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)
ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)

Output of the previous code cell

Aby zweryfikować jakość wyniku przy użyciu cięcia obwodów, musimy znać idealny wynik. Wybrany obwód wykracza poza możliwości brute-force klasycznej symulacji. Dlatego starannie ustalamy parametry obwodu, aby był Cliffordowy.

Przypiszemy wartość parametru 00 dla pierwszych dwóch warstw bramek Rx i wartość π\pi dla ostatniej warstwy. Zapewnia to, że idealny wynik tego obwodu to 1n|1\rangle^{\otimes n}, gdzie nn to liczba Qubitów. W związku z tym wartości oczekiwane Zi\langle Z_i \rangle i ZiZi+1\langle Z_i Z_{i+1} \rangle, gdzie ii jest indeksem Qubitu, wynoszą odpowiednio 1-1 i +1+1.

params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)

ansatz.assign_parameters(params, inplace=True)

Wybierz obserwable

Aby skwantyfikować korzyści z gate cutting, mierzymy wartości oczekiwane obserwabli 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle i 1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle. Jak omówiono wcześniej, idealne wartości oczekiwane wynoszą odpowiednio 1-1 i +1+1.

observables = []

for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)

for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)

observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffs

Krok 2: Optymalizacja problemu pod kątem wykonania na sprzęcie kwantowym

  • Wejście: Abstrakcyjny Circuit i obserwable
  • Wyjście: Docelowy Circuit i obserwable uzyskane przez cięcie bramek dalekiego zasięgu

Transpilacja Circuit

Zauważ, że Circuit można transpilować na tym etapie lub po cięciu. Jeśli transpilacja zostanie przeprowadzona po cięciu, konieczna będzie transpilacja każdego z podeksperymentów wygenerowanych z powodu narzutu próbkowania. Dlatego bardziej rozsądne jest przeprowadzenie transpilacji na tym etapie, aby zredukować narzut transpilacji.

Jednak jeśli transpilacja zostanie wykonana na tym etapie z natywną łącznością sprzętu, transpilator doda wiele bramek SWAP, aby umieścić periodyczną operację 2-Qubitową – zaciemniając korzyści z cięcia Circuit. Aby uniknąć tego problemu, możemy wykorzystać fakt, że znamy dokładne bramki, które należy przeciąć. W szczególności możemy utworzyć wirtualną mapę sprzężeń, dodając wirtualne połączenia między odległymi Qubitami, aby uwzględnić te periodyczne bramki 2-Qubitowe. Zagwarantuje to, że Circuit można transpilować na tym etapie bez dodawania dodatkowych bramek SWAP.

coupling_map = backend.configuration().coupling_map

# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])
pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)

virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)

Output of the previous code cell

Cięcie periodycznych połączeń dalekiego zasięgu

Teraz tniemy bramki w przetranspilowanym Circuit. Zauważ, że bramki 2-Qubitowe, które należy przeciąć, to te łączące ostatni i pierwszy Qubit układu.

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

Zastosujemy układ przetranspilowanego Circuit do obserwabli.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

Na koniec podeksperymenty są generowane przez próbkowanie różnych baz pomiarowych i preparacyjnych.

qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)

Zauważ, że cięcie dalekiego zasięgu interakcji prowadzi do wykonania wielu próbek Circuit, które różnią się bazami pomiarowymi i preparacyjnymi. Więcej informacji na ten temat można znaleźć w artykułach Constructing a virtual two-qubit gate by sampling single-qubit operations oraz Cutting circuits with multiple two-qubit unitaries.

Liczba periodycznych bramek do przecięcia jest równa liczbie powtórzeń warstwy TwoLocal, zdefiniowanej powyżej jako num_reps. Narzut próbkowania cięcia bramek wynosi 6. Dlatego łączna liczba podeksperymentów wyniesie 6num_reps6^{num\_reps}.

print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2

Transpilacja podeksperymentów

W tym momencie podeksperymenty zawierają Circuity z pewnymi bramkami 1-Qubitowymi, które nie należą do zestawu bramek bazowych. Wynika to z faktu, że cięte Qubity są mierzone w różnych bazach, a bramki rotacji używane w tym celu niekoniecznie należą do zestawu bramek bazowych. Na przykład pomiar w bazie X oznacza zastosowanie bramki Hadamarda przed standardowym pomiarem w bazie Z. Jednak Hadamard nie należy do zestawu bramek bazowych.

Zamiast stosować cały proces transpilacji do każdego z Circuitów w podeksperymentach, możemy użyć konkretnych przejść transpilacji. Szczegółowy opis wszystkich dostępnych przejść transpilacji można znaleźć w tej dokumentacji.

Zastosujemy przejścia BasisTranslator, a następnie Optimize1qGatesDecomposition, aby upewnić się, że wszystkie bramki w tych Circuitach należą do zestawu bramek bazowych. Użycie tych dwóch przejść jest szybsze niż cały proces transpilacji, ponieważ inne kroki, takie jak routing i wybór początkowego układu, nie są ponownie wykonywane.

pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)

subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)

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

  • Wejście: Docelowe Circuity
  • Wyjście: Quasi-rozkłady prawdopodobieństwa

Do wykonania ciętych Circuitów używamy prymitywu SamplerV2. Wyłączamy dynamical decoupling oraz twirling, aby wszelka poprawa wyników wynikała wyłącznie z efektywnego zastosowania cięcia bramek dla tego typu Circuit.

options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False

Teraz wyślemy zadania w trybie wsadowym.

with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)

print(f"Job ID {cut_job.job_id()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()

Krok 4: Post-processing i zwrócenie wyniku w pożądanym formacie klasycznym

  • Wejście: Quasi-rozkłady prawdopodobieństwa
  • Wyjście: Zrekonstruowane wartości oczekiwane
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

Teraz obliczymy średnią obserwabli Z-typu o wadze 1 i wadze 2.

cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])

print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495

Weryfikacja krzyżowa: Uzyskanie wartości oczekiwanej bez cięcia

Warto przeprowadzić weryfikację krzyżową, porównując zaletę techniki cięcia Circuit z wariantem bez cięcia. Tutaj obliczymy wartości oczekiwane bez cięcia Circuit. Zauważ, że taki nieprzycięty Circuit będzie cierpiał z powodu dużej liczby bramek SWAP wymaganych do implementacji operacji 2-Qubitowej między pierwszym a ostatnim Qubitem. Użyjemy funkcji sampled_expectation_value, aby uzyskać wartości oczekiwane nieprzyciętego Circuit po uzyskaniu rozkładu prawdopodobieństwa przez SamplerV2. Umożliwia to jednorodne użycie prymitywu we wszystkich instancjach. Warto jednak zauważyć, że mogliśmy użyć EstimatorV2 do bezpośredniego obliczenia wartości oczekiwanych.

if ansatz.num_clbits == 0:
ansatz.measure_all()

pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)

transpiled_circuit = pm_uncut.run(ansatz)
sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])
uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()

Teraz obliczymy średnie wartości oczekiwane wszystkich obserwabli Z-typu o wadze 1 i wadze 2 bez cięcia.

uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]

uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])

print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656

Wizualizacja

Zwizualizujmy teraz poprawę uzyskaną dla obserwabli o wadze 1 i wadze 2 przy użyciu cięcia bramek dla Circuit periodycznego łańcucha

mpl.rcParams.update(mpl.rcParamsDefault)

fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))

ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]

br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]

plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)

plt.axhline(y=0, color="k", linestyle="-")

plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)

plt.legend(fontsize=14)
plt.show()

Output of the previous code cell

Podsumowanie

Podsumowując, obliczyliśmy średnie wartości oczekiwane obserwabli Z-typu o wadze 1 i wadze 2 dla periodycznego łańcucha 1D złożonego z 109 Qubitów. W tym celu:

  • utworzyliśmy wirtualną mapę sprzężeń, dodając połączenie dalekiego zasięgu między pierwszym a ostatnim Qubitem łańcucha 1D, i transpilowaliśmy Circuit.
    • transpilacja na tym etapie pozwoliła uniknąć narzutu transpilacji każdego podeksperymentu osobno po cięciu,
    • użycie wirtualnej mapy sprzężeń pozwoliło uniknąć dodatkowych bramek SWAP dla operacji 2-Qubitowej między pierwszym a ostatnim Qubitem.
  • usunęliśmy dalekiego zasięgu łączność z przetranspilowanego Circuit poprzez cięcie bramek.
  • przekonwertowaliśmy cięte Circuity do zestawu bramek bazowych, stosując odpowiednie przejścia transpilacji.
  • wykonaliśmy cięte Circuity na urządzeniu IBM Quantum za pomocą prymitywu SamplerV2.
  • uzyskaliśmy wartość oczekiwaną poprzez rekonstrukcję wyników ciętych Circuitów.

Wnioski

Z wyników wynika, że średnia obserwabli Z\langle Z \rangle typu wagi 1 i ZZ\langle ZZ \rangle typu wagi 2 jest znacząco ulepszona przez cięcie periodycznych bramek. Zauważ, że w tym badaniu nie uwzględniono żadnych technik tłumienia ani łagodzenia błędów. Obserwowana poprawa wynika wyłącznie z właściwego zastosowania cięcia bramek dla tego problemu. Wyniki mogłyby być jeszcze lepsze przy użyciu technik łagodzenia i tłumienia błędów.

Badanie to pokazuje przykład efektywnego użycia cięcia bramek w celu poprawy wydajności obliczeń.

Ankieta dotycząca samouczka

Wypełnij tę krótką ankietę, aby podzielić się opinią na temat tego samouczka. Twoje uwagi pomogą nam ulepszyć nasze materiały i doświadczenia użytkownika.

Link do ankiety

Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.