Реалізація в Qiskit
У цьому уроці ми реалізуємо деякі ідеї з уроку про заплутаність у дії, використовуючи Qiskit.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"qiskit": "qiskit", "qiskit_aer": "qiskit-aer"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit import __version__
print(__version__)
2.1.1
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, array_to_latex
from qiskit.result import marginal_distribution
from qiskit.circuit.library import UGate
from math import pi
import random
Ось реалізація протоколу квантової телепортації у вигляді квантової схеми.
qubit = QuantumRegister(1, "Q")
ebit0 = QuantumRegister(1, "A")
ebit1 = QuantumRegister(1, "B")
a = ClassicalRegister(1, "a")
b = ClassicalRegister(1, "b")
protocol = QuantumCircuit(qubit, ebit0, ebit1, a, b)
# Prepare ebit used for teleportation
protocol.h(ebit0)
protocol.cx(ebit0, ebit1)
protocol.barrier()
# Alice's operations
protocol.cx(qubit, ebit0)
protocol.h(qubit)
protocol.barrier()
# Alice measures and sends classical bits to Bob
protocol.measure(ebit0, a)
protocol.measure(qubit, b)
protocol.barrier()
# Bob uses the classical bits to conditionally apply gates
with protocol.if_test((a, 1)):
protocol.x(ebit1)
with protocol.if_test((b, 1)):
protocol.z(ebit1)
display(protocol.draw(output="mpl"))
У схемі використовується кілька можливостей Qiskit, яких ми ще не бачили в попередніх уроках: функції barrier та if_test.
Функція barrier створює візуальне розділення, що робить діаграму схеми більш читабельною, а також забороняє Qiskit виконувати різноманітні спрощення та оптимізації через бар'єр під час компіляції, коли схеми запускаються на реальному обладнанні.
Функція if_test застосовує операцію умовно — залежно від значення класичного біта або регістра.
Спочатку схема ініціалізує у стан (що не є частиною самого протоколу), потім виконуються операції Аліси, потім її вимірювання, і нарешті — операції Боба. Щоб перевірити правильність роботи протоколу, ми застосуємо випадково згенерований однокубітний вентиль до ініціалізованого стану кубіта , отримавши таким чином випадковий квантовий вектор стану для телепортації. Застосувавши інверсію (тобто спряжено-транспоновану матрицю) цього вентиля до після виконання протоколу, ми можемо перевірити, що стан було телепортовано, вимірявши та переконавшись, що він повернувся до стану .
Спочатку ми випадково оберемо унітарний вентиль для кубіта.
random_gate = UGate(
theta=random.random() * 2 * pi,
phi=random.random() * 2 * pi,
lam=random.random() * 2 * pi,
)
display(array_to_latex(random_gate.to_matrix()))
Тепер ми створимо нову тестову схему, яка спочатку застосовує наш випадковий вентиль до , потім запускає схему телепортації, і нарешті застосовує інверсію нашого випадкового вентиля до кубіта та виконує вимірювання. Результат повинен бути з повною певністю.
# Create a new circuit including the same bits and qubits used in the
# teleportation protocol.
test = QuantumCircuit(qubit, ebit0, ebit1, a, b)
# Start with the randomly selected gate on Q
test.append(random_gate, qubit)
test.barrier()
# Append the entire teleportation protocol from above.
test = test.compose(protocol)
test.barrier()
# Finally, apply the inverse of the random unitary to B and measure.
test.append(random_gate.inverse(), ebit1)
result = ClassicalRegister(1, "Result")
test.add_register(result)
test.measure(ebit1, result)
display(test.draw(output="mpl"))
Нарешті, запустимо симулятор Aer для цієї схеми та побудуємо гістограму результатів. Ми побачимо статистику для всіх трьох класичних бітів: нижній/лівий біт завжди повинен бути , що вказує на успішну телепортацію кубіта у , тоді як два інших біти мають бути приблизно рівномірними.
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))
Якщо хочеш, можна також відфільтрувати статистику, щоб зосередитися виключно на кубіті тестового результату, ось так:
filtered_statistics = marginal_distribution(statistics, [2])
display(plot_histogram(filtered_statistics))
Надщільне кодування
Надщільне кодування — це протокол, який, у певному сенсі, досягає мети, комплементарної до телепортації. Замість того щоб передавати один кубіт за допомогою двох класичних бітів зв'язку (ціною одного е-біта заплутаності), він дозволяє передавати два класичних біти за допомогою одного кубіта квантового зв'язку (знову ж таки, ціною одного е-біта заплутаності).
Детальніше: є відправник (Аліса) і одержувач (Боб), які мають спільний е-біт заплутаності. Відповідно до угод, прийнятих у цьому уроці, це означає, що Аліса тримає кубіт , Боб тримає кубіт , і разом пара перебуває у стані . Аліса хоче передати Бобу два класичних біти, які ми позначимо як і , і зробить це, надіславши йому один кубіт.
Можна вважати, що це досягнення менш вражаюче, ніж те, чого досягає телепортація. Надсилання кубітів, найімовірніше, буде значно складнішим, ніж надсилання класичних бітів у найближчому майбутньому, тому обмін одним кубітом квантового зв'язку на два класичних біти — та ще й ціною е-біта — навряд чи здається вартим того. Однак це не означає, що надщільне кодування нецікаве — навпаки, воно дуже навіть цікаве.
Відповідно до теми уроку, одна з причин, чому надщільне кодування цікаве, полягає в тому, що воно демонструє конкретне і (в контексті теорії інформації) досить разюче використання заплутаності. Відома теорема квантової теорії інформації — теорема Голєво — означає, що без спільного заплутаного стану неможливо передати більше одного біта класичної інформації, надіславши один кубіт. (Теорема Голєво є більш загальною за це. Її точне формулювання технічне й потребує пояснень, але це один із її наслідків.) Отже, завдяки надщільному кодуванню спільна заплутаність фактично дозволяє подвоїти пропускну здатність класичної інформації при надсиланні кубітів.
Протокол
Наступна діаграма квантової схеми описує протокол надщільного кодування:

Інакше кажучи, Аліса робить таке:
-
Якщо , Аліса застосовує вентиль до свого кубіта (а якщо — не застосовує).
-
Якщо , Аліса застосовує вентиль до свого кубіта (а якщо — не застосовує).
Після цього Аліса надсилає свій кубіт Бобу.
Коли Боб отримує кубіт , він спочатку застосовує вентиль CNOT (керований NOT), де є керуючим кубітом, а — цільовим, а потім застосовує вентиль Адамара до . Потім він вимірює , щоб отримати , і , щоб отримати , — в обох випадках за допомогою вимірювань у стандартному базисі.
Аналіз
Ідея цього протоколу проста: Аліса фактично обирає, в якому стані Белла вона хотіла б перебувати разом із Бобом, надсилає Бобу свій кубіт, і Боб вимірює, щоб визначити, який стан Белла обрала Аліса.
Тобто спочатку вони розділяють і залежно від бітів та Аліса або залишає цей стан незмінним, або переводить його в один із інших станів Белла, застосовуючи або до свого кубіта
Дії Боба мають такий вплив на чотири стани Белла:
Це можна перевірити безпосередньо, обчислюючи результати операцій Боба над цими станами по одному.
Отже, коли Боб виконує свої вимірювання, він може визначити, який стан Белла обрала Аліса. Перевірка правильності роботи протоколу зводиться до розгляду кожного випадку:
-
Якщо , то стан на момент отримання Бобом дорівнює . Він перетворює цей стан на і отримує .
-
Якщо , то стан на момент отримання Бобом дорівнює . Він перетворює цей стан на і отримує .
-
Якщо , то стан на момент отримання Бобом дорівнює . Він перетворює цей стан на і отримує .
-
Якщо , то стан на момент отримання Бобом дорівнює . Він перетворює цей стан на і отримує . (Фазовий множник мінус один тут жодного впливу не має.)
Реалізація надщільного кодування
Ось проста реалізація надщільного кодування, де ми задаємо схему залежно від бітів, які потрібно передати. Спочатку оберемо два біти для передачі. (Пізніше ми оберемо їх випадково, але поки що зробимо довільний вибір.)
c = "1"
d = "0"
Тепер побудуємо схему відповідно. Тут ми дозволимо Qiskit використовувати стандартні назви для кубітів: для верхнього кубіта та для нижнього.
protocol = QuantumCircuit(2)
# Prepare ebit used for superdense coding
protocol.h(0)
protocol.cx(0, 1)
protocol.barrier()
# Alice's operations
if d == "1":
protocol.z(0)
if c == "1":
protocol.x(0)
protocol.barrier()
# Bob's actions
protocol.cx(0, 1)
protocol.h(0)
protocol.measure_all()
display(protocol.draw(output="mpl"))
Тут мало що нового, крім функції measure_all, яка вимірює всі кубіти і поміщає результати в єдиний класичний регістр (тому в цьому випадку маємо два біти).
Запуск симулятора Aer дає очікуваний результат.
result = AerSimulator().run(protocol).result()
statistics = result.get_counts()
for outcome, frequency in statistics.items():
print(f"Measured {outcome} with frequency {frequency}")
display(plot_histogram(statistics))
Measured 10 with frequency 1024
Тепер скористаємося додатковим кубітом як генератором випадкових бітів — по суті, щоб підкидати чесну монету. Використаємо його для випадкового вибору і , а потім запустимо протокол надщільного кодування.
rbg = QuantumRegister(1, "coin")
ebit0 = QuantumRegister(1, "A")
ebit1 = QuantumRegister(1, "B")
Alice_c = ClassicalRegister(1, "Alice c")
Alice_d = ClassicalRegister(1, "Alice d")
test = QuantumCircuit(rbg, ebit0, ebit1, Alice_d, Alice_c)
# Initialize the ebit
test.h(ebit0)
test.cx(ebit0, ebit1)
test.barrier()
# Use the 'coin' qubit twice to generate Alice's bits c and d.
test.h(rbg)
test.measure(rbg, Alice_c)
test.h(rbg)
test.measure(rbg, Alice_d)
test.barrier()
# Now the protocol runs, starting with Alice's actions, which depend
# on her bits.
with test.if_test((Alice_d, 1), label="Z"):
test.z(ebit0)
with test.if_test((Alice_c, 1), label="X"):
test.x(ebit0)
test.barrier()
# Bob's actions
test.cx(ebit0, ebit1)
test.h(ebit0)
test.barrier()
Bob_c = ClassicalRegister(1, "Bob c")
Bob_d = ClassicalRegister(1, "Bob d")
test.add_register(Bob_d)
test.add_register(Bob_c)
test.measure(ebit0, Bob_d)
test.measure(ebit1, Bob_c)
display(test.draw(output="mpl"))
Запуск симулятора Aer показує результати: класичні біти Аліси та Боба завжди збігаються.
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))
Реалізація в Qiskit
Гру CHSH разом із описаною вище квантовою стратегією можна реалізувати в Qiskit таким чином.
По-перше, ось визначення самої гри, яке приймає довільну стратегію як аргумент.
def chsh_game(strategy):
# This function runs the CHSH game, using the strategy (a function
# from two bits to two bits), returning 1 for a win and 0 for a loss.
# Choose x and y randomly
x, y = random.randint(0, 1), random.randint(0, 1)
# Use the strategy to determine a and b
a, b = strategy(x, y)
# Decide if the strategy wins or loses
if (a != b) == (x & y):
return 1 # Win
return 0 # Lose
Тепер створимо функцію, що генерує схему залежно від питань для Аліси та Боба. Для простоти залишимо кубітам стандартні імена, а для дій Аліси та Боба використаємо вбудований вентиль .
def chsh_circuit(x, y):
# This function creates a `QuantumCircuit` implementing the quantum
# strategy described above (including the e-bit preparation).
qc = QuantumCircuit(2, 2)
# Prepare an e-bit
qc.h(0)
qc.cx(0, 1)
qc.barrier()
# Alice's actions
if x == 0:
qc.ry(0, 0)
else:
qc.ry(-pi / 2, 0)
qc.measure(0, 0)
# Bob's actions
if y == 0:
qc.ry(-pi / 4, 1)
else:
qc.ry(pi / 4, 1)
qc.measure(1, 1)
return qc
Ось чотири можливі схеми залежно від того, які питання ставляться.
# Draw the four possible circuits
print("(x,y) = (0,0)")
display(chsh_circuit(0, 0).draw(output="mpl"))
print("(x,y) = (0,1)")
display(chsh_circuit(0, 1).draw(output="mpl"))
print("(x,y) = (1,0)")
display(chsh_circuit(1, 0).draw(output="mpl"))
print("(x,y) = (1,1)")
display(chsh_circuit(1, 1).draw(output="mpl"))
(x,y) = (0,0)
(x,y) = (0,1)
(x,y) = (1,0)
(x,y) = (1,1)
Тепер створимо завдання за допомогою симулятора Aer, що запускає схему один раз для заданої пари вхідних значень .
def quantum_strategy(x, y):
# This function runs the appropriate quantum circuit defined above
# one time and returns the measurement results
# Setting `shots=1` to run the circuit once
result = AerSimulator().run(chsh_circuit(x, y), shots=1).result()
statistics = result.get_counts()
# Determine the output bits and return them
bits = list(statistics.keys())[0]
a, b = bits[0], bits[1]
return a, b
Нарешті, зіграємо в гру 1000 разів і обчислимо частку виграшів стратегії.
NUM_GAMES = 1000
TOTAL_SCORE = 0
for _ in range(NUM_GAMES):
TOTAL_SCORE += chsh_game(quantum_strategy)
print("Fraction of games won:", TOTAL_SCORE / NUM_GAMES)
Fraction of games won: 0.867
Також можна визначити класичну стратегію й перевірити, наскільки добре вона працює. Це лише одна стратегія — інші можна перевірити, змінивши код, — але вона належить до оптимальних класичних стратегій.
def classical_strategy(x, y):
# This function implements just one example of an optimal classical
# strategy for the CHSH game. Other classical strategies can be
# implemented by changing the bit values assigned to a and b.
# Alice's answer
if x == 0:
a = 0
elif x == 1:
a = 1
# Bob's answer
if y == 0:
b = 1
elif y == 1:
b = 0
return a, b
Знову зіграємо в гру 1000 разів, щоб побачити, наскільки вона ефективна.
NUM_GAMES = 1000
TOTAL_SCORE = 0
for _ in range(NUM_GAMES):
TOTAL_SCORE += chsh_game(classical_strategy)
print("Fraction of games won:", TOTAL_SCORE / NUM_GAMES)
Fraction of games won: 0.747
Хоча тут задіяна випадковість, після 1000 запусків статистика навряд чи сильно відхилиться. Квантова стратегія виграє приблизно у 85% випадків, тоді як класична не може вигравати більш ніж приблизно у 75%.