Перейти до основного вмісту

Квантова оцінка фази за допомогою Qiskit-функцій Q-CTRL

Оцінка використання: 40 секунд на процесорі Heron r2. (ПРИМІТКА: Це лише оцінка. Ваш час виконання може відрізнятися.)

Передумови

Квантова оцінка фази (QPE) — це фундаментальний алгоритм у квантових обчисленнях, який лежить в основі багатьох важливих застосувань, таких як алгоритм Шора, оцінка енергії основного стану в квантовій хімії та задачі на власні значення. QPE оцінює фазу φ\varphi, пов'язану з власним станом унітарного оператора, закодовану у співвідношенні

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

і визначає її з точністю ϵ=O(1/2m)\epsilon = O(1/2^m), використовуючи mm лічильних кубітів [1]. Підготувавши ці кубіти в суперпозиції, застосувавши контрольовані степені UU, а потім використавши обернене квантове перетворення Фур'є (QFT) для вилучення фази у двійково закодовані результати вимірювань, QPE створює розподіл ймовірностей із піком на бітових рядках, двійкові дроби яких апроксимують φ\varphi. В ідеальному випадку найбільш імовірний результат вимірювання безпосередньо відповідає двійковому розкладу фази, тоді як ймовірність інших результатів швидко зменшується зі збільшенням кількості лічильних кубітів. Однак виконання глибоких QPE-схем на апаратному забезпеченні створює труднощі: велика кількість кубітів та заплутуючих операцій робить алгоритм дуже чутливим до декогеренції та помилок вентилів. Це призводить до розширених і зміщених розподілів бітових рядків, що маскують справжню власну фазу. Як наслідок, бітовий рядок із найвищою ймовірністю може більше не відповідати правильному двійковому розкладу φ\varphi.

У цьому посібнику ми представляємо реалізацію алгоритму QPE з використанням інструментів придушення помилок та управління продуктивністю Fire Opal від Q-CTRL, що пропонуються як Qiskit-функція (див. документацію Fire Opal). Fire Opal автоматично застосовує передові оптимізації, включаючи динамічне розв'язування, покращення розташування кубітів та методи придушення помилок, що забезпечує результати з вищою точністю. Ці покращення наближають апаратні розподіли бітових рядків до тих, що отримані в безшумних симуляціях, щоб ти міг надійно ідентифікувати правильну власну фазу навіть в умовах впливу шуму.

Вимоги

Перш ніж розпочати цей посібник, переконайся, що у тебе встановлено наступне:

  • Qiskit SDK v1.4 або новіший, з підтримкою візуалізації
  • Qiskit Runtime v0.40 або новіший (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog v0.9.0 (pip install qiskit-ibm-catalog)
  • Fire Opal SDK v9.0.2 або новіший (pip install fire-opal)
  • Q-CTRL Visualizer v8.0.2 або новіший (pip install qctrl-visualizer)

Налаштування

Спочатку автентифікуйся за допомогою твого API-ключа IBM Quantum. Потім вибери Qiskit-функцію наступним чином. (Цей код передбачає, що ти вже зберіг свій обліковий запис у локальному середовищі.)

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

Крок 1: Відображення класичних вхідних даних на квантову задачу

У цьому посібнику ми демонструємо QPE для відновлення власної фази відомого однокубітного унітарного оператора. Унітарний оператор, фазу якого ми хочемо оцінити, — це однокубітний фазовий вентиль, що застосовується до цільового кубіта:

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

Ми готуємо його власний стан ψ=1|\psi\rangle=|1\rangle. Оскільки 1|1\rangle є власним вектором U(θ)U(\theta) із власним значенням eiθe^{i\theta}, власна фаза, яку потрібно оцінити, дорівнює:

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

Ми встановлюємо θ=162π\theta=\tfrac{1}{6}\cdot 2\pi, тому справжня фаза дорівнює φ=1/6\varphi=1/6. QPE-схема реалізує контрольовані степені U2kU^{2^k} шляхом застосування контрольованих фазових обертань з кутами θ2k\theta\cdot2^k, а потім застосовує обернене QFT до лічильного регістра та вимірює його. Отримані бітові рядки концентруються навколо двійкового представлення 1/61/6.

Схема використовує mm лічильних кубітів (для встановлення точності оцінки) плюс один цільовий кубіт. Ми починаємо з визначення будівельних блоків, необхідних для реалізації QPE: квантове перетворення Фур'є (QFT) та його обернене, допоміжні функції для перетворення між десятковими та двійковими дробами власної фази, а також допоміжні функції для нормалізації необроблених підрахунків у ймовірності для порівняння результатів симуляції та апаратного забезпечення.

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

Крок 2: Оптимізація задачі для виконання на квантовому апаратному забезпеченні

Ми будуємо QPE-схему, підготувавши лічильні кубіти в суперпозиції, застосувавши контрольовані фазові обертання для кодування цільової власної фази та завершивши оберненим QFT перед вимірюванням.

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

Крок 3: Виконання за допомогою примітивів Qiskit

Ми встановлюємо кількість вимірювань та кубітів для експерименту і кодуємо цільову фазу φ=1/6\varphi = 1/6, використовуючи mm двійкових розрядів. З цими параметрами ми будуємо QPE-схему, яка буде виконана на симуляції, стандартному апаратному забезпеченні та бекендах, покращених за допомогою Fire Opal.

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-симуляції

Спочатку ми генеруємо еталонний розподіл за допомогою симулятора matrix_product_state та перетворюємо підрахунки в нормалізовані ймовірності для подальшого порівняння з результатами апаратного забезпечення.

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

Запуск на апаратному забезпеченні

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)

Запуск на апаратному забезпеченні з 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},
)

Крок 4: Постобробка та повернення результату у бажаному класичному форматі

# 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

Вихідні дані попередньої комірки коду

Симуляція встановлює базовий рівень для правильної власної фази. Стандартні запуски на апаратному забезпеченні демонструють шум, який спотворює цей результат, оскільки шум розподіляє ймовірність між багатьма неправильними бітовими рядками. З використанням Q-CTRL Performance Management розподіл стає чіткішим, і правильний результат відновлюється, що забезпечує надійну QPE на цьому масштабі.

Джерела

[1] Лекція 7: Оцінка фази та факторизація. IBM Quantum Learning — Основи квантових алгоритмів. Отримано 3 жовтня 2025 року.

Опитування щодо посібника

Будь ласка, приділи хвилину, щоб залишити відгук про цей посібник. Твої враження допоможуть нам покращити наш контент та досвід користувачів.

Посилання на опитування