Jądra kwantowe z bramkami ułamkowymi
Szacowane zużycie: poniżej 30 sekund na procesorze Heron r2 (UWAGA: To jest jedynie szacunek. Rzeczywisty czas wykonania może się różnić.)
Wyniki nauki
-
Czym są bramki ułamkowe i jak zmniejszają głębokość i czas trwania obwodu na QPU IBM®
-
Ograniczenia związane z użyciem bramek ułamkowych (w szczególności zakres kąta RZZ)
-
Jak zbudować przepływ pracy jądra kwantowego wykorzystujący bramki ułamkowe z IBM Quantum Compute Service
-
Jak porównać metryki wykonania na sprzęcie (głębokość, czas trwania, liczbę bramek nielokalnych, wierność) z bramkami ułamkowymi i bez nich
-
Jak używać tylko ułamkowych bramek RX, zachowując standardowy przepływ pracy wzorców Qiskit
Wymagania wstępne
-
Przepływ pracy wzorców Qiskit
-
Przewodnik Bramki ułamkowe
-
Samouczek Trenowanie kwantowego jądra oraz lekcja Kwantowe jądra z kursu Kwantowe uczenie maszynowe
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 możesz wydajniej implementować pewne unitarne operacje na sprzęcie.
IBM Quantum® Heron QPU obsługuje następujące bramki ułamkowe:
- dla
- dla dowolnej rzeczywistej wartości
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 i , 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:
- jest ograniczony do kątów w zakresie .
- Używanie bramek ułamkowych nie jest obsługiwane w przypadku obwodów dynamicznych, twirlingu Pauliego, probabilistycznego anulowania błędów (PEC) oraz ekstrapolacji zerowego szumu (ZNE) (przy użyciu probabilistycznego wzmocnienia błędów (PEA)).
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.
Podejścia do przepływu pracy dla ograniczenia kąta RZZ
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 . Istnieją dwa podejścia, aby zapewnić spełnienie tego warunku, co omawiamy poniżej. Rekomendujemy drugie podejście i w tym samouczku demonstrujemy je na przykładzie inspirowanym metodą jądra kwantowego. Aby lepiej zrozumieć, gdzie jądra kwantowe mogą być przydatne, zalecamy przeczytanie 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.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer 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, użyj drugiego podejścia opisanego 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ż obejmuje przypisywanie wartości parametrów do obwodów kwantowych i lokalne przechowywanie obwodów z powiązanymi parametrami.
Zwróć uwagę na znany problem w qiskit-ibm-runtime w wersji v0.47.0, w którym bramki RZZ z nieprawidłowymi kątami mogą pozostać w obwodach nawet po transpilacji w niektórych scenariuszach.
Zobacz qiskit-ibm-runtime#2441, aby śledzić postępy w rozwiązywaniu tego problemu. Zalecamy poniższe obejście, dopóki problem nie zostanie rozwiązany.
pm = generate_preset_pass_manager(backend=backend, ...)
pm.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend.target),
RemoveIdentityEquivalent(target=backend.target),
]
)
... = pm.run(...)
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.41 lub nowszy (
pip install qiskit-ibm-runtime) - Qiskit Aer v0.17 lub nowszy (
pip install qiskit-aer) - 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 UGate, n_local, unitary_overlap
from qiskit.transpiler import Target, PassManager
from qiskit.transpiler.passes import (
Optimize1qGatesDecomposition,
RemoveIdentityEquivalent,
)
from qiskit_aer.primitives import SamplerV2 as AerSampler
from qiskit_basis_constructor import DEFAULT_EQUIVALENCE_LIBRARY
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
from qiskit_ibm_runtime.transpiler.passes import FoldRzzAngle
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_marrakesh
No fractional gates: ['cz', 'id', 'rz', 'sx', 'x']
With fractional gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x']
Przykład symulatora na małą skalę
W tej sekcji przechodzimy przez cztery kroki przepływu pracy wzorców Qiskit na symulatorze, wykorzystując obwód jądra kwantowego jako praktyczny przykład.
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: gdzie 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.4.1 (patrz zgłoszenie).
Następnie obliczamy funkcję jądra dla identycznych danych wejściowych — na przykład . 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
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, 12, 2))
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)

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)

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
)
pm_f.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend_f.target),
RemoveIdentityEquivalent(target=backend_f.target),
]
)
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})

Aby ocenić wpływ bramek ułamkowych, sprawdzamy liczbę bramek nielokalnych (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 = AerSampler.from_backend(backend_f)
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())
085ce928-767e-4200-93bf-3905e5411cfe
Krok 4: Przetwarzanie końcowe i zwracanie wyników w pożądanym formacie klasycznym
Wartość funkcji jądra można uzyskać, mierząc prawdopodobieństwo ciągu bitów złożonego z samych zer 00...00 w wynikach.
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)
[0.929, 0.882, 0.8645, 0.817]
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})

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 = AerSampler.from_backend(backend_c)
job = sampler_c.run(pubs=zip(t_qc_c, params), shots=shots)
print(job.job_id())
f2cca29d-7263-4976-9e51-13a91b75c3ae
# step 4: post-processing
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)
[0.8625, 0.7605, 0.702, 0.671]
Porównanie głębokości, czasów trwania i wierności
W tej sekcji porównujemy liczbę bramek nielokalnych 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 0x116af3cb0>
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 0x11ea4f4d0>
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 0x1247fc440>
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 0x120b792b0>
Przykład sprzętowy na dużą skalę
W tej sekcji przeprowadzamy benchmark obiegu pracy kwantowego jądra z ułamkowymi bramkami i bez nich na sprzęcie kwantowym o liczbie kubitów sięgającej 40.
Kroki 1-4 połączone
Obieg pracy ma taką samą strukturę jak przykład na małą skalę. Transponujemy wszystkie obwody z ułamkowymi bramkami i bez nich, zbieramy metryki, a następnie przesyłamy obwody do rzeczywistego sprzętu kwantowego.
# -------------------------Step 1-------------------------
qubits = list(range(4, 44, 4))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]
b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
def benchmark(b_qc, backend):
# -------------------------Step 2-------------------------
pm = generate_preset_pass_manager(optimization_level, backend=backend)
if "rzz" in backend.target.operation_names:
# workaround until https://github.com/Qiskit/qiskit-ibm-runtime/issues/2441 is resolved
pm.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend.target),
RemoveIdentityEquivalent(target=backend.target),
]
)
t_qc = pm.run(b_qc)
nnl = [qc.num_nonlocal_gates() for qc in t_qc]
depth = [qc.depth() for qc in t_qc]
duration = [
qc.estimate_duration(backend_f.target, unit="u") for qc in t_qc
]
# -------------------------Step 3-------------------------
sampler = SamplerV2(mode=backend)
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.dynamical_decoupling.skip_reset_qubits = True
sampler.options.environment.job_tags = ["TUT_FG"]
job = sampler.run(t_qc, shots=shots)
job_id = job.job_id()
return nnl, depth, duration, job_id
def postprocessing(job_id: str):
# -------------------------Step 4-------------------------
job = service.job(job_id)
result = job.result()
fidelities = [fidelity(result=res) for res in result]
usage = job.usage()
return fidelities, usage
backend_f = service.backend(backend_name, use_fractional_gates=True)
nnl_f, depth_f, duration_f, job_id_f = benchmark(
b_qc, backend_f
) # step 2 & 3
print("job id (w/ fractional gates):", job_id_f)
fidelity_f, usage_f = postprocessing(job_id_f) # step 4
job id (w/ fractional gates): d8uasitbh0os73eqnpig
backend_c = service.backend(backend_name, use_fractional_gates=False)
nnl_c, depth_c, duration_c, job_id_c = benchmark(b_qc, backend_c)
print("job id (w/o fractional gates):", job_id_c)
fidelity_c, usage_c = postprocessing(job_id_c)
job id (w/o fractional gates): d8uav3lposuc738pruug
Następnie porównujemy metryki.
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 0x12461e660>
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 0x11f2ac980>
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 0x125c91be0>
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 0x11fcf6e40>
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: 8 seconds
fractional gates: 8 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.
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")
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})

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})

Optymalizacja bramek U za pomocą ułamkowych bramek RX
W tej sekcji pokazujemy, jak zoptymalizować bramki U przy użyciu ułamkowych bramek RX, opierając się na tym samym obwodzie wprowadzonym w poprzedniej sekcji. 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})
Kolejne kroki
Jeśli ta praca wydała Ci się interesująca, mogą Cię zainteresować następujące materiały:
- Przewodnik Fractional gates
- Kiedy nie używać bramek ułamkowych
- Dokumentacja API przejścia transpilera
FoldRzzAngle - Samouczek Quantum kernel training
- Lekcja Quantum kernels w kursie Quantum machine learning