Skip to main content
IBM Quantum Platform

Quantumphasenschätzung mit dem Q-CTRL- Qiskit Functions

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


Hintergrund

Die Quantenphasenschätzung (Quantum Phase Estimation, QPE) ist ein grundlegender Algorithmus in der Quanteninformatik, der die Basis für viele wichtige Anwendungen wie den Shor-Algorithmus, die Abschätzung der Grundzustandsenergie in der Quantenchemie und Eigenwertprobleme bildet. QPE schätzt die Phase φ\varphi, die mit einem Eigenzustand eines unitären Operators verbunden ist, kodiert in der Beziehung

U∣φ⟩=e2πiφ∣φ⟩,U \lvert \varphi \rangle = e^{2\pi i \varphi} \lvert \varphi \rangle,

und bestimmt es mit einer Genauigkeit von ϵ=O(1/2m)\epsilon = O(1/2^m) unter Verwendung von mm Zählqubits [1]. Indem man diese Qubits in Überlagerung präpariert, kontrollierte Potenzen von UU anwendet und dann die inverse Quanten-Fourier-Transformation (QFT) nutzt, um die Phase in binär kodierte Messergebnisse zu extrahieren, erzeugt QPE eine Wahrscheinlichkeitsverteilung, die bei Bitstrings gipfelt, deren binäre Brüche sich φ\varphi annähern. Im Idealfall entspricht das wahrscheinlichste Messergebnis direkt der binären Erweiterung der Phase, während die Wahrscheinlichkeit anderer Ergebnisse schnell mit der Anzahl der zählenden Qubits abnimmt. Der Betrieb tiefer QPE-Schaltkreise auf Hardware stellt jedoch eine Herausforderung dar: Die große Anzahl von Qubits und Verschränkungsoperationen machen den Algorithmus sehr anfällig für Dekohärenz und Gatterfehler. Dies führt zu verbreiterten und verschobenen Verteilungen von Bitstrings, die die wahre Eigenphase verdecken. Dies hat zur Folge, dass die Bitfolge mit der höchsten Wahrscheinlichkeit nicht mehr der korrekten binären Erweiterung von φ\varphi entspricht.

In diesem Tutorial stellen wir eine Implementierung des QPE-Algorithmus unter Verwendung von Q-CTRLs Fire Opal-Fehlerunterdrückungs- und Leistungsmanagement-Tools vor, die als Qiskit-Funktion angeboten werden (siehe die Fire Opal-Dokumentation ). Fire Opal wendet automatisch fortschrittliche Optimierungen an, einschließlich dynamischer Entkopplung, Verbesserungen des Qubit-Layouts und Fehlerunterdrückungstechniken, was zu realitätsgetreueren Ergebnissen führt. Diese Verbesserungen bringen die Hardware-Bitstring-Verteilungen näher an die in rauschfreien Simulationen erhaltenen Verteilungen heran, so dass Sie auch unter Rauscheffekten zuverlässig die richtige Eigenphase identifizieren können.


Anforderungen

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

  • Qiskit SDK v1.4 oder später, mit Visual isierungsunterstützung
  • Qiskit Runtime v0.40 oder später (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog v0.9.0 (pip install qiskit-ibm-catalog)
  • Fire Opal SDK v9.0.2 oder höher (pip install fire-opal)
  • Q-CTRL Visualizer v8.0.2 oder höher (pip install qctrl-visualizer)

Einrichtung

Melden Sie sich zunächst mit Ihrem API-Schlüssel für „ IBM Quantum “ an. Wählen Sie anschließend die Qiskit-Funktion wie folgt aus. (Dieser Code setzt voraus, dass Sie Ihr Konto bereits in Ihrer lokalen Umgebung gespeichert haben.)

from qiskit import QuantumCircuit

import numpy as np
import matplotlib.pyplot as plt
import qiskit
from qiskit import qasm2
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import qctrlvisualizer as qv
from qiskit_ibm_catalog import QiskitFunctionsCatalog

plt.style.use(qv.get_qctrl_style())
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Access Function
perf_mgmt = catalog.load("q-ctrl/performance-management")

Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen

In diesem Tutorial veranschaulichen wir QPE, um die Eigenphase eines bekannten Ein-Qubit-Unitariums wiederherzustellen. Die Einheit, deren Phase wir schätzen wollen, ist das Ein-Qubit-Phasentor, das auf das Ziel-Qubit angewendet wird:

U(θ)=(100eiθ)=eiθ ∣1⟩ ⁣⟨1∣.U(\theta)= \begin{pmatrix} 1 & 0\\[2pt] 0 & e^{i\theta} \end{pmatrix} = e^{i\theta\,|1\rangle\!\langle 1|}.

Wir bereiten seinen Eigenzustand ∣ψ⟩=∣1⟩|\psi\rangle=|1\rangle vor. Da ∣1⟩|1\rangle ein Eigenvektor von U(θ)U(\theta) mit dem Eigenwert eiθe^{i\theta} ist, lautet die zu schätzende Eigenphase:

φ=θ2π(mod1)\varphi = \frac{\theta}{2\pi} \pmod{1}

Wir stellen θ=16⋅2π\theta=\tfrac{1}{6}\cdot 2\pi ein, so dass die Phase der Grundwahrheit φ=1/6\varphi=1/6 ist. Der QPE-Schaltkreis implementiert die kontrollierten Potenzen U2kU^{2^k} durch Anwendung kontrollierter Phasendrehungen mit Winkeln θ⋅2k\theta\cdot2^k, wendet dann die inverse QFT auf das Zählregister an und misst sie. Die resultierenden Bitstrings konzentrieren sich auf die binäre Darstellung von 1/61/6.

Die Schaltung verwendet mm Zählende Qubits (zur Einstellung der Schätzgenauigkeit) plus ein Ziel-Qubit. Wir beginnen mit der Definition der Bausteine, die für die Implementierung von QPE erforderlich sind: die Quanten-Fourier-Transformation (QFT) und ihre Umkehrung, Hilfsfunktionen zur Umrechnung zwischen Dezimal- und Binärbruchteilen der Eigenphase und Hilfsfunktionen zur Normalisierung von Rohwerten in Wahrscheinlichkeiten für den Vergleich von Simulations- und Hardwareergebnissen.

def inverse_quantum_fourier_transform(quantum_circuit, number_of_qubits):
    """
    Apply an inverse Quantum Fourier Transform the first `number_of_qubits` qubits in the
    `quantum_circuit`.
    """
    for qubit in range(number_of_qubits // 2):
        quantum_circuit.swap(qubit, number_of_qubits - qubit - 1)
    for j in range(number_of_qubits):
        for m in range(j):
            quantum_circuit.cp(-np.pi / float(2 ** (j - m)), m, j)
        quantum_circuit.h(j)
    return quantum_circuit
def bitstring_count_to_probabilities(data, shot_count):
    """
    This function turns an unsorted dictionary of bitstring counts into a sorted dictionary
    of probabilities.
    """
    # Turn the bitstring counts into probabilities.
    probabilities = {
        bitstring: bitstring_count / shot_count
        for bitstring, bitstring_count in data.items()
    }

    sorted_probabilities = dict(
        sorted(probabilities.items(), key=lambda x: x[1], reverse=True)
    )

    return sorted_probabilities

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

Wir bauen die QPE-Schaltung auf, indem wir die zählenden Qubits in Überlagerung vorbereiten, kontrollierte Phasendrehungen anwenden, um die Zieleigenphase zu kodieren, und mit einer inversen QFT vor der Messung abschließen.

def quantum_phase_estimation_benchmark_circuit(
    number_of_counting_qubits, phase
):
    """
    Create the circuit for quantum phase estimation.

    Parameters
    ----------
    number_of_counting_qubits : The number of qubits in the circuit.
    phase : The desired phase.

    Returns
    -------
    QuantumCircuit
        The quantum phase estimation circuit for `number_of_counting_qubits` qubits.
    """
    qc = QuantumCircuit(
        number_of_counting_qubits + 1, number_of_counting_qubits
    )
    target = number_of_counting_qubits

    # |1> eigenstate for the single-qubit phase gate
    qc.x(target)

    # Hadamards on counting register
    for q in range(number_of_counting_qubits):
        qc.h(q)

    # ONE controlled phase per counting qubit: cp(phase * 2**k)
    for k in range(number_of_counting_qubits):
        qc.cp(phase * (1 << k), k, target)

    qc.barrier()

    # Inverse QFT on counting register
    inverse_quantum_fourier_transform(qc, number_of_counting_qubits)

    qc.barrier()
    for q in range(number_of_counting_qubits):
        qc.measure(q, q)
    return qc

Schritt 3: Mit „ Qiskit primitives “ ausführen

Wir legen die Anzahl der Schüsse und Qubits für das Experiment fest und kodieren die Zielphase φ=1/6\varphi = 1/6 mit mm Binärziffern. Mit diesen Parametern erstellen wir den QPE-Schaltkreis, der auf der Simulation, der Standardhardware und den mit Fire Opal erweiterten Backends ausgeführt wird.

shot_count = 10000
num_qubits = 35
phase = (1 / 6) * 2 * np.pi
circuits_quantum_phase_estimation = (
    quantum_phase_estimation_benchmark_circuit(
        number_of_counting_qubits=num_qubits, phase=phase
    )
)

MPS-Simulation ausführen

Zunächst erzeugen wir mit dem matrix_product_state Simulator eine Referenzverteilung und wandeln die Zählungen in normalisierte Wahrscheinlichkeiten um, um sie später mit den Hardware-Ergebnissen zu vergleichen.

# Run the algorithm on the IBM Aer simulator.
aer_simulator = AerSimulator(method="matrix_product_state")

# Transpile the circuits for the simulator.
transpiled_circuits = qiskit.transpile(
    circuits_quantum_phase_estimation, aer_simulator
)
simulated_result = (
    aer_simulator.run(transpiled_circuits, shots=shot_count)
    .result()
    .get_counts()
)
simulated_result_probabilities = []

simulated_result_probabilities.append(
    bitstring_count_to_probabilities(
        simulated_result,
        shot_count=shot_count,
    )
)

Auf Hardware ausführen

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuits = pm.run(circuits_quantum_phase_estimation)
# Run the algorithm with IBM default.
sampler = Sampler(backend)

# Run all circuits using IBM Quantum Compute Service.
ibm_default_job = sampler.run([isa_circuits], shots=shot_count)

Auf Hardware mit Fire Opal ausführen

# Run the circuit using Sampler
fire_opal_job = perf_mgmt.run(
    primitive="sampler",
    pubs=[qasm2.dumps(circuits_quantum_phase_estimation)],
    backend_name=backend.name,
    options={"default_shots": shot_count},
)

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

# Retrieve results.
ibm_default_result = ibm_default_job.result()
ibm_default_probabilities = []

for idx, pub_result in enumerate(ibm_default_result):
    ibm_default_probabilities.append(
        bitstring_count_to_probabilities(
            pub_result.data.c0.get_counts(),
            shot_count=shot_count,
        )
    )
fire_opal_result = fire_opal_job.result()

fire_opal_probabilities = []
for idx, pub_result in enumerate(fire_opal_result):
    fire_opal_probabilities.append(
        bitstring_count_to_probabilities(
            pub_result.data.c0.get_counts(),
            shot_count=shot_count,
        )
    )
data = {
    "simulation": simulated_result_probabilities,
    "default": ibm_default_probabilities,
    "fire_opal": fire_opal_probabilities,
}
def plot_distributions(
    data,
    number_of_counting_qubits,
    top_k=None,
    by="prob",
    shot_count=None,
):
    def nrm(d):
        s = sum(d.values())
        return {k: (v / s if s else 0.0) for k, v in d.items()}

    def as_float(d):
        return {k: float(v) for k, v in d.items()}

    def to_space(d):
        if by == "prob":
            return nrm(as_float(d))
        else:
            if shot_count and 0.99 <= sum(d.values()) <= 1.01:
                return {
                    k: v * float(shot_count) for k, v in as_float(d).items()
                }
            else:
                return as_float(d)

    def topk(d, k):
        items = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
        return items[: (k or len(d))]

    phase = "1/6"

    sim = to_space(data["simulation"])
    dft = to_space(data["default"])
    qct = to_space(data["fire_opal"])

    correct = max(sim, key=sim.get) if sim else None
    print("Correct result:", correct)

    sim_items = topk(sim, top_k)
    dft_items = topk(dft, top_k)
    qct_items = topk(qct, top_k)

    sim_keys, y_sim = zip(*sim_items) if sim_items else ([], [])
    dft_keys, y_dft = zip(*dft_items) if dft_items else ([], [])
    qct_keys, y_qct = zip(*qct_items) if qct_items else ([], [])

    fig, axes = plt.subplots(3, 1, layout="constrained")
    ylab = "Probabilities"

    def panel(ax, keys, ys, title, color):
        x = np.arange(len(keys))
        bars = ax.bar(x, ys, color=color)
        ax.set_title(title)
        ax.set_ylabel(ylab)
        ax.set_xticks(x)
        ax.set_xticklabels(keys, rotation=90)
        ax.set_xlabel("Bitstrings")
        if correct in keys:
            i = keys.index(correct)
            bars[i].set_edgecolor("black")
            bars[i].set_linewidth(2)
        return max(ys, default=0.0)

    c_sim, c_dft, c_qct = (
        qv.QCTRL_STYLE_COLORS[5],
        qv.QCTRL_STYLE_COLORS[1],
        qv.QCTRL_STYLE_COLORS[0],
    )
    m1 = panel(axes[0], list(sim_keys), list(y_sim), "Simulation", c_sim)
    m2 = panel(axes[1], list(dft_keys), list(y_dft), "Default", c_dft)
    m3 = panel(axes[2], list(qct_keys), list(y_qct), "Q-CTRL", c_qct)

    for ax, m in zip(axes, (m1, m2, m3)):
        ax.set_ylim(0, 1.05 * (m or 1.0))

    for ax in axes:
        ax.label_outer()
    fig.suptitle(
        rf"{number_of_counting_qubits} counting qubits, $2\pi\varphi$={phase}"
    )
    fig.set_size_inches(20, 10)
    plt.show()
experiment_index = 0
phase_index = 0

distributions = {
    "simulation": data["simulation"][phase_index],
    "default": data["default"][phase_index],
    "fire_opal": data["fire_opal"][phase_index],
}

plot_distributions(
    distributions, num_qubits, top_k=100, by="prob", shot_count=shot_count
)

Output:

Correct result: 00101010101010101010101010101010101
Output of the previous code cell

Die Simulation legt die Basis für die richtige Eigenphase fest. Standard-Hardwareläufe zeigen Rauschen, das dieses Ergebnis verschleiert, da das Rauschen die Wahrscheinlichkeit über viele falsche Bitstrings verteilt. Mit Q-CTRL Performance Management wird die Verteilung schärfer und das korrekte Ergebnis wird wiederhergestellt, was eine zuverlässige QPE in diesem Umfang ermöglicht.


Referenzen

[1] Vorlesung 7: Phasenabschätzung und Faktorisierung. IBM Quantum Learning - Grundlagen der Quantenalgorithmen. Abgerufen am 3. Oktober 2025.


Tutorial-Umfrage

Bitte nehmen Sie sich eine Minute Zeit, um uns Ihr Feedback zu diesem Tutorial zu geben. Ihre Erkenntnisse helfen uns, unser Inhaltsangebot und unsere Nutzererfahrung zu verbessern.

Link zur Umfrage

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