Skip to main content
IBM Quantum Platform

Quantenkerne mit fraktionalen Gattern

Geschätzte Nutzungsdauer: unter 30 Sekunden auf einem Heron r2 Prozessor (HINWEIS: Dies ist nur eine Schätzung. Ihre Laufzeit kann variieren.)


Lernziele

  • Was sind „Fractional Gates“ und wie reduzieren sie die Schaltungstiefe und die Ausführungsdauer auf den QPUs v IBM®?
  • Die mit der Verwendung von Fractional-Gates verbundenen Einschränkungen (insbesondere der RZZ-Winkelbereich)
  • So erstellen Sie einen Quantum-Kernel-Workflow, der fraktionale Gatter mit dem „ IBM Quantum “-Compute-Service nutzt
  • Wie man Metriken zur Hardware-Ausführung (Tiefe, Dauer, Anzahl nicht-lokaler Gatter, Genauigkeit) mit und ohne fraktionierte Gatter vergleicht
  • Wie man ausschließlich fraktionierte RX-Gates verwendet und dabei den Standard-Workflow von Qiskit beibehält

Voraussetzungen


Hintergrund

Fractional Gates auf QPUs v IBM

Fraktionale Gatter sind parametrisierte Quantengatter, die die direkte Ausführung von Drehungen mit beliebigen Winkeln (innerhalb bestimmter Grenzen) ermöglichen, wodurch die Notwendigkeit entfällt, diese in mehrere Basisgatter zu zerlegen. Durch die Nutzung der nativen Wechselwirkungen zwischen physikalischen Qubits lassen sich bestimmte unitäre Operationen auf der Hardware effizienter umsetzen.

IBM Quantum® Heron QPUs unterstützen die folgenden fraktionalen Gatter:

  • RZZ(θ)R_{ZZ}(\theta) für 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) für jeden realen Wert θ\theta

Diese Gatter können sowohl die Tiefe als auch die Dauer von Quantenschaltungen erheblich reduzieren. Sie sind besonders vorteilhaft bei Anwendungen, die sich stark auf RZZR_{ZZ} und RXR_X stützen, wie die Hamilton-Simulation, der Quantum Approximate Optimization Algorithm (QAOA) und Quantenkernverfahren. In diesem Tutorium konzentrieren wir uns auf den Quantenkernel als praktisches Beispiel.

Einschränkungen

Fractional Gates sind derzeit eine experimentelle Funktion, die mit einigen Einschränkungen verbunden ist:

Fractional Gates erfordern einen anderen Arbeitsablauf als der Standardansatz. In diesem Tutorium wird anhand einer praktischen Anwendung erklärt, wie man mit gebrochenen Gattern arbeitet.

Im Folgenden finden Sie weitere Einzelheiten zu Fractional Gates.

Workflow-Ansätze für die RZZ-Winkelbeschränkung

Der Arbeitsablauf für die Verwendung von Fractional Gates folgt im Allgemeinen dem Qiskit-Patterns -Workflow. Der wesentliche Unterschied besteht darin, dass alle RZZ-Winkel die Nebenbedingung „ 0<θ≤π/20 < \theta \leq \pi/2 “ erfüllen müssen. Es gibt zwei Ansätze, um sicherzustellen, dass diese Bedingung erfüllt ist, wie wir im Folgenden erläutern werden. Wir empfehlen den zweiten Ansatz und veranschaulichen ihn in diesem Tutorial anhand eines Beispiels, das von der Quantenkernel-Methode inspiriert ist. Um besser zu verstehen, in welchen Bereichen Quantenkerne voraussichtlich nützlich sein könnten, empfehlen wir die Lektüre von Liu, Arunachalam & Temme (2021).

Sie können auch das Tutorial zum Quantum-Kernel-Training und die Lektion zu Quantum-Kerneln im Kurs „Quantum Machine Learning“ auf IBM Quantum® Learning durcharbeiten.

1. Generieren Sie Parameterwerte, die die RZZ-Winkelbeschränkung erfüllen

Wenn Sie sicher sind, dass alle RZZ-Winkel innerhalb des gültigen Bereichs liegen, können Sie den Standardarbeitsablauf für Qiskit-Muster befolgen. In diesem Fall übermitteln Sie die Parameterwerte einfach als Teil einer PUB. Der Arbeitsablauf läuft folgendermaßen 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 Sie versuchen, ein PUB zu übermitteln, das ein RZZ-Tor mit einem Winkel außerhalb des gültigen Bereichs enthält, erhalten Sie eine Fehlermeldung wie diese:

'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, wenden Sie die zweite, im Folgenden beschriebene Vorgehensweise an.

2. Weisen Sie den Schaltkreisen vor der Transpilation Parameterwerte zu

Das qiskit-ibm-runtime Paket bietet einen speziellen Transpiler-Durchlauf namens FoldRzzAngle. Dieser Durchlauf transformiert Quantenschaltungen so, dass alle RZZ-Winkel die RZZ-Winkelbeschränkung erfüllen. transpileWenn Sie das Backend für generate_preset_pass_manager oder bereitstellen, wendet FoldRzzAngle Qiskit automatisch auf die Quantenschaltungen an. Bei diesem Ansatz müssen Sie den Quantenschaltungen vor der Transpilierung Parameterwerte zuweisen. Der Arbeitsablauf verläuft wie folgt.

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

Beachten Sie, dass dieser Arbeitsablauf einen höheren Rechenaufwand verursacht als der erste Ansatz, da dabei den Quantenschaltungen Parameterwerte zugewiesen und die mit Parametern versehenen Schaltungen lokal gespeichert werden müssen.

Caution

Beachten Sie ein bekanntes Problem unter qiskit-ibm-runtimev0.47.0, bei dem in bestimmten Szenarien RZZ-Gates mit ungültigen Winkeln auch nach der Transpilierung in den Schaltkreisen verbleiben können.

Unter qiskit-ibm-runtime#2441 können Sie den Fortschritt in dieser Angelegenheit verfolgen. Bis zur Behebung des Problems empfehlen wir folgende Abhilfe.

pm = generate_preset_pass_manager(backend=backend, ...)
pm.post_optimization = PassManager(
    [
        FoldRzzAngle(),
        Optimize1qGatesDecomposition(target=backend.target),
        RemoveIdentityEquivalent(target=backend.target),
    ]
)
... = pm.run(...)

Anforderungen

Bevor Sie mit diesem Tutorial beginnen, sollten Sie sicherstellen, dass Sie Folgendes installiert haben:

  • Qiskit SDK v2.0 oder später, mit Visual isierungsunterstützung
  • Qiskit Runtime v0.41 oder später (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 oder höher (pip install qiskit-aer)
  • 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 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

Fraktionale Gatter aktivieren und Basisgatter überprüfen

Um gebrochene Gatter zu verwenden, können Sie ein Backend erhalten, das diese unterstützt, indem Sie die Option use_fractional_gates=True setzen. Wenn das Backend gebrochene Gatter unterstützt, werden Sie rzz und rx unter den Basisgattern finden.

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

Output:

Backend: ibm_marrakesh
No fractional gates: ['cz', 'id', 'rz', 'sx', 'x']
With fractional gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x']

Beispiel für einen Simulator im kleinen Maßstab

In diesem Abschnitt gehen wir die vier Schritte des Qiskit-Patterns -Workflows auf einem Simulator durch und verwenden dabei die Quantenkernel-Schaltung als praktisches Beispiel.

Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden

Quanten-Kernel-Schaltung

In diesem Abschnitt wird die Quantenkernschaltung mit RZZ-Gattern untersucht, um den Arbeitsablauf für fraktionale Gatter vorzustellen.

Wir beginnen mit der Konstruktion eines Quantenschaltkreises zur Berechnung der einzelnen Einträge der Kernelmatrix. Dies geschieht durch die Kombination von ZZ-Merkmalskreisen mit einer einheitlichen Überlappung. Die Kernel-Funktion nimmt Vektoren im Merkmalsraum auf und gibt ihr inneres Produkt als einen Eintrag der Kernel-Matrix zurück: K(x,y)=⟨Φ(x)∣Φ(y)⟩,K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, wobei ∣Φ(x)⟩|\Phi(x)\rangle für den Quantenzustand steht, der durch die Merkmale abgebildet wird.

Wir bauen manuell eine ZZ-Feature-Map-Schaltung unter Verwendung von RZZ-Gattern auf. zz_feature_mapObwohl Qiskit eine integrierte Funktion bereitstellt, werden RZZ-Gatter zum Stand von Qiskit v2.4.1 derzeit noch nicht unterstützt ( siehe Issue ).

Als nächstes berechnen wir die Kernel-Funktion für identische Eingaben - zum Beispiel K(x,x)=1K(x, x) = 1. Auf verrauschten Quantencomputern kann dieser Wert aufgrund von Rauschen kleiner als 1 sein. Ein Ergebnis, das näher bei 1 liegt, deutet auf ein geringeres Rauschen in der Ausführung hin. In diesem Lernprogramm wird dieser Wert als Treue bezeichnet und wie folgt definiert 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

Für Systeme mit 4 bis 40 Qubits werden Quantenkernschaltungen und die entsprechenden Parameterwerte generiert und anschließend deren Treue bewertet.

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

Die Vier-Qubit-Schaltung ist unten abgebildet.

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

Output:

Output of the previous code cell

Im Standardarbeitsablauf von Qiskit Patterns werden die Parameterwerte normalerweise als Teil eines PUB an das Sampler- oder Estimator-Primitiv übergeben. Wenn jedoch ein Backend verwendet wird, das gebrochene Gatter unterstützt, müssen diese Parameterwerte dem Quantenschaltkreis vor der Transpilierung explizit zugewiesen werden.

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

Output:

Output of the previous code cell

Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware

Anschließend wird die Schaltung mit Hilfe des Pass-Managers nach dem üblichen Qiskit-Muster umgesetzt. Durch die Bereitstellung eines Backends, das gebrochene Gates unterstützt, an generate_preset_pass_manager ist automatisch ein spezieller Pass namens FoldRzzAngle enthalten. Mit diesem Durchgang wird die Schaltung so verändert, dass sie die RZZ-Winkelvorgaben erfüllt. Infolgedessen werden die RZZ-Gatter mit negativen Werten in der vorherigen Abbildung in positive Werte umgewandelt, und es werden einige zusätzliche X-Gatter 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
)
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)

Output:

OrderedDict({'rz': 35, 'rzz': 18, 'x': 13, 'rx': 9, 'measure': 4, 'barrier': 2})
Output of the previous code cell

Um die Auswirkungen von Teilgattern zu beurteilen, bewerten wir die Anzahl der nichtlokalen Gatter (CZ und RZZ für dieses Backend), zusammen mit der Tiefe und Dauer der Schaltkreise 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 primitives “ ausführen

Wir führen die transpilierte Schaltung mit dem Backend aus, das fraktionale Gatter unterstützt.

sampler_f = AerSampler.from_backend(backend_f)
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())

Output:

085ce928-767e-4200-93bf-3905e5411cfe

Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format

Sie können den Kernel-Funktionswert K(x,x)K(x, x) erhalten, indem Sie die Wahrscheinlichkeit der Null-Bitfolge 00...00 in der Ausgabe messen.

result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)

Output:

[0.929, 0.882, 0.8645, 0.817]

Vergleich von Arbeitsablauf und Schaltkreis ohne fraktionierte Gatter

In diesem Abschnitt stellen wir den Standard-Workflow für Qiskit-Muster unter Verwendung eines Backends vor, das keine gebrochenen Gatter unterstützt. Wenn Sie die transpilierten Schaltungen vergleichen, werden Sie feststellen, dass die Version mit fraktionierten Gatterelementen (aus dem vorherigen Abschnitt) kompakter ist als die ohne fraktionierte Gatterelemente.

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

Output:

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 = AerSampler.from_backend(backend_c)
job = sampler_c.run(pubs=zip(t_qc_c, params), shots=shots)
print(job.job_id())

Output:

f2cca29d-7263-4976-9e51-13a91b75c3ae
# step 4: post-processing
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)

Output:

[0.8625, 0.7605, 0.702, 0.671]

Vergleich von Tiefe, Dauer und Genauigkeit

In diesem Abschnitt vergleichen wir die Anzahl der nichtlokalen Gatter und die Treue zwischen Schaltungen mit und ohne fraktionierte Gatter. Dies verdeutlicht die potenziellen Vorteile der Verwendung von Teilgattern 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()

Output:

<matplotlib.legend.Legend at 0x116af3cb0>
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()

Output:

<matplotlib.legend.Legend at 0x11ea4f4d0>
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()

Output:

<matplotlib.legend.Legend at 0x1247fc440>
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()

Output:

<matplotlib.legend.Legend at 0x120b792b0>
Output of the previous code cell

Beispiel für groß angelegte Hardware

In diesem Abschnitt führen wir Vergleichstests des Quantum-Kernel-Workflows mit und ohne fraktionierte Gatter auf Quantenhardware mit bis zu 40 Qubits durch.

Schritte 1–4 zusammengefasst

Der Arbeitsablauf folgt derselben Struktur wie das Beispiel im kleinen Maßstab. Wir transpilieren alle Schaltkreise mit und ohne fraktionale Gatter, erfassen Metriken und lassen die Schaltkreise anschließend auf echter Quantenhardware ausführen.

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

Output:

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)

Output:

job id (w/o fractional gates): d8uav3lposuc738pruug

Anschließend vergleichen wir die Kennzahlen.

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

Output:

<matplotlib.legend.Legend at 0x12461e660>
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()

Output:

<matplotlib.legend.Legend at 0x11f2ac980>
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()

Output:

<matplotlib.legend.Legend at 0x125c91be0>
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()

Output:

<matplotlib.legend.Legend at 0x11fcf6e40>
Output of the previous code cell

Wir vergleichen die QPU-Nutzungszeit mit und ohne Fractional Gates. Die Ergebnisse in der folgenden Zelle zeigen, dass die QPU-Nutzungszeiten fast identisch sind.

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

Output:

no fractional gates: 8 seconds
fractional gates: 8 seconds

Fortgeschrittenes Thema: Verwendung nur von gebrochenen RX-Gates

Die Notwendigkeit des geänderten Arbeitsablaufs bei der Verwendung von fraktionierten Gattern ergibt sich in erster Linie aus der Beschränkung der RZZ-Gate-Winkel. Wenn Sie jedoch nur die gebrochenen RX-Gatter verwenden und die gebrochenen RZZ-Gatter ausschließen, können Sie weiterhin dem Standardarbeitsablauf für Qiskit-Muster folgen. Dieser Ansatz kann insbesondere bei Schaltungen mit einer großen Anzahl von RX-Gattern und U-Gattern erhebliche Vorteile bieten, da er die Gesamtzahl der Gatter reduziert und die Leistung potenziell verbessert. In diesem Abschnitt zeigen wir Ihnen, wie Sie Ihre Schaltungen optimieren können, indem Sie nur gebrochene RX-Gatter verwenden und auf RZZ-Gatter verzichten.

Um dies zu unterstützen, stellen wir eine Utility-Funktion zur Verfügung, mit der Sie ein bestimmtes Basis-Gate in einem Target-Objekt deaktivieren können. Hier verwenden wir sie, um RZZ-Tore zu deaktivieren.

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

Wir verwenden eine Schaltung, die aus U-, CZ- und RZZ-Gattern besteht, als Beispiel.

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

Output:

Output of the previous code cell

Zunächst transpilieren wir die Schaltung für ein Backend, das keine fraktionalen Gatter 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")

Output:

OrderedDict({'rz': 23, 'sx': 16, 'cz': 4})
Output of the previous code cell

Dann transpilieren wir dieselbe Schaltung mit gebrochenen RX-Gattern, wobei RZZ-Gatter ausgeschlossen werden. Dies führt zu einer leichten Verringerung der Gesamtzahl der Gatter dank der effizienteren Implementierung der RX-Gatter.

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

Output:

OrderedDict({'rz': 22, 'sx': 14, 'cz': 4, 'rx': 1})
Output of the previous code cell

Optimieren Sie U-Gatter mit gebrochenen RX-Gattern

In diesem Abschnitt zeigen wir, wie man U-Gatter mit fraktionalen RX-Gattern optimieren kann, wobei wir auf derselben Schaltung aufbauen, die im vorherigen Abschnitt vorgestellt wurde.

Wir setzen die Schaltung nur mit gebrochenen RX-Gattern um, ohne RZZ-Gatter. Durch die Einführung einer benutzerdefinierten Zerlegungsregel, wie sie im Folgenden gezeigt wird, können wir die Anzahl der Ein-Qubit-Gatter reduzieren, die zur Implementierung eines U-Gatters erforderlich sind.

Diese Funktion wird derzeit in diesem GitHub -Ticket 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 unter Verwendung constructor-beta der vom Paket qiskit-basis-constructor bereitgestellten Übersetzung an. Dadurch verringert sich die Gesamtzahl der Gatter im Vergleich zur vorherigen Transpilierung.

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

Output:

OrderedDict({'rz': 16, 'rx': 9, 'cz': 4})
Output of the previous code cell

Nächste Schritte

Empfehlungen

Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:

War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.