Введение в Qiskit. Применение квантовых операций на одном и множестве кубитов. Построение квантовой схемы. Измерение кубита. Визуализация полученных значений.
Для работы используется Qiskit внутри https://quantum-computing.ibm.com/
В квантовой схеме мы даём что-то на вход, делаем вычисления и извлекаем результат (measurement).
QuantumCircuit в Qiskit создаётся с указанием количества кубитов и классических битов. Когда происходит измерение, то квантовый бит записывает результат в классический бит.
from qiskit import QuantumCircuit, assemble, Aer
from qiskit.visualization import plot_histogram
n = 8
n_q = n
n_b = n
qc_output = QuantumCircuit(n_q,n_b)
for j in range(n):
qc_output.measure(j,j)
Для визуализации квантовой схемы:
qc_output.draw()
Кубиты всегда инициализируются 0, т.к. над ними не производится никаких операций, то результат будет 0.
# симулятор из Qiskit
sim = Aer.get_backend('qasm_simulator')
# https://qiskit.org/documentation/stubs/qiskit.compiler.assemble.html сериализирует payload, принимает на вход квантовую схему, возвращает Qobj, который может быть запущен на любом бэкенде (симулятор или системе)
qobj = assemble(qc_output)
counts = sim.run(qobj).result().get_counts()
plot_histogram(counts)
Квантовые компьютеры не возвращают точные данные, они возвращают вероятность (когда мы измеряем кубиты). В данном случае нет никаких квантовых операций, поэтому результат всегда будет одинаковый. Более того qasm_simulator сам по себе не содержит шумов, поэтому при повторных запусках результат отличаться не будет.
NOT gate - меняет кубит на обратное значение (0 - 1, 1 - 0). В Qiskit - x, допустим, для применения на конкретный кубит, можно использовать как в примере ниже
qc_encode = QuantumCircuit(n)
qc_encode.x(7)
qc_encode.draw()
Квантовые схемы можно соединять:
qc = qc_encode + qc_output
qc.draw()
Теперь выводится 10000000. Это потому, что на 7 кубит был применён NOT гейт. В Qiskit справа налево, поэтому NOT на 7 кубит превратил 00000000 в 10000000. Такой подход у них выбран потому это удобно когда биты используются для представления чисел, т.е. кубит 7 говорит сколько 2^7 в числе, т.е. 128 в 8 битном компьютере.
Попробуйте, закодировать какое-нибудь число, допустим, свой возраст. Не используемые биты слева оставьте 0.
qc_encode = QuantumCircuit(n)
qc_encode.x(0)
qc_encode.x(1)
qc_encode.x(4)
qc_encode.draw()
Для многих задач также полезен XOR gate
A | B | XOR |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Этот гейт полезен для определяния правого бита при сложении. Допустим, 1+1 = 10, 0 - как раз то, что вернёт и XOR.
В квантовых компьютерах XOR выполняется при помощи controlled-NOT gate. Сокращенно CNOT, в Qiskit - cx. Он используется на 2-х кубитах. Первый кубит - это управляющий кубит, он не меняется. А во на второй применяется XOR и он изменяется.
qc_cnot = QuantumCircuit(2)
qc_cnot.cx(0,1)
qc_cnot.draw()
Его названия связано с тем, что поведение можно объяснить как: на целевой кубит применяется NOT если управляющий 1 и ничего не делает в остальных случаях.
Чему будет равен ответ?
qc = QuantumCircuit(2,2)
qc.x(0)
qc.cx(0,1)
qc.measure(0,0)
qc.measure(1,1)
qc.draw()
Попробуйте выполнить на симуляторе и проверить.
Результат получился 11. На 1-ый кубит применяется NOT, который делает его 1. Затем на 2-ой кубит применяется CNOT (1 XOR 0 = 1), соответственно 2-ой кубит становится 1. Результат 11.
Давайте подумаем над задачей, мы хотим сделать алгоритм, который складывает 2 кубита. Т.е.
A | B | SUM |
---|---|---|
0 | 0 | 00 |
0 | 1 | 01 |
1 | 0 | 01 |
1 | 1 | 10 |
# результат вычисления мы хотим положить в другие кубиты, поэтому 2 кубита - для входа, 2 будем использовать в вычислениях, итого 4. И для результата понадобятся только 2 классических бита
qc_ha = QuantumCircuit(4,2)
# кодируем входящие кубиты
qc_ha.x(0) # Закомментируйте, чтобы A = 0
qc_ha.x(1) # Закомментируйте, чтобы B = 0
qc_ha.barrier() # для визуального более удобного разделения этапов на схеме
# CNOTs для кубита 2. Кубит 2 - это то, что будет записано в конце (т.е. второй классический бит). Мы можем использовать 2 CNOT гейта на 0 и 2, а затем на 1 и 2 кубиты, что вычислить, чему будет равен кубит 2
qc_ha.cx(0,2)
qc_ha.cx(1,2)
qc_ha.barrier()
# вычисляем
qc_ha.measure(2,0)
qc_ha.measure(3,1)
qc_ha.draw()
С кубитом 3 пока ничего не делали (с 1-ым классическим битом). Мы можем увидеть, что он равен 1 только в том случае, когда A и B - 1. И, конечно, есть такой гейт :)
Который смотрит на 2 управляющих кубита, а не на 1. Это гейт Тоффоли (Toffoli). Он применяет NOT на целевой кубито только когда оба управляющих 1. В Qiskit это ccx. В булевой логике это аналог AND.
# результат вычисления мы хотим положить в другие кубиты, поэтому 2 кубита - для входа, 2 будем использовать в вычислениях, итого 4. И для результата понадобятся только 2 классических бита
qc_ha = QuantumCircuit(4,2)
# кодируем входящие кубиты
qc_ha.x(0) # Закомментируйте, чтобы A = 0
qc_ha.x(1) # Закомментируйте, чтобы B = 0
qc_ha.barrier() # для визуального более удобного разделения этапов на схеме
# CNOTs для кубита 2. Кубит 2 - это то, что будет записано в конце (т.е. второй классический бит). Мы можем использовать 2 CNOT гейта на 0 и 2, а затем на 1 и 2 кубиты, что вычислить, чему будет равен кубит 2
qc_ha.cx(0,2)
qc_ha.cx(1,2)
# Toffoli, чтобы вычислить 3-ий кубит (1-ый классический бит)
qc_ha.ccx(0,1,3)
qc_ha.barrier()
# вычисляем
qc_ha.measure(2,0)
qc_ha.measure(3,1)
qc_ha.draw()
Это самые простейщие элементы, которые постоянно используются в квантовых вычислениях.
В квантовой физике используются векторы состояния. Допустим, для стандартного вектора это 0, 1, 2, 3 и т.д. Мы можем сказать, что какая-то точка находится в 3. Вектор состояния это 0 0 0 1 0 0 0, т.е. обозначить вероятность, с которой мы можем найти эту точку в данной позиции.
В отличии от бита, который может принимать 0 или 1. Кубит принимает это значение только во время измерения. В другое время его значение - это нечто более сложное, чем просто бинарное значение. |0> = [1 0], |1> = [0 1]
from qiskit import QuantumCircuit, assemble, Aer
from qiskit.visualization import plot_histogram, plot_bloch_vector
from math import sqrt, pi
qc = QuantumCircuit(1) # Создание квантовой схемы с одним кубитом
initial_state = [0,1] # Определяем начальное состояние как |1>
qc.initialize(initial_state, 0) # Применяем к 0 кубиту
qc.draw() # Рисуем схему
svsim = Aer.get_backend('statevector_simulator') # Берём симулятор
qobj = assemble(qc) # Создаём Qobj
result = svsim.run(qobj).result() # Симулируем и возвращаем результат симуляции
out_state = result.get_statevector()
print(out_state) # Отобразим вектор состояния
Python j - это i для обозначения комплексных чисел. Т.е. тут это [0.+0.j 1.+0.j], где 0.+0.j - 0, 1.+0.j - 1
Измерим кубиты:
qc.measure_all()
qc.draw()
Получим вероятность, а не вектор состояния и нарисуем:
counts = result.get_counts()
plot_histogram(counts)