Основы Cirq

Стереотипные программисты обычно не любят мышь, поэтому и для программирования квантовых компьютеров есть не только drag-n-drop интерфейс построения схем, но и специализированные языки программирования (Quipper, Silq, Q# и т.д.), и библиотеки для языков программирования общего назначения (Qiskit, PennyLane, Strawberry Fields).

В нашем курсе мы будем пользоваться библиотекой Cirq от Google для языка программирования Python.

Установка и запуск

Библиотека требует Python версии 3.7 и выше. Установите его для своей ОС: Linux, Mac OS X, Windows.

Желательно использовать виртуальное окружение, чтобы не засорять глобальный набор пакетов.

Установите cirq при помощи pip:

python -m pip install cirq

Проверить, что всё работает, можно при помощи следующей команды:

python -c 'import cirq_google; print(cirq_google.Sycamore)'

Первая программа

Для описания схем нужны кубиты и гейты, которые выполняют над кубитами операции.

import cirq

# Создаём кубит с именем q
qubit = cirq.NamedQubit('q')

# Схема применяет гейт Адамара и измеряет результат в m
circuit = cirq.Circuit(cirq.H(qubit), cirq.measure(qubit, key='m'))
print("Circuit:")
print(circuit)

# Эмулируем работу схемы несколько раз
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=20)
print("Results:")
print(result)
Circuit:
q: ───H───M('m')───
Results:
m=00110001000101101010

Запуск и моделирование

Библиотека «заточена» под работу на реальных квантовых устройствах, поэтому результаты запуска будут возвращать те значения, которые выдал бы идеальный квантовый компьютер (работающий без шумов и ошибок). Функции, в названии которых есть run, обычно имеют именно такое поведение. В этом случае легко тестировать программы, используя классический компьютер, а затем запуская для работы уже на квантовом компьютере (доступном, например, в облаке).

Также доступна и моделирование работы компьютера, которое вычисляет вектор состояния. В названии функций для моделирования обычно есть слово simulate.

Запуск схемы вы уже видели в прошлом разделе, поэтому посмотрим на код для моделирования создания состояния Белла \(1/\sqrt{2} * (|00\rangle + |11\rangle)\):

import cirq

bell_circuit = cirq.Circuit()
q0, q1 = cirq.LineQubit.range(2)
bell_circuit.append(cirq.H(q0))
bell_circuit.append(cirq.CNOT(q0, q1))

print('Circuit:')
print(bell_circuit)

# Initialize Simulator
s = cirq.Simulator()

print('Simulate the circuit:')
results = s.simulate(bell_circuit)
print(results)
Circuit:
0: ───H───@───
          │
1: ───────X───
Simulate the circuit:
measurements: (no measurements)

qubits: (cirq.LineQubit(0), cirq.LineQubit(1))
output vector: 0.707|00⟩ + 0.707|11⟩

phase:
output vector: |⟩

Кубиты и гейты, устройства и моменты

Кубиты

# Для отдельных кубитов можно задать имена. Это полезно, если ваш алгоритм
# абстрактный или не оптимизирован под конкретную реализацию квантового
# компьютера (где могут быть ограничения на кубиты, к которым применяются
# операции)

q0 = cirq.NamedQubit('source')
q1 = cirq.NamedQubit('target')

print(q0,q1)

# Можно создать кубиты в цепочке, задав для каждого номер или интервал
q3 = cirq.LineQubit(3)

# здесь создаются кубиты LineQubit(0), LineQubit(1), LineQubit(2)
q0, q1, q2 = cirq.LineQubit.range(3)

# Можно создать кубиты в сетке, по отдельности
q4_5 = cirq.GridQubit(4, 5)

# или все сразу. Код создаёт 16 кубитов от (0,0) до (3,3)
qubits = cirq.GridQubit.square(4)

Устройства

В библиотеке есть определения имеющихся квантовых устройств, например, компьютера Sycamore от Google. Линии на схеме задают пары кубитов, между которыми возможны двухкубитовые операции.

import cirq_google
print(cirq_google.Sycamore)
                                             (0, 5)───(0, 6)
                                             │        │
                                             │        │
                                    (1, 4)───(1, 5)───(1, 6)───(1, 7)
                                    │        │        │        │
                                    │        │        │        │
                           (2, 3)───(2, 4)───(2, 5)───(2, 6)───(2, 7)───(2, 8)
                           │        │        │        │        │        │
                           │        │        │        │        │        │
                  (3, 2)───(3, 3)───(3, 4)───(3, 5)───(3, 6)───(3, 7)───(3, 8)───(3, 9)
                  │        │        │        │        │        │        │        │
                  │        │        │        │        │        │        │        │
         (4, 1)───(4, 2)───(4, 3)───(4, 4)───(4, 5)───(4, 6)───(4, 7)───(4, 8)───(4, 9)
         │        │        │        │        │        │        │        │
         │        │        │        │        │        │        │        │
(5, 0)───(5, 1)───(5, 2)───(5, 3)───(5, 4)───(5, 5)───(5, 6)───(5, 7)───(5, 8)
         │        │        │        │        │        │        │
         │        │        │        │        │        │        │
         (6, 1)───(6, 2)───(6, 3)───(6, 4)───(6, 5)───(6, 6)───(6, 7)
                  │        │        │        │        │
                  │        │        │        │        │
                  (7, 2)───(7, 3)───(7, 4)───(7, 5)───(7, 6)
                           │        │        │
                           │        │        │
                           (8, 3)───(8, 4)───(8, 5)
                                    │
                                    │
                                    (9, 4)

Гейты

Для большинства «стандартных» гейтов в библиотеке есть соответствующие объекты, например, cirq.H для гейта Адамара. Гейт, применённый к каким-либо кубитам, в cirq называется операцией (например, cirq.H(q0) — это гейт Адамара, применённый к кубиту q0).

Схемы задаются при помощи класса Circuit, к которому можно добавлять гейты, используя метод append:

circuit = cirq.Circuit()
qubits = cirq.LineQubit.range(3)
circuit.append(cirq.H(qubits[0]))
circuit.append(cirq.H(qubits[1]))
circuit.append(cirq.H(qubits[2]))
print(circuit)
0: ───H───

1: ───H───

2: ───H───

В append можно передавать операции по отдельности или итерируемые объекты с операциями:

circuit2 = cirq.Circuit()
ops = [cirq.H(q) for q in cirq.LineQubit.range(3)]
circuit2.append(ops)
print(circuit2)
0: ───H───

1: ───H───

2: ───H───

Моменты

Схемы состоят из моментов Moment — наборов операций, которые можно выполнять параллельно. Обычно cirq пытается использовать самый ранний момент для применения операции.

При этом если кубит уже использовался в более ранней операции, то в схеме создаётся новый момент.

print(cirq.Circuit(cirq.SWAP(q, q + 1) for q in cirq.LineQubit.range(3)))
0: ───×───────────
      │
1: ───×───×───────
          │
2: ───────×───×───
              │
3: ───────────×───