Einführung in fraktionale Gates
Geschätzte Laufzeit: unter 30 Sekunden auf einem Heron-r2-Prozessor (HINWEIS: Dies ist nur eine Schätzung. Deine tatsächliche Laufzeit kann abweichen.)
Hintergrund
Fraktionale Gates auf IBM QPUs
Fraktionale Gates sind parametrisierte Quantengates, die die direkte Ausführung von Rotationen mit beliebigen Winkeln (innerhalb bestimmter Grenzen) ermöglichen und damit die Notwendigkeit entfällt, diese in mehrere Basis-Gates zu zerlegen. Durch Nutzung der nativen Wechselwirkungen zwischen physikalischen Qubits können bestimmte Unitaries effizienter auf der Hardware implementiert werden.
IBM Quantum® Heron QPUs unterstützen folgende fraktionale Gates:
- für
- für jeden reellen Wert
Diese Gates können sowohl die Tiefe als auch die Dauer von Quantencircuits erheblich reduzieren. Sie sind besonders vorteilhaft in Anwendungen, die stark auf und setzen, wie z. B. Hamiltonsimulation, der Quantum Approximate Optimization Algorithm (QAOA) und Quantenkernmethoden. In diesem Tutorial konzentrieren wir uns auf den Quantenkern als praktisches Beispiel.
Einschränkungen
Fraktionale Gates sind derzeit ein experimentelles Feature und unterliegen einigen Einschränkungen:
- ist auf Winkel im Bereich beschränkt.
- Die Verwendung fraktionaler Gates wird nicht unterstützt für dynamische Circuits, Pauli Twirling, probabilistische Fehlerauslöschung (PEC) und Zero-Noise-Extrapolation (ZNE) (mit probabilistischer Fehlerverstärkung (PEA)).
Fraktionale Gates erfordern im Vergleich zum Standardvorgehen einen anderen Workflow. Dieses Tutorial erklärt anhand einer praktischen Anwendung, wie du mit fraktionalen Gates arbeitest.
Weitere Details zu fraktionalen Gates findest du hier:
Überblick
Der Workflow für fraktionale Gates folgt im Allgemeinen dem Qiskit-Patterns-Workflow. Der wesentliche Unterschied besteht darin, dass alle RZZ-Winkel die Bedingung erfüllen müssen. Es gibt zwei Ansätze, um diese Bedingung sicherzustellen. Dieses Tutorial konzentriert sich auf den zweiten Ansatz und empfiehlt ihn.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-basis-constructor qiskit-ibm-runtime
1. Parameterwerte generieren, die die RZZ-Winkelbedingung erfüllen
Wenn du sicher bist, dass alle RZZ-Winkel innerhalb des gültigen Bereichs liegen, kannst du den Standard-Qiskit-Patterns-Workflow verwenden. In diesem Fall übergibst du die Parameterwerte einfach als Teil eines PUB. Der Workflow läuft wie folgt ab:
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)])
Wenn du versuchst, einen PUB mit einem RZZ-Gate zu übermitteln, dessen Winkel außerhalb des gültigen Bereichs liegt, erhältst du eine Fehlermeldung wie:
'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].'
Um diesen Fehler zu vermeiden, solltest du den weiter unten beschriebenen zweiten Ansatz in Betracht ziehen.
2. Parameterwerte vor der Transpilierung dem Circuit zuweisen
Das qiskit-ibm-runtime-Paket stellt einen spezialisierten Transpiler-Pass namens FoldRzzAngle bereit.
Dieser Pass transformiert Quantencircuits so, dass alle RZZ-Winkel die RZZ-Winkelbedingung erfüllen.
Wenn du dem generate_preset_pass_manager oder transpile ein Backend übergibst, wendet Qiskit FoldRzzAngle automatisch auf die Quantencircuits an.
Das erfordert, dass du Parameterwerte den Quantencircuits vor der Transpilierung zuweist.
Der Workflow läuft wie folgt ab:
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)])
Beachte, dass dieser Workflow einen höheren Rechenaufwand als der erste Ansatz verursacht, da Parameterwerte den Quantencircuits zugewiesen werden und die parametrisch gebundenen Circuits lokal gespeichert werden müssen. Außerdem gibt es ein bekanntes Problem in Qiskit, bei dem die Transformation von RZZ-Gates in bestimmten Szenarien fehlschlagen kann. Ein Workaround ist im Abschnitt Fehlerbehebung beschrieben. Dieses Tutorial zeigt, wie fraktionale Gates über den zweiten Ansatz verwendet werden, anhand eines Beispiels, das von der Quantenkernmethode inspiriert ist. Um besser zu verstehen, wo Quantenkerne voraussichtlich nützlich sind, empfehlen wir, Liu, Arunachalam & Temme (2021) zu lesen.
Du kannst auch das Tutorial Quantenkern-Training und die Lektion Quantenkerne im Kurs Quantum Machine Learning auf IBM Quantum Learning durcharbeiten.
Voraussetzungen
Stelle vor dem Start dieses Tutorials sicher, dass folgende Pakete installiert sind:
- Qiskit SDK v2.0 oder höher, mit Visualisierungs-Unterstützung
- Qiskit Runtime v0.37 oder höher (
pip install qiskit-ibm-runtime) - Qiskit Basis Constructor (
pip install qiskit_basis_constructor)
Einrichtung
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
Fraktionale Gates aktivieren und Basis-Gates prüfen
Um fraktionale Gates zu nutzen, kannst du ein Backend, das diese unterstützt, über die Option use_fractional_gates=True abrufen.
Wenn das Backend fraktionale Gates unterstützt, siehst du rzz und rx in der Liste seiner Basis-Gates.
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']
Workflow mit fraktionalen Gates
Schritt 1: Klassische Eingaben auf Quantenproblem abbilden
Quantenkern-Circuit
In diesem Abschnitt untersuchen wir den Quantenkern-Circuit unter Verwendung von RZZ-Gates, um den Workflow für fraktionale Gates vorzustellen.
Wir beginnen mit dem Aufbau eines Quantencircuits zur Berechnung einzelner Einträge der Kernmatrix. Dazu werden ZZ-Feature-Map-Circuits mit einem unitären Overlap kombiniert. Die Kernfunktion nimmt Vektoren im feature-gemappten Raum und gibt deren inneres Produkt als Eintrag der Kernmatrix zurück: wobei den feature-gemappten Quantenzustand darstellt.
Wir konstruieren manuell einen ZZ-Feature-Map-Circuit mit RZZ-Gates.
Obwohl Qiskit eine eingebaute zz_feature_map bereitstellt, unterstützt diese seit Qiskit v2.0.2 keine RZZ-Gates (siehe Issue).
Als Nächstes berechnen wir die Kernfunktion für identische Eingaben – zum Beispiel . Auf verrauschten Quantencomputern kann dieser Wert aufgrund von Rauschen kleiner als 1 sein. Ein Ergebnis näher an 1 deutet auf geringeres Rauschen bei der Ausführung hin. In diesem Tutorial bezeichnen wir diesen Wert als Fidelity, definiert als
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
Quantenkern-Circuits und die zugehörigen Parameterwerte werden für Systeme mit 4 bis 40 Qubits generiert, und anschließend werden deren Fidelities ausgewertet.
qubits = list(range(4, 44, 4))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]
Der Vier-Qubit-Circuit wird unten visualisiert.
circuits[0].draw("mpl", fold=-1)

Im Standard-Qiskit-Patterns-Workflow werden Parameterwerte typischerweise als Teil eines PUB an den Sampler oder Estimator übergeben. Wenn jedoch ein Backend verwendet wird, das fraktionale Gates unterstützt, müssen diese Parameterwerte dem Quantencircuit explizit vor der Transpilierung zugewiesen werden.
b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
b_qc[0].draw("mpl", fold=-1)

Schritt 2: Problem für die Quantenhardware-Ausführung optimieren
Anschließend transpilieren wir den Circuit mit dem Pass Manager gemäß dem Standard-Qiskit-Muster.
Durch Übergabe eines Backends, das fraktionale Gates unterstützt, an generate_preset_pass_manager wird automatisch ein spezialisierter Pass namens FoldRzzAngle eingebunden.
Dieser Pass modifiziert den Circuit, damit er die RZZ-Winkelbedingungen einhält.
Dadurch werden RZZ-Gates mit negativen Werten aus der vorherigen Abbildung in positive Werte umgewandelt und einige zusätzliche X-Gates hinzugefügt.
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)])

Um die Auswirkungen fraktionaler Gates zu bewerten, messen wir die Anzahl der nicht-lokalen Gates (CZ und RZZ für dieses Backend), zusammen mit Circuit-Tiefen und -Dauern, und vergleichen diese Metriken später mit denen eines Standard-Workflows.
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
]
Schritt 3: Mit Qiskit-Primitiven ausführen
Wir führen den transpilierten Circuit mit dem Backend aus, das fraktionale Gates unterstützt.
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
Schritt 4: Nachverarbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format
Den Kernfunktionswert erhältst du, indem du die Wahrscheinlichkeit des Null-Bitstrings 00...00 in der Ausgabe misst.
# 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]
Vergleich von Workflow und Circuit ohne fraktionale Gates
In diesem Abschnitt stellen wir den Standard-Qiskit-Patterns-Workflow mit einem Backend vor, das keine fraktionalen Gates unterstützt. Beim Vergleich der transpilierten Circuits wirst du feststellen, dass die Version mit fraktionalen Gates (aus dem vorherigen Abschnitt) kompakter ist als die Version ohne.
# 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 = 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]
Vergleich von Tiefen und Fidelities
In diesem Abschnitt vergleichen wir die Anzahl der nicht-lokalen Gates und die Fidelities zwischen Circuits mit und ohne fraktionale Gates. Dies verdeutlicht die potenziellen Vorteile der Verwendung fraktionaler Gates in Bezug auf Ausführungseffizienz und -qualität.
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>
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>
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>
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>
Wir vergleichen die QPU-Nutzungszeit mit und ohne fraktionale Gates. Die Ergebnisse in der folgenden Zelle zeigen, dass die QPU-Nutzungszeiten nahezu identisch sind.
print(f"no fractional gates: {usage_c} seconds")
print(f"fractional gates: {usage_f} seconds")
no fractional gates: 7 seconds
fractional gates: 7 seconds
Erweitertes Thema: Ausschließliche Verwendung fraktionaler RX-Gates
Die Notwendigkeit des geänderten Workflows bei der Verwendung fraktionaler Gates ergibt sich hauptsächlich aus der Einschränkung der RZZ-Gate-Winkel. Wenn du jedoch nur die fraktionalen RX-Gates verwendest und die fraktionalen RZZ-Gates ausschließt, kannst du weiterhin dem Standard-Qiskit-Patterns-Workflow folgen. Dieser Ansatz kann dennoch sinnvolle Vorteile bieten, insbesondere in Circuits mit einer großen Anzahl von RX-Gates und U-Gates, indem die Gesamtanzahl der Gates reduziert und die Leistung potenziell verbessert wird. In diesem Abschnitt zeigen wir, wie du deine Circuits nur mit fraktionalen RX-Gates optimierst, ohne RZZ-Gates zu verwenden.
Dazu stellen wir eine Hilfsfunktion bereit, mit der du ein bestimmtes Basis-Gate in einem Target-Objekt deaktivieren kannst. Hier verwenden wir sie, um RZZ-Gates zu deaktivieren.
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
Als Beispiel verwenden wir einen Circuit, der aus U-, CZ- und RZZ-Gates besteht.
qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")
Zuerst transpilieren wir den Circuit für ein Backend, das keine fraktionalen Gates unterstützt.
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)])

Dann transpilieren wir denselben Circuit mit fraktionalen RX-Gates, ohne RZZ-Gates zu verwenden. Dies führt zu einer leichten Reduzierung der Gesamtanzahl an Gates dank der effizienteren Implementierung der RX-Gates.
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)])

U-Gates mit fraktionalen RX-Gates optimieren
In diesem Abschnitt zeigen wir, wie U-Gates mit fraktionalen RX-Gates optimiert werden, aufbauend auf demselben Circuit aus dem vorherigen Abschnitt.
Du musst das qiskit-basis-constructor-Paket für diesen Abschnitt installieren.
Dies ist eine Beta-Version eines neuen Transpilierungs-Plugins für Qiskit, das in Zukunft in Qiskit integriert werden könnte.
# %pip install qiskit-basis-constructor
from qiskit.circuit.library import UGate
from qiskit_basis_constructor import DEFAULT_EQUIVALENCE_LIBRARY
Wir transpilieren den Circuit ausschließlich mit fraktionalen RX-Gates, ohne RZZ-Gates. Durch Einführung einer benutzerdefinierten Zerlegungsregel, wie unten gezeigt, können wir die Anzahl der für die Implementierung eines U-Gates erforderlichen Einzel-Qubit-Gates reduzieren.
Dieses Feature wird derzeit in diesem GitHub-Issue diskutiert.
# 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)
Als Nächstes wenden wir den Transpiler mit der constructor-beta-Übersetzung des qiskit-basis-constructor-Pakets an.
Als Ergebnis wird die Gesamtanzahl der Gates im Vergleich zur vorherigen Transpilierung reduziert.
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)])
Fehlerbehebung
Problem: Ungültige RZZ-Winkel können nach der Transpilierung verbleiben
Ab Qiskit v2.0.3 gibt es bekannte Probleme, bei denen RZZ-Gates mit ungültigen Winkeln auch nach der Transpilierung im Circuit verbleiben können. Das Problem tritt typischerweise unter folgenden Bedingungen auf.
Fehler bei Verwendung der target-Option mit generate_preset_pass_manager oder transpiler
Wenn die target-Option mit generate_preset_pass_manager oder transpiler verwendet wird, wird der spezialisierte Transpiler-Pass FoldRzzAngle nicht aufgerufen.
Um eine korrekte Behandlung von RZZ-Winkeln für fraktionale Gates sicherzustellen, empfehlen wir, stets die backend-Option zu verwenden.
Weitere Details sind in diesem Issue zu finden.
Fehler wenn Circuits bestimmte Gates enthalten
Wenn dein Circuit bestimmte Gates wie XXPlusYYGate enthält, kann der Qiskit-Transpiler RZZ-Gates mit ungültigen Winkeln erzeugen.
Falls du auf dieses Problem stößt, findest du in diesem GitHub-Issue einen Workaround.
Tutorial-Umfrage
Bitte nimm an dieser kurzen Umfrage teil, um Feedback zu diesem Tutorial zu geben. Deine Erkenntnisse helfen uns, unsere Inhalte und die Nutzererfahrung zu verbessern.