Lumaktaw sa pangunahing nilalaman

Quantum Phase Estimation gamit ang Q-CTRL's Qiskit Functions

Tinatayang paggamit: 40 segundo sa Heron r2 processor. (PAALALA: Ito ay tantiya lamang. Maaaring mag-iba ang inyong runtime.)

Background

Ang Quantum Phase Estimation (QPE) ay isang pangunahing algorithm sa quantum computing na bumubuo ng batayan ng maraming mahalagang aplikasyon gaya ng Shor's algorithm, quantum chemistry ground-state energy estimation, at eigenvalue problems. Tinatantya ng QPE ang phase φ\varphi na nauugnay sa isang eigenstate ng unitary operator, na naka-encode sa relasyon

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

at tinutukoy ito sa precision na ϵ=O(1/2m)\epsilon = O(1/2^m) gamit ang mm counting qubit [1]. Sa pamamagitan ng paghahanda ng mga qubit na ito sa superposition, paglalapat ng controlled powers ng UU, at pagkatapos ay paggamit ng inverse Quantum Fourier Transform (QFT) upang kunin ang phase sa binary-encoded measurement outcomes, ang QPE ay gumagawa ng probability distribution na nakatuon sa mga bitstring na ang binary fractions ay humigit-kumulang sa φ\varphi. Sa ideal na kaso, ang pinaka-malamang na measurement outcome ay direktang tumutugma sa binary expansion ng phase, habang ang posibilidad ng iba pang mga kinalabasan ay bumababa nang mabilis kasama ng bilang ng mga counting qubit. Gayunpaman, ang pagpapatakbo ng malalim na QPE circuit sa hardware ay may mga hamon: ang malaking bilang ng mga qubit at entangling operation ay ginagawang lubhang sensitibo ang algorithm sa decoherence at gate error. Nagreresulta ito sa mas malawak at nalipat na mga distribusyon ng mga bitstring, na nakakatago sa tunay na eigenphase. Bilang resulta, ang bitstring na may pinakamataas na posibilidad ay maaaring hindi na tumugma sa tamang binary expansion ng φ\varphi.

Sa tutorial na ito, inilalahad namin ang isang pagpapatupad ng QPE algorithm gamit ang Q-CTRL's Fire Opal error suppression at performance management tools, na inaalok bilang Qiskit Function (tingnan ang Fire Opal documentation). Awtomatikong inilalapat ng Fire Opal ang mga advanced optimization, kabilang ang dynamical decoupling, pagpapabuti ng qubit layout, at mga diskarte sa error suppression, na nagreresulta sa mas mataas na fidelity na mga kinalabasan. Ang mga pagpapabuting ito ay nagdadala ng mga distribusyon ng bitstring sa hardware na mas malapit sa nakuha sa walang ingay na mga simulation, upang matukoy ninyo nang maaasahan ang tamang eigenphase kahit sa ilalim ng mga epekto ng ingay.

Requirements

Bago simulan ang tutorial na ito, siguruhing mayroon kayong mga sumusunod na naka-install:

  • Qiskit SDK v1.4 o mas bago, na may suporta sa visualization
  • Qiskit Runtime v0.40 o mas bago (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog v0.9.0 (pip install qiskit-ibm-catalog)
  • Fire Opal SDK v9.0.2 o mas bago (pip install fire-opal)
  • Q-CTRL Visualizer v8.0.2 o mas bago (pip install qctrl-visualizer)

Setup

Una, mag-authenticate gamit ang inyong IBM Quantum API key. Pagkatapos, piliin ang Qiskit Function tulad ng sumusunod. (Ipinapalagay ng code na ito na nai-save ninyo na ang inyong account sa inyong lokal na kapaligiran.)

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qctrlvisualizer qiskit qiskit-aer qiskit-ibm-catalog qiskit-ibm-runtime
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")

Step 1: Map classical inputs to a quantum problem

Sa tutorial na ito, ipinakikita namin ang QPE upang mabawi ang eigenphase ng kilalang single-qubit unitary. Ang unitary na ang phase ay nais nating tantiyahin ay ang single-qubit phase gate na inilapat sa target qubit:

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

Inihahanda namin ang eigenstate nito ψ=1|\psi\rangle=|1\rangle. Dahil ang 1|1\rangle ay isang eigenvector ng U(θ)U(\theta) na may eigenvalue na eiθe^{i\theta}, ang eigenphase na tatantiyahin ay:

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

Itinakda namin ang θ=162π\theta=\tfrac{1}{6}\cdot 2\pi, kaya ang ground-truth phase ay φ=1/6\varphi=1/6. Ipinapatupad ng QPE circuit ang mga controlled power na U2kU^{2^k} sa pamamagitan ng paglalapat ng mga controlled phase rotation na may mga anggulo na θ2k\theta\cdot2^k, pagkatapos ay inilalapat ang inverse QFT sa counting register at sinusukat ito. Ang mga resultang bitstring ay nakakonsentrasyon sa paligid ng binary representation ng 1/61/6.

Gumagamit ang circuit ng mm counting qubit (upang itakda ang estimation precision) kasama ang isang target qubit. Nagsisimula kami sa pamamagitan ng pagtukoy ng mga building block na kailangan upang ipatupad ang QPE: ang Quantum Fourier Transform (QFT) at ang inverse nito, mga utility function upang mag-map sa pagitan ng decimal at binary fraction ng eigenphase, at mga helper upang i-normalize ang mga raw count sa mga posibilidad para sa paghahambing ng simulation at hardware result.

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

Step 2: Optimize problem for quantum hardware execution

Binubuo namin ang QPE circuit sa pamamagitan ng paghahanda ng mga counting qubit sa superposition, paglalapat ng mga controlled phase rotation upang i-encode ang target eigenphase, at pagtatapos sa isang inverse QFT bago ang pagsukat.

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

Step 3: Execute using Qiskit primitives

Itinakda namin ang bilang ng mga shot at qubit para sa eksperimento, at nag-encode ng target phase na φ=1/6\varphi = 1/6 gamit ang mm binary digit. Sa mga parameter na ito, binubuo namin ang QPE circuit na isasagawa sa simulation, default hardware, at Fire Opal–enhanced backend.

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

Run MPS simulation

Una, bumubuo kami ng reference distribution gamit ang matrix_product_state simulator at kino-convert ang mga count sa normalized na mga posibilidad para sa susunod na paghahambing sa mga resulta ng hardware.

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

Run on hardware

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 Qiskit Runtime.
ibm_default_job = sampler.run([isa_circuits], shots=shot_count)

Run on hardware with Fire Opal

# Run the circuit using the 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},
)

Step 4: Post-process and return result in desired classical 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
)
Correct result: 00101010101010101010101010101010101

Output of the previous code cell

Itinakda ng simulation ang baseline para sa tamang eigenphase. Ang mga default hardware run ay nagpapakita ng ingay na nagtatago sa resultang ito, dahil ang ingay ay kumakalat ng posibilidad sa maraming maling bitstring. Sa Q-CTRL Performance Management, ang distribusyon ay nagiging mas matalas at ang tamang kinalabasan ay nabawi, na nagbibigay-daan sa maaasahang QPE sa sukat na ito.

References

[1] Lecture 7: Phase Estimation and Factoring. IBM Quantum Learning - Fundamentals of quantum algorithms. Retrieved October 3, 2025.

Tutorial survey

Mangyaring maglaan ng isang minuto upang magbigay ng feedback sa tutorial na ito. Ang inyong mga insight ay tutulong sa amin na mapabuti ang aming mga content offering at karanasan ng user.

Link to survey