Sari la conținutul principal

Introducere în porțile fracționale

Estimare de utilizare: sub 30 de secunde pe un procesor Heron r2 (NOTĂ: Aceasta este doar o estimare. Timpul de execuție poate varia.)

Fundal

Porți fracționale pe QPU-urile IBM

Porțile fracționale sunt porți cuantice parametrizate ce permit executarea directă a rotațiilor cu unghi arbitrar (în anumite limite), eliminând nevoia de a le descompune în mai multe porți de bază. Prin valorificarea interacțiunilor native dintre qubiții fizici, utilizatorii pot implementa anumite unitare mai eficient pe hardware.

QPU-urile IBM Quantum® Heron suportă următoarele porți fracționale:

  • RZZ(θ)R_{ZZ}(\theta) pentru 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) pentru orice valoare reală θ\theta

Aceste porți pot reduce semnificativ atât adâncimea, cât și durata circuitelor cuantice. Sunt deosebit de avantajoase în aplicațiile care se bazează mult pe RZZR_{ZZ} și RXR_X, cum ar fi simularea Hamiltonianului, Algoritmul de Optimizare Aproximativă Cuantică (QAOA) și metodele de kernel cuantic. În acest tutorial, ne concentrăm pe kernelul cuantic ca exemplu practic.

Limitări

Porțile fracționale sunt în prezent o funcționalitate experimentală și vin cu câteva restricții:

Porțile fracționale necesită un flux de lucru diferit față de abordarea standard. Acest tutorial explică cum să lucrezi cu porți fracționale printr-o aplicație practică.

Consultă următoarele resurse pentru mai multe detalii despre porțile fracționale.

Prezentare generală

Fluxul de lucru pentru utilizarea porților fracționale urmează în general fluxul Qiskit patterns. Diferența esențială este că toate unghiurile RZZ trebuie să satisfacă constrângerea 0<θπ/20 < \theta \leq \pi/2. Există două abordări pentru a asigura respectarea acestei condiții. Acest tutorial se concentrează pe și recomandă a doua abordare.

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

1. Generează valori de parametri care respectă constrângerea unghiului RZZ

Dacă ești sigur că toate unghiurile RZZ se încadrează în intervalul valid, poți urma fluxul standard Qiskit patterns. În acest caz, transmiți pur și simplu valorile parametrilor ca parte a unui PUB. Fluxul de lucru se desfășoară astfel.

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

Dacă încerci să trimiți un PUB care conține o poartă RZZ cu un unghi în afara intervalului valid, vei întâlni un mesaj de eroare de tipul:

'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].'

Pentru a evita această eroare, ar trebui să iei în considerare a doua abordare descrisă mai jos.

2. Asignează valorile parametrilor circuitelor înainte de transpilare

Pachetul qiskit-ibm-runtime oferă un pas de transpilare specializat numit FoldRzzAngle. Acest pas transformă circuitele cuantice astfel încât toate unghiurile RZZ să respecte constrângerea unghiului RZZ. Dacă furnizezi backend-ul la generate_preset_pass_manager sau transpile, Qiskit aplică automat FoldRzzAngle circuitelor cuantice. Aceasta îți cere să asignezi valorile parametrilor circuitelor cuantice înainte de transpilare. Fluxul de lucru se desfășoară astfel.

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

Reține că acest flux de lucru implică un cost computațional mai mare decât prima abordare, deoarece presupune asignarea valorilor parametrilor circuitelor cuantice și stocarea locală a circuitelor cu parametri legați. În plus, există o problemă cunoscută în Qiskit în care transformarea porților RZZ poate eșua în anumite scenarii. Pentru o soluție alternativă, consultă secțiunea Depanare. Acest tutorial demonstrează cum să folosești porțile fracționale prin a doua abordare, printr-un exemplu inspirat din metoda kernelului cuantic. Pentru a înțelege mai bine unde kernelurile cuantice sunt susceptibile să fie utile, îți recomandăm să citești Liu, Arunachalam & Temme (2021).

Poți parcurge și tutorialul Antrenarea kernelului cuantic și lecția Kerneluri cuantice din cursul de învățare automată cuantică de pe IBM Quantum Learning.

Cerințe

Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:

  • Qiskit SDK v2.0 sau o versiune ulterioară, cu suport pentru vizualizare
  • Qiskit Runtime v0.37 sau o versiune ulterioară (pip install qiskit-ibm-runtime)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

Configurare

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

Activează porțile fracționale și verifică porțile de bază

Pentru a utiliza porțile fracționale, poți obține un backend care le suportă setând opțiunea use_fractional_gates=True. Dacă backend-ul suportă porțile fracționale, vei vedea rzz și rx listate printre porțile sale de bază.

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']

Flux de lucru cu porți fracționale

Pasul 1: Mapează intrările clasice la problema cuantică

Circuit kernel cuantic

În această secțiune, explorăm circuitul kernelului cuantic folosind porți RZZ pentru a introduce fluxul de lucru pentru porțile fracționale.

Începem prin construirea unui circuit cuantic pentru a calcula intrările individuale ale matricei kernel. Aceasta se face combinând circuite ZZ feature map cu o suprapunere unitară. Funcția kernel preia vectori din spațiul mapat prin caracteristici și returnează produsul lor intern ca intrare a matricei kernel: K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, unde Φ(x)|\Phi(x)\rangle reprezintă starea cuantică mapată prin caracteristici.

Construim manual un circuit ZZ feature map folosind porți RZZ. Deși Qiskit oferă un zz_feature_map integrat, acesta nu suportă în prezent porțile RZZ începând cu Qiskit v2.0.2 (vezi problema).

Apoi, calculăm funcția kernel pentru intrări identice — de exemplu, K(x,x)=1K(x, x) = 1. Pe calculatoarele cuantice cu zgomot, această valoare poate fi mai mică de 1 din cauza zgomotului. Un rezultat mai aproape de 1 indică un zgomot mai scăzut în execuție. În acest tutorial, ne referim la această valoare ca fidelitate, definită ca 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

Circuitele kernelului cuantic și valorile corespunzătoare ale parametrilor sunt generate pentru sisteme cu 4 până la 40 de qubiți, iar fidelitățile acestora sunt evaluate ulterior.

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

Circuitul cu patru qubiți este vizualizat mai jos.

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

Output of the previous code cell

În fluxul standard Qiskit patterns, valorile parametrilor sunt transmise de obicei primitivei Sampler sau Estimator ca parte a unui PUB. Totuși, când folosești un backend care suportă porțile fracționale, aceste valori ale parametrilor trebuie asignate explicit circuitului cuantic înainte de transpilare.

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

Pasul 2: Optimizează problema pentru execuția pe hardware quantum

Transpilăm apoi circuitul folosind managerul de treceri, urmând pattern-ul standard Qiskit. Furnizând un backend care suportă gate-uri fracționare la generate_preset_pass_manager, o trecere specializată numită FoldRzzAngle este inclusă automat. Această trecere modifică circuitul pentru a respecta constrângerile de unghi RZZ. Prin urmare, gate-urile RZZ cu valori negative din figura anterioară sunt transformate în valori pozitive, iar câteva gate-uri X suplimentare sunt adăugate.

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

Pentru a evalua impactul gate-urilor fracționare, calculăm numărul de gate-uri nelocale (CZ și RZZ pentru acest backend), împreună cu adâncimile și duratele circuitelor, și comparăm aceste metrici cu cele din fluxul de lucru standard mai târziu.

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
]

Pasul 3: Execută folosind primitivele Qiskit

Rulăm circuitul transpilat cu backend-ul care suportă gate-uri fracționare.

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

Pasul 4: Post-procesează și returnează rezultatul în formatul clasic dorit

Poți obține valoarea funcției kernel K(x,x)K(x, x) măsurând probabilitatea șirului de biți toți-zero 00...00 în ieșire.

# 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]

Comparație între fluxul de lucru și circuitul fără gate-uri fracționare

În această secțiune, prezentăm fluxul de lucru standard Qiskit patterns folosind un backend care nu suportă gate-uri fracționare. Comparând circuitele transpilate, vei observa că versiunea cu gate-uri fracționare (din secțiunea anterioară) este mai compactă decât cea fără gate-uri fracționare.

# 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]

Comparație între adâncimi și fidelități

În această secțiune, comparăm numărul de gate-uri nelocale și fidelitățile între circuitele cu și fără gate-uri fracționare. Aceasta evidențiază beneficiile potențiale ale utilizării gate-urilor fracționare în ceea ce privește eficiența și calitatea execuției.

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

Comparăm timpul de utilizare al QPU-ului cu și fără gate-uri fracționare. Rezultatele din celula următoare arată că timpii de utilizare ai QPU-ului sunt aproape identici.

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

Subiect avansat: Folosind doar porți RX fracționale

Necesitatea unui flux de lucru modificat atunci când se folosesc porți fracționale provine în principal din restricția asupra unghiurilor porții RZZ. Cu toate acestea, dacă folosești doar porțile RX fracționale și excluzi porțile RZZ fracționale, poți continua să urmezi fluxul de lucru standard al tiparelor Qiskit. Această abordare poate oferi în continuare beneficii semnificative, în special în Circuit-uri care implică un număr mare de porți RX și U, reducând numărul total de porți și îmbunătățind potențial performanța. În această secțiune, demonstrăm cum să îți optimizezi Circuit-urile folosind doar porți RX fracționale, omițând porțile RZZ.

Pentru a susține acest lucru, furnizăm o funcție utilitară care îți permite să dezactivezi un anumit basis gate dintr-un obiect Target. Aici, o folosim pentru a dezactiva porțile 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

Folosim un Circuit format din porți U, CZ și RZZ ca exemplu.

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

Output of the previous code cell

Mai întâi transpilăm Circuit-ul pentru un Backend care nu suportă porți fracționale.

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

Apoi, transpilăm același Circuit folosind porți RX fracționale, excluzând porțile RZZ. Aceasta conduce la o ușoară reducere a numărului total de porți, datorită implementării mai eficiente a porților 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

Optimizarea porților U cu porți RX fracționale

În această secțiune, demonstrăm cum să optimizăm porțile U folosind porți RX fracționale, bazându-ne pe același Circuit introdus în secțiunea anterioară.

Va trebui să instalezi pachetul qiskit-basis-constructor package pentru această secțiune. Aceasta este o versiune beta a unui nou plugin de transpilare pentru Qiskit, care ar putea fi integrat în Qiskit în viitor.

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

Transpilăm Circuit-ul folosind doar porți RX fracționale, excluzând porțile RZZ. Introducând o regulă de descompunere personalizată, după cum se arată mai jos, putem reduce numărul de porți pe un singur Qubit necesare pentru a implementa o poartă U.

Această funcționalitate este în prezent în discuție în această problemă GitHub.

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

În continuare, aplicăm Transpiler-ul folosind traducerea constructor-beta furnizată de pachetul qiskit-basis-constructor. Ca rezultat, numărul total de porți este redus față de transpilarea anterioară.

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

Depanare

Problemă: Unghiurile RZZ invalide pot rămâne după transpilare

Începând cu Qiskit v2.0.3, există probleme cunoscute în care porțile RZZ cu unghiuri invalide pot rămâne în Circuit-uri chiar și după transpilare. Problema apare de obicei în următoarele condiții.

Eșec la utilizarea opțiunii target cu generate_preset_pass_manager sau transpiler

Când opțiunea target este folosită cu generate_preset_pass_manager sau transpiler, pasul specializat al Transpiler-ului FoldRzzAngle nu este invocat. Pentru a asigura gestionarea corectă a unghiurilor RZZ pentru porțile fracționale, îți recomandăm să folosești întotdeauna opțiunea backend în schimb. Consultă această problemă pentru mai multe detalii.

Eșec când Circuit-urile conțin anumite porți

Dacă Circuit-ul tău include anumite porți, cum ar fi XXPlusYYGate, Transpiler-ul Qiskit poate genera porți RZZ cu unghiuri invalide. Dacă întâmpini această problemă, consultă această problemă GitHub pentru o soluție de evitare.

Sondaj tutorial

Te rog să completezi acest scurt sondaj pentru a oferi feedback despre acest tutorial. Opiniile tale ne vor ajuta să îmbunătățim conținutul și experiența utilizatorilor.

Link către sondaj

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.