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

Реалізація в Qiskit

У цьому розділі ми розглянемо деякі реалізації концепцій, представлених у цьому уроці, за допомогою Qiskit. Якщо ти хочеш запустити ці реалізації самостійно — а це настійно рекомендується, — зверни до сторінки Встановлення Qiskit у документації IBM Quantum за інструкціями щодо налаштування Qiskit.

Слід розуміти, що Qiskit постійно розвивається та насамперед зосереджений на максимізації продуктивності квантових комп'ютерів, якими він керує, а самі комп'ютери теж постійно вдосконалюються. У результаті Qiskit може зазнавати змін, які іноді призводять до застарівання коду. З огляду на це, перед кожним прикладом коду Qiskit у цьому курсі ми виконуватимемо наведені нижче команди, щоб було зрозуміло, яка версія Qiskit використовувалася. Починаючи з Qiskit v1.0, це простий спосіб дізнатися поточну встановлену версію.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import __version__

print(__version__)
2.1.1

Якщо ти запускаєш це у хмарному середовищі Python, можливо, знадобиться встановити деякі з таких пакетів:

#!pip install qiskit
#!pip install jupyter
#!pip install sympy
#!pip install matplotlib
#!pip install pylatexenc

Вектори та матриці в Python

Qiskit використовує мову програмування Python, тому перш ніж говорити безпосередньо про Qiskit, корисно коротко розглянути операції з матрицями та векторами в Python.

У Python матричні та векторні обчислення можна виконувати за допомогою класу array з бібліотеки NumPy, яка надає функціональність для багатьох чисельних та наукових обчислень. Наступний код завантажує цю бібліотеку, визначає два стовпчикових вектори — ket0 та ket1, що відповідають векторам стану кубіта 0\vert 0\rangle та 1,\vert 1\rangle, — і виводить їх середнє значення.

import numpy as np

ket0 = np.array([[1], [0]])
ket1 = np.array([[0], [1]])

print(ket0 / 2 + ket1 / 2)
[[0.5]
[0.5]]

Клас array також можна використовувати для створення матриць, які представляють операції.

M1 = np.array([[1, 1], [0, 0]])
M2 = np.array([[1, 0], [0, 1]])
M = M1 / 2 + M2 / 2
print(M)
[[1.  0.5]
[0. 0.5]]

Зверни увагу: весь код у межах одного уроку цього курсу очікується виконувати послідовно. Тому тут не потрібно знову імпортувати NumPy, оскільки його вже було імпортовано.

Множення матриць, включно з множенням матриці на вектор як окремим випадком, можна виконувати за допомогою функції matmul з NumPy.

print(np.matmul(M1, ket1))
print(np.matmul(M1, M2))
print(np.matmul(M, M))
[[1]
[0]]
[[1 1]
[0 0]]
[[1. 0.75]
[0. 0.25]]

Цей формат виводу залишає бажати кращого з точки зору зручності сприйняття. Одне рішення — у ситуаціях, де потрібне більш гарне відображення, — це використати функцію array_to_latex з Qiskit, з модуля qiskit.visualization. Зауваж, що у наведеному нижче коді використовується стандартна функція Python display. На відміну від неї, поведінка print може залежати від того, що виводиться, наприклад, для масивів, визначених у NumPy.

from qiskit.visualization import array_to_latex

display(array_to_latex(np.matmul(M1, ket1)))
display(array_to_latex(np.matmul(M1, M2)))
display(array_to_latex(np.matmul(M, M)))
[10] \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} [1100] \begin{bmatrix} 1 & 1 \\ 0 & 0 \\ \end{bmatrix} [134014] \begin{bmatrix} 1 & \frac{3}{4} \\ 0 & \frac{1}{4} \\ \end{bmatrix}

Стани, вимірювання та операції

Qiskit містить кілька класів, що дозволяють створювати та маніпулювати станами, вимірюваннями та операціями — тому не потрібно програмувати з нуля все необхідне для симуляції квантових станів, вимірювань та операцій на Python. Нижче наведено кілька прикладів для початку роботи.

Визначення та відображення векторів стану

Клас Statevector у Qiskit надає функціональність для визначення та маніпулювання векторами квантового стану. У наведеному нижче коді клас Statevector імпортується та визначаються кілька векторів. (Також імпортується функція sqrt з бібліотеки NumPy для обчислення квадратного кореня. Цю функцію, як варіант, можна викликати як np.sqrt, якщо NumPy вже імпортовано, як це зроблено вище; це просто інший спосіб імпортувати та використовувати лише цю конкретну функцію.)

from qiskit.quantum_info import Statevector
from numpy import sqrt

u = Statevector([1 / sqrt(2), 1 / sqrt(2)])
v = Statevector([(1 + 2.0j) / 3, -2 / 3])
w = Statevector([1 / 3, 2 / 3])

Клас Statevector містить метод draw для відображення векторів стану різними способами, зокрема text для звичайного тексту, latex для відрендереного LaTeX та latex_source для коду LaTeX, що зручно для копіювання та вставки в документи. (Використовуй print замість display для відображення коду LaTeX — це дає найкращий результат.)

display(u.draw("text"))
display(u.draw("latex"))
print(u.draw("latex_source"))
[0.70710678+0.j,0.70710678+0.j]

220+221\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

Клас Statevector також містить метод is_valid, який перевіряє, чи є заданий вектор дійсним вектором квантового стану (тобто чи має він евклідову норму, що дорівнює 1):

display(u.is_valid())
display(w.is_valid())
True
False

Симуляція вимірювань за допомогою Statevector

Далі ми побачимо один зі способів симуляції вимірювань квантових станів у Qiskit за допомогою методу measure класу Statevector. Використаємо той самий вектор стану кубіта v, визначений раніше.

display(v.draw("latex"))

(13+2i3)0231(\frac{1}{3} + \frac{2 i}{3}) |0\rangle- \frac{2}{3} |1\rangle

Виклик методу measure симулює вимірювання у стандартному базисі. Він повертає результат цього вимірювання разом із новим вектором квантового стану системи після вимірювання. (Тут використовується функція print Python з префіксом f для форматованого виводу зі вбудованими виразами.)

outcome, state = v.measure()
print(f"Measured: {outcome}\nPost-measurement state:")
display(state.draw("latex"))
Measured: 1
Post-measurement state:

1- |1\rangle

Результати вимірювань є імовірнісними, тому цей метод може повертати різні результати при кожному запуску. Для конкретного прикладу вектора v, визначеного вище, метод measure визначає вектор квантового стану після вимірювання як

(1+2i5)0\biggl(\frac{1 + 2i}{\sqrt{5}}\biggr) \vert 0\rangle

(а не 0\vert 0\rangle) або

1- \vert 1\rangle

(а не 1\vert 1\rangle), залежно від результату вимірювання. В обох випадках альтернативи до 0\vert 0\rangle та 1\vert 1\rangle фактично еквівалентні цим векторам стану; кажуть, що вони еквівалентні з точністю до глобальної фази, оскільки один дорівнює іншому, помноженому на комплексне число на одиничному колі. Ця тема детальніше розглядається в уроці Квантові схеми і наразі її можна не брати до уваги.

Statevector видасть помилку, якщо метод measure застосовується до недійсного вектора квантового стану.

Statevector також має метод sample_counts, який дозволяє симулювати будь-яку кількість вимірювань системи, кожного разу починаючи зі свіжої копії стану. Наприклад, наведений нижче код показує результат вимірювання вектора v 10001000 разів, що (з великою ймовірністю) призводить до результату 00 приблизно 55 разів із кожних 99 (або близько 556556 з 10001000 спроб) і результату 11 приблизно 44 рази з кожних 99 (або близько 444444 з 10001000 спроб). Наступний код також демонструє функцію plot_histogram з модуля qiskit.visualization для візуалізації результатів.

from qiskit.visualization import plot_histogram

statistics = v.sample_counts(1000)
plot_histogram(statistics)

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

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

Виконання операцій за допомогою Operator та Statevector

Унітарні операції можна визначати в Qiskit за допомогою класу Operator, як показано в наступному прикладі. Цей клас містить метод draw з аналогічними аргументами до Statevector. Зауваж, що опція latex дає результати, еквівалентні array_from_latex.

from qiskit.quantum_info import Operator

Y = Operator([[0, -1.0j], [1.0j, 0]])
H = Operator([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]])
S = Operator([[1, 0], [0, 1.0j]])
T = Operator([[1, 0], [0, (1 + 1.0j) / sqrt(2)]])

display(T.draw("latex"))
[10022+2i2] \begin{bmatrix} 1 & 0 \\ 0 & \frac{\sqrt{2}}{2} + \frac{\sqrt{2} i}{2} \\ \end{bmatrix}

Унітарну операцію можна застосувати до вектора стану за допомогою методу evolve.

v = Statevector([1, 0])

v = v.evolve(H)
v = v.evolve(T)
v = v.evolve(H)
v = v.evolve(S)
v = v.evolve(Y)

display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Попередній перегляд квантових схем

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

from qiskit import QuantumCircuit

circuit = QuantumCircuit(1)

circuit.h(0)
circuit.t(0)
circuit.h(0)
circuit.s(0)
circuit.y(0)

display(circuit.draw(output="mpl"))

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

Тут використовується метод draw класу QuantumCircuit з рендерером mpl (скорочення від Matplotlib — бібліотеки візуалізації Python). Це єдиний рендерер, який ми використовуватимемо для квантових схем у цьому курсі, хоча є й інші варіанти, зокрема текстовий та рендерер на основі LaTeX.

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

display(Operator.from_circuit(circuit).draw("latex"))
[0.14644660940.3535533906i0.8535533906+0.3535533906i0.3535533906+0.8535533906i0.3535533906+0.1464466094i] \begin{bmatrix} 0.1464466094 - 0.3535533906 i & 0.8535533906 + 0.3535533906 i \\ -0.3535533906 + 0.8535533906 i & 0.3535533906 + 0.1464466094 i \\ \end{bmatrix}

Також можна ініціалізувати початковий вектор квантового стану і потім еволюціонувати цей стан відповідно до послідовності операцій, описаної схемою.

ket0 = Statevector([1, 0])
v = ket0.evolve(circuit)
display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Наведений нижче код симулює експеримент, де стан, отриманий зі схеми вище, вимірюється у стандартному базисі 4000 разів (щоразу використовуючи свіжу копію стану).

statistics = v.sample_counts(4000)
display(plot_histogram(statistics))

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