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

Вступ до Qiskit

У цьому ноутбуці ми розглянемо, як програмувати квантові вентилі та квантові Circuit за допомогою Qiskit, а також як запускати їх на симуляторах і реальних квантових комп'ютерах за допомогою Qiskit patterns. Пізніше ми познайомимось із різними способами кодування інформації та завершимо бонусним прикладом квантової телепортації.

Перш ніж почати

Дотримуйся інструкцій зі встановлення та налаштування, якщо ти ще цього не зробив(-ла), включно з кроками для налаштування роботи з IBM Quantum™ Platform.

Рекомендується використовувати середовище розробки Jupyter для взаємодії з квантовими комп'ютерами. Обов'язково встанови рекомендовану додаткову підтримку візуалізації ('qiskit[visualization]'). Також знадобиться пакет matplotlib для другої частини цього прикладу.

Щоб дізнатися про квантові обчислення загалом, відвідай курс «Основи квантової інформації» в IBM Quantum Learning

Імпорти

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# Import necessary modules for this notebook
import time
import qiskit

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector, plot_state_qsphere
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import plot_histogram
print(qiskit.__version__)
2.3.1

Щоб запускати квантові Circuit на апаратному забезпеченні, спочатку потрібно налаштувати свій акаунт. Це можна зробити так:

  1. Перейди на оновлену IBM Quantum® Platform.
  2. Перейди у верхній правий кут (як показано на малюнку вище), створи свій API-токен і скопіюй його у надійне місце.
  3. У наступній комірці заміни deleteThisAndPasteYourAPIKeyHere своїм API-ключем.
  4. Перейди у нижній лівий кут (як показано на малюнку вище) та створи свій екземпляр. Переконайся, що вибрав(-ла) відкритий план.
  5. Після створення екземпляра скопіюй його CRN-код. Можливо, потрібно оновити сторінку, щоб побачити екземпляр.
  6. У комірці нижче заміни deleteThisAndPasteYourCRNHere своїм CRN-кодом.

Докладніше про налаштування акаунту IBM Cloud® дивись у цьому посібнику.

⚠️ Примітка: Постався до свого API-ключа як до надійного пароля. Дивись посібник Cloud setup для отримання докладнішої інформації про використання API-ключа у захищених і ненадійних середовищах.

#your_api_key = "deleteThisAndPasteYourAPIKeyHere"
#your_crn = "deleteThisAndPasteYourCRNHere"

QiskitRuntimeService.save_account(
channel="ibm_quantum_platform",
token=your_api_key,
instance=your_crn,
overwrite=True
)

1. Квантові Gate та квантові Circuit

Квантові Circuit — це моделі квантових обчислень, у яких обчислення є послідовністю квантових Gate. Розглянемо деякі з найпопулярніших квантових Gate.

X Gate

X Gate відповідає обертанню навколо осі X сфери Блоха на π\pi радіан. Він відображає 0|0\rangle в 1|1\rangle, а 1|1\rangle в 0|0\rangle. Це квантовий еквівалент Gate NOT для класичних комп'ютерів, який іноді називають «перекидачем бітів» (bit-flip).

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}

# Let's apply an X-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

H Gate

Gate Адамара (H Gate) являє собою обертання на π\pi навколо осі, що знаходиться посередині між осями XX і ZZ. Він відображає базисний стан 0|0\rangle у 0+12\frac{|0\rangle + |1\rangle}{\sqrt{2}}, що означає, що вимірювання матиме рівні ймовірності отримати 1 або 0, створюючи «суперпозицію» станів. Цей стан також записується як +|+\rangle.

H=12(1111)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}

# Let's apply an H-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

CX Gate (CNOT Gate)

Gate керованого NOT (або CNOT, або CX) діє на два Qubit. Він виконує операцію NOT (еквівалентну застосуванню X Gate) на другому Qubit лише тоді, коли перший Qubit перебуває у стані 1|1\rangle, в іншому випадку залишаючи його без змін. Примітка: Qiskit нумерує біти в рядку справа наліво.

CX=(1000010000010010)CX = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0\\ \end{pmatrix}

# Let's apply a CX-gate on |11>
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
qc.cx(0,1)
qc.draw(output='mpl')

Quantum circuit diagram

sv=Statevector(qc)
plot_state_qsphere(sv)

Code output

Створи перший стан Белла

ϕ+=12(00+11)|\phi^+ \rangle = \frac{1}{\sqrt 2}(|00 \rangle + |11 \rangle)

# Create a Bell state circuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Plot the state using q-sphere visualization
sv = Statevector(qc)
plot_state_qsphere(sv)
# q-sphere is useful for visualizing states when Bloch sphere fails to

Code output

Створи другий стан Белла

ϕ=12(0011)|\phi^- \rangle = \frac{1}{\sqrt 2}(|00 \rangle - |11 \rangle)

# Create a circuit with the second Bell state

qc = QuantumCircuit(2)
qc.x(0)
qc.h(0)
qc.cx(0,1)

qc.draw("mpl")

Quantum circuit diagram

Пояснення полягає в тому, що:

H1=12(01)=H|1\rangle=\frac{1}{\sqrt{2} }(|0\rangle-|1\rangle) = |-\rangle
# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Створи тривимірний стан GHZ

GHZ=12(000+111)|GHZ \rangle = \frac{1}{\sqrt 2}(|000 \rangle + |111 \rangle)

# Create a circuit with 3-qubit GHZ state

qc= QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)

qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Створи стан логотипу Qiskit

Qiskit=12(0010+1101)|Qiskit \rangle = \frac{1}{\sqrt 2}(|0010 \rangle + |1101 \rangle)

Centered Image
# Create a circuit with the Qiskit logo state

qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.x(1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

2. Створи і запусти просту квантову програму

Чотири кроки для написання квантової програми за допомогою Qiskit patterns:

  1. Відобрази задачу у квантово-нативний формат.

  2. Оптимізуй Circuit та оператори.

  3. Виконай, використовуючи квантову примітивну функцію.

  4. Проаналізуй результати.

2.1 Відображення задачі у квантово-нативний формат

У квантовій програмі квантові схеми (Circuit) є нативним форматом для представлення квантових інструкцій, а оператори — це спостережувані величини, які потрібно виміряти. Під час створення схеми (Circuit) ти зазвичай створюєш новий об'єкт QuantumCircuit, а потім послідовно додаєш до нього інструкції.

Наступна комірка коду створює схему (Circuit), яка породжує стан GHZ — стан, у якому три кубіти (Qubit) повністю заплутані між собою.

Qiskit SDK використовує нумерацію бітів LSb 0, де nthn^{th} розряд має значення 1n1 \ll n або 2n2^n. Докладніше дивись у темі Порядок бітів у Qiskit SDK.

# Create a GHZ state circuit

qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

Перелік усіх доступних операцій дивись у документації QuantumCircuit.

Під час створення квантових схем (Circuit) потрібно також врахувати, який тип даних ти хочеш отримати після виконання. Qiskit надає два способи повернення даних: можна отримати розподіл імовірностей для набору кубітів (Qubit), які ти вибираєш для вимірювання, або отримати значення очікування спостережуваної величини. Підготуй своє завдання для вимірювання схеми (Circuit) одним із цих двох способів за допомогою примітивів Qiskit (детально пояснених у Кроці 3).

У цьому прикладі вимірюються значення очікування з використанням підмодуля qiskit.quantum_info, що задається за допомогою операторів (математичних об'єктів, які представляють дію або процес, що змінює квантовий стан). Наступна комірка коду створює шість тривимірних операторів Паулі: ZZZ, ZZX, ZII, XXI, ZZI та III.

# Set up six different observables.

observables_labels = ["ZZZ", "ZZX", "ZII", "XXI", "ZZI", "III"]

observables = [SparsePauliOp(label) for label in observables_labels]
print(observables)
[SparsePauliOp(['ZZZ'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZX'],
coeffs=[1.+0.j]), SparsePauliOp(['ZII'],
coeffs=[1.+0.j]), SparsePauliOp(['XXI'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZI'],
coeffs=[1.+0.j]), SparsePauliOp(['III'],
coeffs=[1.+0.j])]

Тут щось на кшталт оператора ZZI є скороченням для тензорного добутку ZZIZ\otimes Z\otimes I, що означає вимірювання Z на кубіті (Qubit) 2 і Z на кубіті (Qubit) 1 разом та отримання інформації про кореляцію між кубітом (Qubit) 2 і кубітом (Qubit) 1. Значення очікування такого виду зазвичай також записуються як Z2Z1\langle Z_2 Z_1 \rangle.

Якщо спостережуваний стан є тривимірним станом GHZ, то вимірювання Z2Z1\langle Z_2 Z_1 \rangle має дорівнювати 1.

2.2 Оптимізація схем (Circuit) та операторів

Під час виконання схем (Circuit) на пристрої важливо оптимізувати набір інструкцій, що містить схема (Circuit), та мінімізувати загальну глибину (приблизно кількість інструкцій) схеми (Circuit). Це гарантує отримання найкращих можливих результатів шляхом зменшення впливу помилок і шуму. Крім того, інструкції схеми (Circuit) повинні відповідати набору інструкцій (ISA) Backend-пристрою та враховувати базові вентилі (Gate) і зв'язність кубітів (Qubit) пристрою.

Наступний код створює екземпляр реального пристрою для надсилання завдання та перетворює схему (Circuit) і спостережувані величини відповідно до ISA цього Backend. Якщо ти раніше не зберігав облікові дані, виконай інструкції тут, щоб пройти автентифікацію за допомогою свого API-токена.

# Choose a real backend
service = QiskitRuntimeService(channel='ibm_quantum_platform',)
backend = service.least_busy(min_num_qubits=156)
# print backend details
print(
f"Name: {backend.name}\n"
f"Version: {backend.backend_version}\n"
f"No. of qubits: {backend.num_qubits}\n"
f"Processor type: {backend.processor_type}\n"
)
Name: ibm_marrakesh
Version: 1.0.21
No. of qubits: 156
Processor type: {'family': 'Heron', 'revision': '2'}
# option to use the AerSimulator instead of a real quantum device
seed_sim=42
backend=AerSimulator.from_backend(backend,seed_simulator=seed_sim)

Транспілюй схему (Circuit) в ISA-схему (Circuit)

# Convert to an ISA circuit and layout-mapped observables.

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc)

isa_circuit.draw("mpl", idle_wires=False)

Quantum circuit diagram

mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]
print(mapped_observables)
[SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIXIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])]

2.3 Виконання за допомогою квантових примітивів

Квантові комп'ютери можуть видавати випадкові результати, тому зазвичай збирають вибірку виходів, запускаючи схему (Circuit) багато разів. Можна оцінити значення спостережуваної величини, використовуючи клас Estimator. Estimator — це один із двох примітивів; другий — Sampler, який можна використовувати для отримання даних з квантового комп'ютера. Ці об'єкти мають метод run(), що виконує вибір схем (Circuit), спостережуваних величин і параметрів (якщо є), використовуючи примітивний уніфікований блок (PUB). Під час виконання цього коду на реальному квантовому залізі розглянь можливість застосування технік пом'якшення та придушення помилок, щоб зменшити власний шум квантового комп'ютера.

# Construct the Estimator instance.
estimator = Estimator(mode=backend)
estimator.options.resilience_level = 1
estimator.options.default_shots = 5000

Надішли завдання за допомогою примітиву Estimator.

# One pub, with one circuit to run against six different observables.
job = estimator.run([(isa_circuit, mapped_observables)])

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job.job_id()}")
>>> Job ID: 97ecd036-1767-49b0-a1dc-c71638c3c3c4
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

Після надсилання завдання можна зачекати, поки завдання не завершиться у поточному екземплярі Python, або скористатися job_id, щоб отримати дані пізніше. (Дивись розділ про отримання завдань для деталей.)

Після завершення завдання перевір його вихідні дані через атрибут result() завдання.

# This is the result of the entire submission.  You submitted one Pub,
# so this contains one inner result (and some metadata of its own).
job_result = job.result()

# This is the result from our single pub, which had six observables,
# so contains information on all six.
pub_result = job.result()[0]

Тепер можна також виконати схему (Circuit) за допомогою примітиву Sampler

# We include the measurements in the circuit
qc.measure_all()
sampler = Sampler(mode=backend)
qc.draw(output="mpl")

Quantum circuit diagram

Надішли завдання за допомогою примітиву Sampler.

job_sampler = sampler.run(pm.run([qc]))

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job_sampler.job_id()}")
# Get the results
results_sampler = job_sampler.result()
>>> Job ID: a6ee4d2f-c80d-4a86-9a76-e4b1a74502e7

2.4 Аналіз результатів

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

Значення очікування та стандартні відхилення для операторів, які ти вказав для Estimator, доступні через атрибути PubResult.data.evs і PubResult.data.stds результату задачі. Щоб отримати результати від Sampler, використай функцію PubResult.data.meas.get_counts(), яка поверне dict вимірювань у вигляді бітових рядків як ключів і відповідних їм значень лічильників. Для отримання додаткової інформації дивись Get started with Sampler.

# Plot the result
from matplotlib import pyplot as plt
values = pub_result.data.evs
errors = pub_result.data.stds
# plotting graph
# Plotting with error bars
plt.errorbar(observables_labels, values, yerr=errors, fmt='-o', capsize=5)
plt.xlabel("Observables")
plt.ylabel("Values")
plt.title("Plot of Observables vs Values with Error Bars")
plt.grid(True)
plt.tight_layout()
plt.show()

Plot output

Ми бачимо, що спостережувані ZZIZZI і IIIIII мають значення очікування 1, оскільки ZZIZZI вводить два мінуси, які скасовують один одного, а IIIIII діє як тотожність, залишаючи стан GHZ незмінним. Решта спостережуваних мають значення очікування 0, оскільки їхні оператори ZZ вводять непарну кількість мінусів, або оператори XX перевертають кількість кубітів, що робить стани, які накладаються, ортогональними.

Тепер побудуємо результати для Sampler

counts_list = results_sampler[0].data.meas.get_counts()
print(counts_list)
print(f"Outcomes : {counts_list}")
display(plot_histogram(counts_list, title="GHZ state"))
{'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}
Outcomes : {'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}

Code output

2.5 Масштабування до великої кількості кубітів

У квантових обчисленнях робота в масштабах корисності є ключовою для прогресу в цій галузі. Така робота вимагає виконання обчислень у значно більшому масштабі; роботи зі схемами, які можуть використовувати понад 100 кубітів і понад 1000 Gate. Цей приклад робить невеликий крок у цьому напрямку, масштабуючи задачу GHZ до n=10n=10 кубітів. Він використовує робочий процес шаблонів Qiskit і завершується вимірюванням значення очікування Z0Zi\langle Z_0 Z_i \rangle .

Крок 1. Відображення задачі

Напиши функцію, яка повертає QuantumCircuit, що готує nn-кубітний стан GHZ (по суті розширений стан Белла), а потім використай цю функцію для підготовки 10-кубітного стану GHZ та збери спостережувані для вимірювання.

def get_qc_for_n_qubit_GHZ_state(n: int) -> QuantumCircuit:

qc = QuantumCircuit(n)
qc.h(0)
for i in range(n-1):
qc.cx(i, i+1)
return qc
n = 10
qc_n_GHZ = get_qc_for_n_qubit_GHZ_state(n)
qc_n_GHZ.draw("mpl")

Quantum circuit diagram

Далі виконай відображення на оператори, що нас цікавлять. У цьому прикладі використовуються оператори ZZ між кубітами для вивчення поведінки в міру того, як вони стають дедалі далі один від одного. Дедалі неточніші (спотворені) значення очікування між віддаленими кубітами виявлять рівень шуму.

# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + i * "I" + "Z" + "I" * (n-i-2) for i in range(n-1)
]
print(operator_strings)
print(len(operator_strings))

operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIII', 'ZIZIIIIIII', 'ZIIZIIIIII', 'ZIIIZIIIII', 'ZIIIIZIIII', 'ZIIIIIZIII', 'ZIIIIIIZII', 'ZIIIIIIIZI', 'ZIIIIIIIIZ']
9

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

Перетвори Circuit та спостережувані відповідно до ISA Backend.

# Convert to an ISA circuit and layout-mapped observables.
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc_n_GHZ)
isa_operators_list = [operator.apply_layout(isa_circuit.layout) for operator in operators]

Крок 3. Виконання на Backend

Відправ задачу, і якщо ти виконуєш її на реальному обладнанні, увімкни придушення помилок за допомогою техніки зменшення помилок, що називається динамічним розв'язуванням. Рівень стійкості визначає, наскільки сильним є захист від помилок. Вищі рівні дають точніші результати, але ціною більш тривалого часу обробки. Для детального пояснення параметрів, встановлених у наступному коді, дивись Configure error mitigation for Qiskit Runtime.

# Submit the circuit to Estimator
job = estimator.run([(isa_circuit, isa_operators_list)])
job_id = job.job_id()
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

Крок 4. Постобробка результатів

Щоб краще зрозуміти поведінку заплутаних квантових станів на реальному обладнанні, ми аналізуємо попарні кореляції між кубітами в базисі Z. Зокрема, ми розглядаємо значення очікування ⟨Z₀Zᵢ⟩, які вимірюють, наскільки сильно кубіт 0 корелює з кожним іншим кубітом i. Зокрема, ми збираємося побудувати графік:

ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle

Які значення ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle ти очікуєш побачити на графіку?

Варіанти:

a) Спадання зі збільшенням ii

b) Константа, рівна 1

c) Незначні відхилення навколо 1

d) Чергування 1 і 0 для непарних і парних значень ii

data = list(range(1, len(operators) + 1))  # Distance between the Z operators
result = job.result()[0]
values = result.data.evs # Expectation value at each Z operator.
values = [
v / values[0] for v in values
] # Normalize the expectation values to evaluate how they decay with distance.

plt.plot(data, values, marker="o", label=f"{n}-qubit GHZ state")
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Plot output

На цьому графіку ми помічаємо, що Z0Zi\langle Z_0 Z_i \rangle коливається навколо значення 1, хоча в ідеальному моделюванні всі Z0Zi\langle Z_0 Z_i \rangle мали б дорівнювати 1.

Як ти можеш бачити, результати експериментів з 10 кубітами є непоганими, але все ще містять певні помилки. Один із способів покращити результати — реалізувати стан GHZ ефективніше.

Зазвичай стан GHZ реалізують за допомогою сходоподібної послідовності Gate CNOT. Однак ти можеш реалізувати стан GHZ ефективніше, зменшивши глибину двокубітних воріт з n до n/2 або менше.

Одна важлива метрика для оцінки того, наскільки точними будуть результати або наскільки малим буде шум для Circuit, — це глибина двокубітних Gate. Це пов'язано з тим, що рівні похибок двокубітних Gate (приблизно в 10 разів вищі, ніж для однокубітних Gate) домінують над похибками всієї схеми. Використовуй наступний код, щоб отримати глибину двокубітних Gate схеми.

qc.depth(lambda x: x.operation.num_qubits == 2)
def better_ghz(n):
"fan out"
s = int(n / 2)
qc = QuantumCircuit(n)
qc.h(s)
for m in range(s, 0, -1):
qc.cx(m, m - 1)
if not (n % 2 == 0 and m == s):
qc.cx(n - m - 1, n - m)
return qc

better_ghz(n).draw("mpl")

Quantum circuit diagram

# Check 2-qubit gate depth before transpilation
qc_better_ghz = better_ghz(n)
qc_better_ghz.depth(lambda x: x.operation.num_qubits == 2)
5

Варто зазначити цікаву річ: нам вдалося зменшити квантову глибину Circuit, яку ми хочемо виконати, просто будучи кмітливими та придумавши інший спосіб її програмування. Однак будуть ситуації та алгоритми, де ми не зможемо покладатися на ці хитрі прийоми. Саме тут у нагоді стає Transpiler — він допомагає нам ефективно оптимізувати всі ці аспекти, щоб нам не доводилося надто перейматися ними.

3. Кодування інформації

3.1 Amplitude encoding

Тепер, коли ми побачили, як будувати квантові схеми, цікаво дослідити, як ми можемо кодувати класичну інформацію в квантові стани. Один потужний метод — це амплітудне кодування, де амплітуди квантового стану представляють компоненти класичного вектора.

Розглянемо простий приклад. Припустимо, ми хочемо закодувати класичний вектор

x=[x0x1x2x3]\vec{x} = \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \end{bmatrix}

у квантовий стан двох кубітів. Мета — підготувати квантовий стан:

ψ=x000+x101+x210+x311\ket{\psi} = x_0\ket{00} + x_1\ket{01} + x_2\ket{10} + x_3\ket{11}

де x0,x1,x2,x3Rx_0, x_1, x_2, x_3 \in \mathbb{R} (або C\mathbb{C}) і вектор нормований так, що:

x02+x12+x22+x32=1|x_0|^2 + |x_1|^2 + |x_2|^2 + |x_3|^2 = 1

Тепер розглянемо конкретний приклад: x=[0.8924,0.3696,0.2391,0.0990]\vec{x} = [0.8924, 0.3696, 0.2391, 0.0990]

Тоді відповідний квантовий стан такий:

ψ=0.892400+0.369601+0.239110+0.099011\begin{aligned} \ket{\psi} &= 0.8924\,\ket{00} + 0.3696\,\ket{01} + 0.2391\,\ket{10} + 0.0990\,\ket{11} \end{aligned}

Цей стан можна підготувати, використовуючи комбінацію гейтів обертання RyR_y з кутами π/6\pi/6 та π/4\pi/4 для кубітів 0 і 1 відповідно

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
import numpy as np

qc = QuantumCircuit(2)

qc.ry(np.pi / 6, 0)
qc.ry(np.pi / 4, 1)

simulator = AerSimulator()
qc.save_statevector()
result = simulator.run(qc).result()
statevector = result.get_statevector()

print("Statevector:", statevector)
qc.draw(output="mpl")
Statevector: Statevector([0.8923991 +0.j, 0.23911762+0.j, 0.36964381+0.j,
0.09904576+0.j],
dims=(2, 2))

Quantum circuit diagram

from qiskit.quantum_info import Statevector

# Define our vector
v = np.array([0.8924, 0.3696, 0.2391, 0.0990])
v = v/np.linalg.norm(v)
# Create a statevector from the vector
state = Statevector(v)

# Initialize a quantum circuit with 2 qubits
qc = QuantumCircuit(2)
qc.initialize(state.data, [0, 1])

# Optional: simulate the state
print("Statevector:", state)

# Visualize the circuit
qc.decompose().decompose().decompose().decompose().decompose().draw("mpl")
Statevector: Statevector([0.89242154+0.j, 0.36960892+0.j, 0.23910577+0.j,
0.09900239+0.j],
dims=(2, 2))

Quantum circuit diagram

Отже, ми побачили, як кодувати інформацію за допомогою гейтів обертання.

3.2 Angle encoding і параметризовані Circuit

Особливо цікавим способом кодування інформації в квантовий комп'ютер є проектування квантової схеми, що містить деякі кути обертання θ\vec{\theta} або параметри, які можна налаштовувати для представлення сімейства функцій f(θ)f(\vec{\theta}). Розглянемо, наприклад, такий параметризований квантовий Circuit:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta = Parameter("θ")

qc = QuantumCircuit(2)
# We applied a parametrized RX gate
qc.rx(theta, 0)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

Математично ми можемо проаналізувати, яке сімейство функцій ми можемо представити за допомогою цього Circuit:

CNOT01Rx{0}(θ)00=CNOT01(cos(θ/2)00isin(θ/2)10)=cos(θ/2)00isin(θ/2)11\text{CNOT}_{01} \, R_x^{\{0\}}(\theta) |00\rangle = \text{CNOT}_{01} \left( \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{10} \right) = \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{11}

Цілком очевидно, що кількість станів, які ми можемо представити за допомогою цього квантового Circuit, обмежена, оскільки ми не можемо представити, наприклад, стани 10\ket{10} або 01\ket{01}. Однак сімейство станів, яке ми можемо представити, починає зростати, коли ми вводимо більше обертань у відповідних місцях:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta1 = Parameter("θ1")
theta2 = Parameter("θ2")

qc = QuantumCircuit(2)
qc.rx(theta1, 0)
qc.rx(theta2, 1)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

У цьому випадку квантові стани, які ми будемо представляти, такі:

\begin{align*} \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2) R_x^{\{0}}(\theta_1) \ket{00} &= \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2)\left( \cos(\theta_1/2)\ket{00} - i\sin(\theta_1/2)\ket{10} \right) \\ &= \text{CNOT}_{01}\left( \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \right. \\ &\quad \left. - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{10} + \sin(\theta_1/2)\sin(\theta_2/2)\ket{11} \right) \\ &= \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \\ &\quad + \sin(\theta_1/2)\sin(\theta_2/2)\ket{10} - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{11} \end{align*}

Ми бачимо, що цей Circuit генерує ширше сімейство квантових станів порівняно з попереднім. Зокрема, тепер він може породжувати стани з ненульовими амплітудами для 01\ket{01} або 10\ket{10}, що було неможливо з попередньою схемою. Однак цей Circuit все ще не є універсальним генератором квантових станів, хоча він може бути достатньо виразним для проектування схем із певною гнучкістю для представлення деяких функцій. Загалом, чим більше незалежних параметрів (кутів) ми вводимо, тим більшою виразністю має Circuit для апроксимації довільних квантових станів.

Ansatzes та бібліотека Circuit

Такий тип параметризованих квантових Circuit можна використовувати для побудови Ansatzes — пробних квантових станів, що прагнуть апроксимувати розв'язок деякої задачі. Ці Ansatzes є центральним компонентом варіаційних квантових алгоритмів — класу гібридних квантово-класичних алгоритмів, які використовують квантовий комп'ютер для обчислення функції вартості і класичний оптимізатор для її мінімізації. Ми детально розглянемо ці теми в наступному блоці, але поки що ми покажемо, як побудувати простий ansatz за допомогою бібліотеки Circuit у Qiskit.

from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
SU2_ansatz.decompose().draw(output="mpl")

Quantum circuit diagram

Ми побачили, як побудувати простий Ansatz за допомогою функції efficient_su2 з qiskit.circuit.library, який зможе генерувати широкий діапазон квантових станів шляхом налаштування своїх параметрів θ\vec{\theta}.

Висновок

У цьому ноутбуку ти навчився(-лась) будувати квантові схеми — від конструювання квантових Gate до визначення та вимірювання спостережуваних величин — а також ефективно виконувати ці Circuit на симуляторах і реальному квантовому обладнанні. Ти також побачив(-ла) важливість ретельного проектування Circuit для мінімізації помилок при роботі з реальними квантовими пристроями, а також стратегії масштабування Circuit на більшу кількість кубітів, зокрема на прикладі стану GHZ. Крім того, ти дослідив(-ла) різні техніки кодування класичної інформації у квантові стани, включаючи амплітудне кодування та angle encoding. Маючи все це, ти повністю готовий(-а) перейти до наступної сесії і почати працювати з квантовими алгоритмами.

Встановлення Qiskit Code Assistant у VSCode

Перейди за посиланням і дотримуйся інструкцій.

Бонус: Квантова телепортація

Коли ти чуєш термін «квантова телепортація», тобі може уявлятися футуристична науково-фантастична технологія, яка розкладає об'єкт в одному місці і відтворює його десь далеко. Але квантова телепортація зовсім не така. Насправді телепортується не матерія — телепортується інформація.

Квантова телепортація дозволяє передати квантовий стан кубіта з одного місця в інше. Хоча ця передача здається миттєвою, вона не порушує закони фізики. Як таке можливо? Давай розберемося!

Квантова телепортація — це протокол, який дозволяє відправнику (Аліса) передати стан ψ|\psi\rangle кубіта q отримувачу (Боб), використовуючи два ключові ресурси: спільну заплутану пару кубітів a і b та два біти класичного зв'язку c0 і c1.

По суті, протоколу потрібно:

  • q: кубіт Аліси, спочатку в стані ψ|\psi\rangle, який ми хочемо телепортувати.
  • a: половина спільної заплутаної пари, що належить Алісі.
  • b: половина спільної заплутаної пари, що належить Бобу.
  • c0, c1: класичні біти для зберігання результатів вимірювань Аліси.

А як це працює? Робочий процес такий:

  1. Підготуй стан Аліси ψ|\psi\rangle на q. Ми створимо конкретний стан на зразок +|+\rangle для перевірки.
  2. Створи заплутаність: згенеруй пару Белла між a і b.
  3. Операції Аліси: Аліса виконує «вимірювання Белла» на своїх двох кубітах (q і a) і зберігає класичні результати в c0 і c1.
  4. Класичний зв'язок: Аліса надсилає Бобу свої два класичні біти (c0, c1).
  5. Корекції Боба: Боб застосовує певні квантові Gate (X та/або Z) до свого кубіта (b) залежно від значень c0 і c1, які він отримав.

Якщо все зроблено правильно, кубіт Боба b опиниться в стані ψ|\psi\rangle — вихідному стані q Аліси!

Для більш детального пояснення та дослідження квантової телепортації, включаючи математичне обґрунтування того, чому цей протокол працює, ти можеш звернутися до навчальних ресурсів IBM Quantum: Quantum Teleportation. Це частина курсу Basics of Quantum Information.


import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector

# Define individual quantum registers for each qubit
q = QuantumRegister(1, name='q') # message qubit
a = QuantumRegister(1, name='a') # Alice's entangled qubit
b = QuantumRegister(1, name='b') # Bob's entangled qubit

# Classical register for Alice's measurements
cr_alice = ClassicalRegister(2, name='c_alice')

# Create quantum circuit
teleport_qc = QuantumCircuit(q, a, b, cr_alice, name='Teleportation')

# Step 1: Prepare message state |+⟩ on q
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 2: Create entanglement between a and b
teleport_qc.h(a[0])
teleport_qc.cx(a[0], b[0])
teleport_qc.barrier()

# Step 3: Alice's Bell measurement
teleport_qc.cx(q[0], a[0])
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 4: Alice measures q and a
teleport_qc.measure(q[0], cr_alice[0])
teleport_qc.measure(a[0], cr_alice[1])
teleport_qc.barrier()

# Step 5: Bob's conditional measurements
with teleport_qc.if_test((cr_alice[1], 1)):
teleport_qc.x(b[0])
with teleport_qc.if_test((cr_alice[0], 1)):
teleport_qc.z(b[0])

# Draw the circuit
teleport_qc.draw(output='mpl')

Quantum circuit diagram

Після виконання протоколу виникає ключове питання: як перевірити, що телепортація спрацювала? Ми не можемо безпосередньо «побачити» стан кубіта Боба після виконання протоколу. Однак, оскільки ми підготували початковий стан Аліси ψ|\psi\rangle (ми обрали +|+\rangle), ми можемо скористатися спеціальним типом симуляції, щоб перевірити, чи опинився кубіт Боба b у тому самому стані.

Ми будемо використовувати AerSimulator з save_statevector, щоб перевірити, чи опинився кубіт Боба b у вихідному стані Аліси (+|+\rangle). Цей симулятор обчислює вектор фінального квантового стану. і потім представляє його за допомогою plot_bloch_multivector, щоб візуалізувати кубіт Боба (b) порівняно з початковим станом Аліси (q).

# Simulate the teleportation circuit
sv_simulator = AerSimulator(method='statevector')
teleport_qc_sv = teleport_qc.copy()
teleport_qc_sv.save_statevector()

# Execute the circuit on the statevector simulator
job_sv = sv_simulator.run(teleport_qc_sv)
result_sv = job_sv.result()

# Get the final statevector
final_statevector = result_sv.get_statevector()
print("Visualizing final qubit states:")
display(plot_bloch_multivector(final_statevector))
print("Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.")
Visualizing final qubit states:

Quantum circuit diagram

Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.

Як ми бачимо з візуалізації, перші два кубіти (що належать Алісі) колапсували до 0 або 1. Тим часом третій кубіт (що належить Бобу), представлений на третій сфері Блоха, вказує вздовж осі x, що свідчить про те, що він перебуває в стані +|+\rangle, — отже, ми успішно реалізували протокол квантової телепортації!

Підсумок

На цьому моменті зручно зробити короткий підсумок того, чого ми досягли:

  • Аліса передала невідомий квантовий стан Бобу.
  • Жодна фізична частинка не була передана.
  • Вихідний стан кубіта Аліси знищено відповідно до теореми про заборону клонування.