Przejdź do głównej treści

Wprowadzenie do bramek ułamkowych

Szacowane zużycie: poniżej 30 sekund na procesorze Heron r2 (UWAGA: To jest jedynie szacunek. Rzeczywisty czas wykonania może się różnić.)

Tło

Bramki ułamkowe w QPU IBM

Bramki ułamkowe to sparametryzowane bramki kwantowe umożliwiające bezpośrednie wykonywanie obrotów o dowolnym kącie (w określonych granicach), eliminując potrzebę rozkładania ich na wiele bramek bazowych. Dzięki wykorzystaniu natywnych interakcji między fizycznymi Qubitami użytkownicy mogą wydajniej implementować pewne unitarne operacje na sprzęcie.

IBM Quantum® Heron QPU obsługuje następujące bramki ułamkowe:

  • RZZ(θ)R_{ZZ}(\theta) dla 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) dla dowolnej rzeczywistej wartości θ\theta

Bramki te mogą znacznie zmniejszyć zarówno głębokość, jak i czas trwania obwodów kwantowych. Są szczególnie korzystne w zastosowaniach, które w dużym stopniu opierają się na RZZR_{ZZ} i RXR_X, takich jak symulacja Hamiltonianu, kwantowy algorytm przybliżonej optymalizacji (QAOA) oraz kwantowe metody jądrowe. W tym samouczku skupiamy się na kwantowym jądrze jako praktycznym przykładzie.

Ograniczenia

Bramki ułamkowe są obecnie funkcją eksperymentalną i wiążą się z kilkoma ograniczeniami:

Bramki ułamkowe wymagają innego przepływu pracy w porównaniu do standardowego podejścia. Ten samouczek wyjaśnia, jak pracować z bramkami ułamkowymi poprzez praktyczne zastosowanie.

Więcej szczegółów na temat bramek ułamkowych znajdziesz poniżej.

Przegląd

Przepływ pracy z bramkami ułamkowymi generalnie odpowiada przepływowi pracy wzorców Qiskit. Kluczowa różnica polega na tym, że wszystkie kąty RZZ muszą spełniać warunek 0<θπ/20 < \theta \leq \pi/2. Istnieją dwa podejścia, aby zapewnić spełnienie tego warunku. Ten samouczek skupia się na drugim podejściu i je rekomenduje.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-basis-constructor qiskit-ibm-runtime

1. Generowanie wartości parametrów spełniających ograniczenie kąta RZZ

Jeśli masz pewność, że wszystkie kąty RZZ mieszczą się w prawidłowym zakresie, możesz postępować zgodnie ze standardowym przepływem pracy wzorców Qiskit. W tym przypadku po prostu przekazujesz wartości parametrów jako część PUB. Przepływ pracy przebiega następująco.

pm = generate_preset_pass_manager(backend=backend, ...)
t_circuit = pm.run(circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit, parameter_values)])
estimator.run([(t_circuit, t_observable, parameter_values)])

Jeśli spróbujesz przesłać PUB zawierający bramkę RZZ z kątem poza prawidłowym zakresem, napotkasz komunikat o błędzie podobny do:

'The instruction rzz is supported only for angles in the range [0, pi/2], but an angle (20.0) outside of this range has been requested; via parameter value(s) γ[0]=10.0, substituted in parameter expression 2.0*γ[0].'

Aby uniknąć tego błędu, rozważ drugie podejście opisane poniżej.

2. Przypisywanie wartości parametrów do obwodów przed transpilacją

Pakiet qiskit-ibm-runtime dostarcza specjalistyczną przepustkę transpilatora o nazwie FoldRzzAngle. Ta przepustka przekształca obwody kwantowe tak, aby wszystkie kąty RZZ były zgodne z ograniczeniem kąta RZZ. Jeśli podasz Backend do generate_preset_pass_manager lub transpile, Qiskit automatycznie stosuje FoldRzzAngle do obwodów kwantowych. Wymaga to przypisania wartości parametrów do obwodów kwantowych przed transpilacją. Przepływ pracy przebiega następująco.

pm = generate_preset_pass_manager(backend=backend, ...)
b_circuit = circuit.assign_parameters(parameter_values)
t_circuit = pm.run(b_circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit,)])
estimator.run([(t_circuit, t_observable)])

Zauważ, że ten przepływ pracy wiąże się z wyższym kosztem obliczeniowym niż pierwsze podejście, ponieważ wymaga przypisania wartości parametrów do obwodów kwantowych i lokalnego przechowywania obwodów z powiązanymi parametrami. Ponadto istnieje znany problem w Qiskit, gdzie transformacja bramek RZZ może nie powieść się w pewnych scenariuszach. Aby zapoznać się z obejściem problemu, zapoznaj się z sekcją Rozwiązywanie problemów. Ten samouczek demonstruje, jak używać bramek ułamkowych za pomocą drugiego podejścia, poprzez przykład inspirowany kwantową metodą jądrową. Aby lepiej zrozumieć, gdzie kwantowe jądra prawdopodobnie będą użyteczne, zalecamy lekturę Liu, Arunachalam & Temme (2021).

Możesz również przejść przez samouczek Trenowanie kwantowego jądra oraz lekcję Kwantowe metody jądrowe w kursie Kwantowe uczenie maszynowe na IBM Quantum Learning.

Wymagania

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

  • Qiskit SDK v2.0 lub nowszy, z obsługą wizualizacji
  • Qiskit Runtime v0.37 lub nowszy (pip install qiskit-ibm-runtime)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

Konfiguracja

import matplotlib.pyplot as plt
import numpy as np
from qiskit import QuantumCircuit, generate_preset_pass_manager
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import unitary_overlap
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2

Włączanie bramek ułamkowych i sprawdzanie bramek bazowych

Aby używać bramek ułamkowych, możesz uzyskać Backend, który je obsługuje, ustawiając opcję use_fractional_gates=True. Jeśli Backend obsługuje bramki ułamkowe, zobaczysz rzz i rx wymienione wśród jego bramek bazowych.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
) # backend should be a heron device or later
backend_name = backend.name
backend_c = service.backend(backend_name) # w/o fractional gates
backend_f = service.backend(
backend_name, use_fractional_gates=True
) # w/ fractional gates
print(f"Backend: {backend_name}")
print(f"No fractional gates: {backend_c.basis_gates}")
print(f"With fractional gates: {backend_f.basis_gates}")
if "rzz" not in backend_f.basis_gates:
print(f"Backend {backend_name} does not support fractional gates")
Backend: ibm_fez
No fractional gates: ['cz', 'id', 'rz', 'sx', 'x']
With fractional gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x']

Przepływ pracy z bramkami ułamkowymi

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

Circuit kwantowego jądra

W tej sekcji badamy Circuit kwantowego jądra z użyciem bramek RZZ, aby przedstawić przepływ pracy z bramkami ułamkowymi.

Zaczynamy od skonstruowania obwodu kwantowego do obliczania pojedynczych wpisów macierzy jądra. Odbywa się to poprzez połączenie obwodów mapy cech ZZ z unitarnym iloczynem skalarnym. Funkcja jądra przyjmuje wektory w przestrzeni odwzorowanej przez cechy i zwraca ich iloczyn skalarny jako wpis macierzy jądra: K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, gdzie Φ(x)|\Phi(x)\rangle reprezentuje odwzorowany przez cechy stan kwantowy.

Ręcznie konstruujemy Circuit mapy cech ZZ przy użyciu bramek RZZ. Choć Qiskit dostarcza wbudowaną funkcję zz_feature_map, nie obsługuje ona obecnie bramek RZZ w wersji Qiskit v2.0.2 (patrz zgłoszenie).

Następnie obliczamy funkcję jądra dla identycznych danych wejściowych — na przykład K(x,x)=1K(x, x) = 1. Na zaszumionych komputerach kwantowych ta wartość może być mniejsza niż 1 z powodu szumu. Wynik bliższy 1 wskazuje na niższy poziom szumu podczas wykonania. W tym samouczku nazywamy tę wartość wiernością (ang. fidelity), zdefiniowaną jako fidelity=K(x,x).\text{fidelity} = K(x, x).

optimization_level = 2
shots = 2000
reps = 3
rng = np.random.default_rng(seed=123)
def my_zz_feature_map(num_qubits: int, reps: int = 1) -> QuantumCircuit:
x = ParameterVector("x", num_qubits * reps)
qc = QuantumCircuit(num_qubits)
qc.h(range(num_qubits))
for k in range(reps):
K = k * num_qubits
for i in range(num_qubits):
qc.rz(x[i + K], i)
pairs = [(i, i + 1) for i in range(num_qubits - 1)]
for i, j in pairs[0::2] + pairs[1::2]:
qc.rzz((np.pi - x[i + K]) * (np.pi - x[j + K]), i, j)
return qc

def quantum_kernel(num_qubits: int, reps: int = 1) -> QuantumCircuit:
qc = my_zz_feature_map(num_qubits, reps=reps)
inner_product = unitary_overlap(qc, qc, "x", "y", insert_barrier=True)
inner_product.measure_all()
return inner_product

def random_parameters(inner_product: QuantumCircuit) -> np.ndarray:
return np.tile(rng.random(inner_product.num_parameters // 2), 2)

def fidelity(result) -> float:
ba = result.data.meas
return ba.get_int_counts().get(0, 0) / ba.num_shots

Obwody kwantowego jądra oraz odpowiadające im wartości parametrów są generowane dla układów od 4 do 40 Qubitów, a ich wierności są następnie oceniane.

qubits = list(range(4, 44, 4))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]

Cztero-Qubitowy Circuit jest zwizualizowany poniżej.

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

Output of the previous code cell

W standardowym przepływie pracy wzorców Qiskit wartości parametrów są zazwyczaj przekazywane do Sampler lub Estimator jako część PUB. Jednak przy używaniu Backendu obsługującego bramki ułamkowe te wartości parametrów muszą być jawnie przypisane do obwodu kwantowego przed transpilacją.

b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
b_qc[0].draw("mpl", fold=-1)

Output of the previous code cell

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

Następnie transpilujemy Circuit przy użyciu menedżera przepustek zgodnie ze standardowym wzorcem Qiskit. Dostarczając Backend obsługujący bramki ułamkowe do generate_preset_pass_manager, specjalistyczna przepustka o nazwie FoldRzzAngle jest automatycznie dołączana. Ta przepustka modyfikuje Circuit tak, aby był zgodny z ograniczeniami kąta RZZ. W rezultacie bramki RZZ z ujemnymi wartościami na poprzednim rysunku są przekształcane na wartości dodatnie, a niektóre dodatkowe bramki X są dodawane.

backend_f = service.backend(name=backend_name, use_fractional_gates=True)
# pm_f includes `FoldRzzAngle` pass
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_f
)
t_qc_f = pm_f.run(b_qc)
print(t_qc_f[0].count_ops())
t_qc_f[0].draw("mpl", fold=-1)
OrderedDict([('rz', 35), ('rzz', 18), ('x', 13), ('rx', 9), ('measure', 4), ('barrier', 2)])

Output of the previous code cell

Aby ocenić wpływ bramek ułamkowych, sprawdzamy liczbę bramek nielokanych (CZ i RZZ dla tego Backendu), wraz z głębokościami i czasami trwania obwodów, a następnie porównujemy te metryki z wynikami standardowego przepływu pracy.

nnl_f = [qc.num_nonlocal_gates() for qc in t_qc_f]
depth_f = [qc.depth() for qc in t_qc_f]
duration_f = [
qc.estimate_duration(backend_f.target, unit="u") for qc in t_qc_f
]

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

Uruchamiamy transpilowany Circuit z Backendem obsługującym bramki ułamkowe.

sampler_f = SamplerV2(mode=backend_f)
sampler_f.options.dynamical_decoupling.enable = True
sampler_f.options.dynamical_decoupling.sequence_type = "XY4"
sampler_f.options.dynamical_decoupling.skip_reset_qubits = True
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())
d4bninsi51bc738j97eg

Krok 4: Przetwarzanie końcowe i zwracanie wyników w pożądanym formacie klasycznym

Wartość funkcji jądra K(x,x)K(x, x) można uzyskać, mierząc prawdopodobieństwo ciągu bitów złożonego z samych zer 00...00 w wynikach.

# job = service.job("d1obougt0npc73flhiag")
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)
usage_f = job.usage()
[0.9005, 0.647, 0.3345, 0.355, 0.3315, 0.174, 0.1875, 0.149, 0.1175, 0.085]

Porównanie przepływu pracy i obwodu bez bramek ułamkowych

W tej sekcji przedstawiamy standardowy przepływ pracy Qiskit patterns z użyciem Backend, który nie obsługuje bramek ułamkowych. Porównując przetranspilowane obwody, zauważysz, że wersja korzystająca z bramek ułamkowych (z poprzedniej sekcji) jest bardziej zwarta niż ta bez bramek ułamkowych.

# step 1: map classical inputs to quantum problem
# `circuits` and `params` from the previous section are reused here
# step 2: optimize circuits
backend_c = service.backend(backend_name) # w/o fractional gates
pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc_c = pm_c.run(circuits)
print(t_qc_c[0].count_ops())
t_qc_c[0].draw("mpl", fold=-1)
OrderedDict([('rz', 130), ('sx', 80), ('cz', 36), ('measure', 4), ('barrier', 2)])

Output of the previous code cell

nnl_c = [qc.num_nonlocal_gates() for qc in t_qc_c]
depth_c = [qc.depth() for qc in t_qc_c]
duration_c = [
qc.estimate_duration(backend_c.target, unit="u") for qc in t_qc_c
]
# step 3: execute
sampler_c = SamplerV2(backend_c)
sampler_c.options.dynamical_decoupling.enable = True
sampler_c.options.dynamical_decoupling.sequence_type = "XY4"
sampler_c.options.dynamical_decoupling.skip_reset_qubits = True
job = sampler_c.run(pubs=zip(t_qc_c, params), shots=shots)
print(job.job_id())
d4bnirvnmdfs73ae3a2g
# step 4: post-processing
# job = service.job("d1obp8j3rr0s73bg4810")
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)
usage_c = job.usage()
[0.6675, 0.5725, 0.098, 0.102, 0.065, 0.0235, 0.006, 0.0015, 0.0015, 0.002]

Porównanie głębokości i wierności

W tej sekcji porównujemy liczbę bramek nielokalne oraz wierności między obwodami z bramkami ułamkowymi i bez nich. Podkreśla to potencjalne korzyści ze stosowania bramek ułamkowych pod względem wydajności wykonania i jakości.

plt.plot(qubits, depth_c, "-o", label="no fractional gates")
plt.plot(qubits, depth_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("depth")
plt.title("Comparison of depths")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bcaac50>

Output of the previous code cell

plt.plot(qubits, duration_c, "-o", label="no fractional gates")
plt.plot(qubits, duration_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("duration (µs)")
plt.title("Comparison of durations")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bdef310>

Output of the previous code cell

plt.plot(qubits, nnl_c, "-o", label="no fractional gates")
plt.plot(qubits, nnl_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("number of non-local gates")
plt.title("Comparison of numbers of non-local gates")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12be8ac90>

Output of the previous code cell

plt.plot(qubits, fidelity_c, "-o", label="no fractional gates")
plt.plot(qubits, fidelity_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("fidelity")
plt.title("Comparison of fidelities")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bea8290>

Output of the previous code cell

Porównujemy czas użycia QPU z bramkami ułamkowymi i bez nich. Wyniki w poniższej komórce pokazują, że czasy użycia QPU są niemal identyczne.

print(f"no fractional gates: {usage_c} seconds")
print(f"fractional gates: {usage_f} seconds")
no fractional gates: 7 seconds
fractional gates: 7 seconds

Temat zaawansowany: używanie tylko ułamkowych bramek RX

Konieczność stosowania zmodyfikowanego przepływu pracy przy korzystaniu z bramek ułamkowych wynika przede wszystkim z ograniczenia kątów bramki RZZ. Jednak jeśli używasz wyłącznie ułamkowych bramek RX i wykluczasz ułamkowe bramki RZZ, możesz nadal stosować standardowy przepływ pracy Qiskit patterns. Takie podejście może przynieść wymierne korzyści, szczególnie w obwodach zawierających dużą liczbę bramek RX i U, zmniejszając łączną liczbę bramek i potencjalnie poprawiając wydajność. W tej sekcji pokazujemy, jak zoptymalizować obwody przy użyciu wyłącznie ułamkowych bramek RX, pomijając bramki RZZ.

Aby to umożliwić, udostępniamy funkcję pomocniczą, która pozwala wyłączyć określoną bramkę bazową w obiekcie Target. Tutaj używamy jej do wyłączenia bramek RZZ.

from qiskit.circuit.library import n_local
from qiskit.transpiler import Target
def remove_instruction_from_target(target: Target, gate_name: str) -> Target:
new_target = Target(
description=target.description,
num_qubits=target.num_qubits,
dt=target.dt,
granularity=target.granularity,
min_length=target.min_length,
pulse_alignment=target.pulse_alignment,
acquire_alignment=target.acquire_alignment,
qubit_properties=target.qubit_properties,
concurrent_measurements=target.concurrent_measurements,
)

for name, qarg_map in target.items():
if name == gate_name:
continue
instruction = target.operation_from_name(name)
if qarg_map == {None: None}:
qarg_map = None
new_target.add_instruction(instruction, qarg_map, name=name)
return new_target

Jako przykład używamy obwodu złożonego z bramek U, CZ i RZZ.

qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")

Output of the previous code cell

Najpierw transpilujemy obwód dla Backend, który nie obsługuje bramek ułamkowych.

pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc = pm_c.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 23), ('sx', 16), ('cz', 4)])

Output of the previous code cell

Następnie transpilujemy ten sam obwód przy użyciu ułamkowych bramek RX, wykluczając bramki RZZ. Skutkuje to niewielkim zmniejszeniem łącznej liczby bramek, dzięki wydajniejszej implementacji bramek RX.

backend_f = service.backend(backend_name, use_fractional_gates=True)
target = remove_instruction_from_target(backend_f.target, "rzz")
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 22), ('sx', 14), ('cz', 4), ('rx', 1)])

Output of the previous code cell

Optymalizacja bramek U za pomocą ułamkowych bramek RX

W tej sekcji pokazujemy, jak optymalizować bramki U przy użyciu ułamkowych bramek RX, opierając się na tym samym obwodzie wprowadzonym w poprzedniej sekcji.

Będziesz potrzebować pakietu qiskit-basis-constructor (package) do tej sekcji. Jest to wersja beta nowej wtyczki transpilacji dla Qiskit, która może zostać zintegrowana z Qiskit w przyszłości.

# %pip install qiskit-basis-constructor
from qiskit.circuit.library import UGate
from qiskit_basis_constructor import DEFAULT_EQUIVALENCE_LIBRARY

Transponujemy obwód używając wyłącznie ułamkowych bramek RX, wykluczając bramki RZZ. Wprowadzając niestandardową regułę dekompozycji, jak pokazano poniżej, możemy zmniejszyć liczbę jednokubitowych bramek potrzebnych do zaimplementowania bramki U.

Ta funkcja jest obecnie dyskutowana w tym issue na GitHubie.

# special decomposition rule for UGate
x = ParameterVector("x", 3)
zxz = QuantumCircuit(1)
zxz.rz(x[2] - np.pi / 2, 0)
zxz.rx(x[0], 0)
zxz.rz(x[1] + np.pi / 2, 0)
DEFAULT_EQUIVALENCE_LIBRARY.add_equivalence(UGate(x[0], x[1], x[2]), zxz)

Następnie stosujemy Transpiler z tłumaczeniem constructor-beta udostępnionym przez pakiet qiskit-basis-constructor. W efekcie łączna liczba bramek jest mniejsza w porównaniu z poprzednią transpilacją.

pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
translation_method="constructor-beta",
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 16), ('rx', 9), ('cz', 4)])

Output of the previous code cell

Rozwiązywanie problemów

Problem: nieprawidłowe kąty RZZ mogą pozostać po transpilacji

Począwszy od Qiskit v2.0.3, znane są problemy, w których bramki RZZ z nieprawidłowymi kątami mogą pozostać w obwodach nawet po transpilacji. Problem zazwyczaj pojawia się w następujących przypadkach.

Błąd przy użyciu opcji target z generate_preset_pass_manager lub transpiler

Gdy opcja target jest używana z generate_preset_pass_manager lub transpiler, wyspecjalizowany pass Transpilera FoldRzzAngle nie jest wywoływany. Aby zapewnić prawidłową obsługę kątów RZZ dla bramek ułamkowych, zalecamy zawsze używać opcji backend. Szczegóły znajdziesz w tym issue.

Błąd gdy obwody zawierają pewne bramki

Jeśli twój obwód zawiera pewne bramki, takie jak XXPlusYYGate, Transpiler Qiskit może wygenerować bramki RZZ z nieprawidłowymi kątami. Jeśli napotkasz ten problem, zapoznaj się z tym issue na GitHubie, gdzie znajdziesz obejście.

Ankieta dotycząca samouczka

Wypełnij tę krótką ankietę, aby przekazać nam opinię na temat tego samouczka. Twoje uwagi pomogą nam udoskonalić nasze materiały i doświadczenie 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.