Введение
Язык OpenQASM 2.0 (далее просто QASM) служит для описания квантовых алгоритмов в императивном стиле. Впервые язык был описан в статье arxiv и на данный момент поддерживается в различных фреймворках для квантовых вычислений.
Любая программа на QASM начинается со строки OPENQASM 2.0; которая декларирует используемую версию языка, в нашем случае версия 2.0.
Далее следует объявление квантовых и классических переменных (квантовых и классических регистров) которые будут использоваться как ячейки хранения информации.
После объявления переменных следует тело программы, в котором к квантовым переменным применяются 1) квантовые операции и 2) квантовые измерения с последующей записью результата измерения в классическую переменную.
По завершению программы результатом ее работы являются данные записанные в классические переменные. Состояния квантовых переменных не доступны для пользователя в силу фундаментальных принципов квантовой механики. Извлечь информацию из квантовых переменных можно только средствами квантовых измерений. Все объявленные классические переменные инициализируются нулем, все объявленные квантовые переменные инициализируются состоянием |0>.

Если на этапе прочтения введения нет понимания того, что значит инициализировать квантовую переменную, это не страшно, т.к. в дальнейших секциях мы подробно это изучим.

Время жизни всех квантовых и классических переменных совпадает со временем жизни программы, т. е. ресурсы выделяются под переменные в самом начале работы программы и освобождаются только по ее завершению. Язык QASM позволяет определять новые гейты путем использования специального синтаксиса. Наиболее близкий аналог такого определения это определение процедуры или функции без возвращаемого значение в любом "классическом" языке программирования.


Стоит заметить, однако, что возможности «процедур» в языке QASM сильно ограничены по сравнению с его «классическими» аналогами. К этому вопросы мы вернемся подробно в следующих секциях. Язык QASM поддерживает возможность подключения модулей путем конкатенации исходного кода модуля с кодом программы. Код модуля может содержать только объявления гейтов.

Теперь перейдем от общего обзора языка к подробному разбору всех его элементов.
Объявление и инициализация квантовых
и классических регистров

Любой квантовый алгоритм, как и классический, всегда работает с данными, которые необходимо хранить в процессе их обработки. В отличии от классического алгоритма, квантовый алгоритм может оперировать двумя типами данных: квантовыми и классическими данными.
Для хранения обоих типов данных необходимо выделять ресурсы, память, в которой эти данные будут находится. Единицу выделенной памяти в дальнейшем мы будем называть регистр либо переменная.

Начнем с описания процедуры выделения ресурсов под классические данные, которая производиться следующей строкой кода на QASM:

creg name[size];
Здесь creg — ключевое слово, которое говорит о нашем намерении объявить классический регистр (переменную, ячейку памяти), name — име данного регистра, которое будет использоваться в последующих строках программы для идентификации данного регистра, size — размер регистра (колличество бит выделенной памяти).
Например: creg bar[3]; будет объявлять регистр с именем bar размер которого 3 бита, т.е. он может находиться в одном из восьми состояний 000 , 100 , 010 , 110 , 001 , 101 , 011 , 111 .
Сразу после выделения памяти под классический регистр, его состояние инициализируется всеми нулями, например 000 .

Прежде чем перейти к вопросу о том, как выделить ресурсы под квантовые данные, стоит обсудить вопрос о том, что вообще означает хранить квантовые данные. Для хранения квантовых данных необходим квантовый объект имеющий состояние, это состояние и есть данные. Квантовым объектом с самой низкой информационной емкостью является кубит, аналог классического бита.
Состояние одного кубита проще всего представить себе следующим образом:
представим себе трехмерную сферу радиуса 1, любая точка на поверхности данной сферы может быть отождествлена с состоянием кубита. Северный полюс данной сферы соответствует состоянию |0>, южный соответствует состоянию |1>, все остальные точки соответствуют суперпозиционным состояниям. Точка также может находиться и внутри данной сферы, которая называется сферой Блоха, но данный случай сильно выходит за рамки нашего обсуждения. К сожалению, в общем случае, 2 и более кубитов нельзя представлять себе как две и более сфер Блоха.
Причина этого -- явление квантовой запутанности, которое приводит к взрывному росту сложности описания квантовой системы из нескольких кубитов при добавлении новых кубитов в систему. Вместе с тем, квантовая запутанность отвечает и за выдающуюся производительность квантового компьютера.
Сохранить умозрительную картину со сферами Блоха можно только в случае, если кубиты не запутанны друг с другом, т. е. систему из n не запутанных кубитов можно представить себе как n сфер Блоха.

Теперь перейдем к вопросу о выделении ресурсов для хранения квантовых данных, это делается при помощи следующей строки кода:

qreg name[size];
Как и до этого, здесь qreg — ключевое слово, которое говорит о нашем намерении объявить квантовый регистр (квантовую переменную, систему из некоторого кол-во кубитов), name — име данного регистра, которое будет использоваться в последующих строках программы для идентификации данного регистра, size — размер регистра (количество кубитов в регистре). Например: qreg bar[3]; будет объявлять квантовый регистр с именем bar состоящий из трех кубитов.
Сразу после выделения памяти под квантовый регистр, его состояние инициализируется всеми нулями, например |000>, что означает что кубиты, из которых состоит квантовый регистр, не запутанны и состояние каждого кубита соответствует северному полюсу сферы Блоха.
Встроенные операции над квантовыми регистрами

Квантовые и классические регистры не представляют никакого интереса до тех пор, пока над ними нет возможности совершать вычислительные операции.
Стандарт языка QASM предоставляет всего две встроенные операции над квантовыми регистрами. Первая из них, это вращение сферы блоха одного кубита на произвольный угол вокруг произвольной оси.

Такое вращение можно выполнить при помощи следующей строки кода:

U(theta, phi, lambda) name[n];
Здесь U — это идентификатор операции, совершающей поворот; theta, phi, lambda углы, задающие вращение, а именно, последовательность из трех вращений: поворот вокруг оси z на угол lambda , поворот вокруг оси y на угол theta , поворот вокруг оси z на угол phi , именно в таком порядке. Рекомендуем найти дополнительную информацию про углы Эйлера в интернете, будет достаточно ознакомиться со статьей на Wikipedia по данной теме, чтоб убедиться, что выше описанный метод очень удобен для реализации произвольного поворота. Заметим, что на месте углов могут стоять произвольные арифметические выражения включающие десятичные дробные числа, константу π, функции sin, cos, tan, exp, ln, sqrt; name — имя квантового регистра, к кубиту которого применяется вращение; n — номер кубита в регистре, к которому применяется вращение.
Заметим, что нумерация кубитов в регистре стартует с нуля, т.е. самый первый кубит регистра bar будет обозначаться как bar[0] .

Рассмотрим пример:

U(pi / 4 + ln(pi), 0, 0) bar[1];
Означает поворот сферы Блоха второго кубита из регистра с именем bar на угол
π / 4 + ln(π)
вокруг оси z. Вторая встроенная операция -- это так называемый гейт CX (операция контролируемое не). Это операция которая задействует сразу два кубита.

Рассмотрим пример:

CX bar[0], baz[2];
Данная строка кода выполняет операцию CX на первом кубите регистра bar и на третьем кубите регистра baz . Заметим, что операндами операции CX могут быть и кубиты из одного регистра. Говорят, что кубит bar[0] является контролирующим, а кубит baz[2] -- контролируемым. Причина такой терминологии заключается в том, что в зависимости от состояния кубита bar[0] к кубиту baz[2] может быть применено преобразование логическое "не" (поворот сферы Блоха на 180 градусов вокруг оси x). Немного конкретнее, если состояние кубит bar[0] лежит на северном полюсе сферы Блоха, то операция "не" к кубиту baz[2] не применяется. Если же состояние кубита bar[0] лежит на южном полюсе сферы блоха, то к кубиту baz[2] применяется операция "не" (его сфера Блоха переворачивается). Данное объяснение является достаточно поверхностным, для более глубокого понимания сути гейта CX мы рекомендуем обратиться к внешним источникам информации (ссылки?). На этом набор встроенных операций над квантовыми регистрами в языке QASM исчерпан. Заметим, однако, что данного набора достаточно для проведения произвольного квантового вычисления.
Квантовые измерения

Из-за фундаментальных ограничений квантовой механики не существует способа напрямую "измерять" состояние квантовой системы. Единственным способом извлечения информации из квантовой системы являются квантовые измерения. Результат квантового измерения — один бит классической информации. Для его записи можно использовать биты классических регистров. Язык QASM снабжен специальной инструкцией measure для описания квантового измерения.

Рассмотрим пример применения данной инструкции:

measure qbar[2] -> rbar[1];
Данная строка когда означает проведения квантового измерения над третьим кубитом квантового регистра qbar и занесение результата измерения во второй бит классического регистра rbar .
Определение новых операций

Одной из наибольших зол в программировании является дублирование кода. Одним из инструментов борьбы с дублированием кода являются функции, которые имеются практически в любом языке программирования. Данный инструмент позволяет описать некоторую подпрограмму, для которой требуется многократное использование, всего один раз и вызывать ее в произвольных местах кода столько раз, сколько это потребуется. В языке QASM тоже имеется такая конструкция, хотя и в значительно упрощенном виде. А именно, QASM позволяет определять пользовательские гейты по средствам следующего синтаксиса

gate name(params) qargs
{
body
}
Здесь ключевое слово gate сообщает о намерении определить новый пользовательский гейт; name -- имя, которое пользователь дает своему гейту, params -- параметры гейта перечисленные через запятую (например углы Эйлера). Заметим, что (params) можно опустить, если определяемый гейт вообще не берет на вход числовые параметры; "qargs" -- перечисление кубитов через запятую, к которым будет применяться гейт; фигурные скобки обрамляют фрагмент кода описывающего гейт; body -- описание (тело) гейта.

Рассмотрим пример:

gate cu1(lambda) a,b
{
U(0,0,lambda/2) a;
CX a,b;
U(0,0,-lambda/2) b;
CX a,b;
U(0,0,lambda/2) b;
}
Здесь мы определили новый гейт cu1 который является контролируемым вращением сферы Блоха вокруг оси z. Данный гейт берет на вход только один параметр lambda и действует на 2 кубита. Тело гейта представляет из себя чередующиеся применения гейта U и CX . Заметим, что такое определение ничего не возвращает по выполнению, по этому такое определение является скорее аналогом процедуры, а не функции. Заметим ряд других отличий определения гейта от функции: 1) в теле гейта нельзя выделять ресурсы под новые регистры, т.е., в терминах вызова функции, запрещено использование локальных параметров; 2) запрещены условные выполнения гейтов и рекурсивные вызовы; 3) запрещены измерения и сбросы состояния кубитов. Заметим, однако, что в теле гейта могут быть использованы пользовательские гейты, которые были определены ранее.

Рассмотрим пример:

gate ccx a,b,c
{
h c;
cx b,c;
tdg c;
cx a,c;
t c;
cx b,c;
tdg c;
cx a,c;
t b;
t c;
h c;
cx a,b;
t a;
tdg b;
cx a,b;
}

Здесь приведено определение гейта Тоффоли (ccx) -- трехкубитного квантового гейта, который часто встречается в различных квантовых алгоритмах. Данное определение взято из библиотеки стандартных гейтов языка QASM и оно использует некоторые другие гейты, которые ранее определены в библиотеке, а именно гейты tdg , t , h . Заметим также, что т.к. данный гейт пе берет числовые параметры на вход, в его определении после имени ccx отсутствует (params) .
Синтаксический сахар

Для сокращения длины и улучшения читаемости кода в языках программирования часто применяют так называемый "синтаксический сахар". Синтаксический сахар, это такие языковые конструкции, которые позволяют лаконично выразить часто используемые логические конструкции. В случае квантовых программ, зачастую приходится применять одинаковый гейт к целому набору кубитов. Обычно это приводит к длинным однотипным фрагментам кода. Приведем пример. Представим, что нам нужно применить гейт U(1., 1., 1.) ко всем кубитам некоторого квантового регистра bar :

qreg bar[8];
U(1., 1., 1.) bar[0];
U(1., 1., 1.) bar[1];
U(1., 1., 1.) bar[2];
U(1., 1., 1.) bar[3];
U(1., 1., 1.) bar[4];
U(1., 1., 1.) bar[5];
U(1., 1., 1.) bar[6];
U(1., 1., 1.) bar[7];
Подобный код выглядит излишне "задублированным". Действительно, строка U(1., 1., 1.,) входит в него целых 8 раз! На этом моменте хочется изобрести синтаксический сахар, который сократит данный код. Такой синтаксический сахар есть в языке QASM. Заключается он в том, что гейт мы можем применить не к одному кубиту, а к целому квантовому регистру. Упростим фрагмент кода представленный выше при помощи данного синтаксического сахара:

qreg bar[8];
U(1., 1., 1.) bar;
Здесь мы совершили полностью идентичное действие: применили гейт U(1., 1., 1.) ко всем кубитам квантового регистра bar .

Такой синтаксический сахар применим и к двухкубитным гейтам в следующем смысле. Пусть у нас есть два одинаковых квантовых регистра bar и baz в каждом из которых есть по 8 кубитов. Предположим, что мы хотим применить гейт CX к парам кубитов из регистров bar и baz с одинаковыми номерами, т.е.

qreg bar[8];
qreg baz[8];
CX bar[0], baz[0];
CX bar[1], baz[1];
CX bar[2], baz[2];
CX bar[3], baz[3];
CX bar[4], baz[4];
CX bar[5], baz[5];
CX bar[6], baz[6];
CX bar[7], baz[7];
С использованием синтаксического сахара данное действие можно выразить коротко:

qreg bar[8];
qreg baz[8];
CX bar, baz;
То же справедливо и для пользовательских гейтов произвольного размера, т.е. трехкубитных, четырехкубитных и т. д.

Однако это не все возможности синтаксического сахара языка QASM. Представим себе еще один сценарий. Пусть по прежнему у нас есть два одинаковых квантовых регистра bar и baz в каждом из которых есть по 8 кубитов. Но теперь мы хотим применить контролирующую часть гейта CX ко всем кубитам регистра bar , а контролируемую часть только к одному кубиту из регистра baz , например к четвертому, т. е.:

qreg bar[8];
qreg baz[8];
CX bar[0], baz[4];
CX bar[1], baz[4];
CX bar[2], baz[4];
CX bar[3], baz[4];
CX bar[4], baz[4];
CX bar[5], baz[4];
CX bar[6], baz[4];
CX bar[7], baz[4];
С использованием синтаксического сахара такая конструкция может быть переписана в следующей форме:

qreg bar[8];
qreg baz[8];
CX bar, baz[4];
Для полного понимания того, как данный синтаксический сахар работает в самом общем виде, попробуйте самостоятельно "обезсахарить" следующий фрагмент кода:

qreg bar[8];
qreg baz[8];
qreg fiz[5];
ccx bar, fiz[3], baz;
Важно заметить, что такой синтаксический сахар применим не только к применению гейтов к квантовым регистрам, но и к измерениям:

measure qbar[0] -> cbar[0];                 measure qbar -> cbar;
measure qbar[1] -> cbar[1];
measure qbar[2] -> cbar[2];
measure qbar[3] -> cbar[3];      <=>
measure qbar[4] -> cbar[4];
measure qbar[5] -> cbar[5];
measure qbar[6] -> cbar[6];
measure qbar[7] -> cbar[7];
И к сбросу состояния регистра, про который мы будем подробно говорить в следующих разделах:

reset qbar[0];                  reset qbar;
reset qbar[1];
reset qbar[2];
reset qbar[3];      <=>
reset qbar[4];
reset qbar[5];
reset qbar[6];
reset qbar[7];

Ксловное применение квантовой опции

Во всех полноценных языках программирования есть возможность выбирать то или иное действие в зависимости от некоторого условия. Обычно такая возможность реализуется с использованием ключевого слова if . В контексте квантовых вычислений тоже довольно часто возникает такая необходимость, а именно, необходимость применить тот или иной гейт к квантовому регистру в зависимости от состояния классического регистра. Например, в протоколе квантовой телепортации в зависимости от результата измерения, а как мы знаем результаты измерения записываются в классические регистры и определяют их состояние, требуется применить тот или иной гейт к квантовому регистру. К примеру квантовой телепортации мы еще вернемся подробнее в разделе с примерами. А сейчас давайте рассмотрим как реализовать такое поведение в языке QASM. Рассмотрим пример. Пусть у нас есть классический регистр cbar , состоящий из 5 битов и квантовый регистр qbar , состоящий из 8 кубитов. Предположим, что мы хотим применить гейт U(1., 1., 1.) к каждому кубиту квантового регистра qbar только при условии, что классический регистр cbar находится в некотором определенном состоянии, например 01010 . Данное поведение реализуется следующей строкой кода:

if(cbar==10) U(1., 1., 1.) qbar;
Сразу возникает вопрос, откуда взялось число 10? На самом деле двоичное число 01010 в десятичной системе счисления и есть 10, т.е. для краткости в условии битовую строку, соответствующую состоянию классического регистра, записывают в десятичной системе счисления. В остальном семантика данной строки кода ясна: если классический регистр cbar находится в состоянии 01010 =10, то мы применяем гейт U(1., 1., 1.) ко всем кубитам регистра qbar . В противном случае мы не делаем ничего. Заметим, что в общем случае после if(cbar==10) может идти применение любого многокубитного гейта с использованием синтаксического сахара в самом общем виде.
Сброс состояния квантового регистра

Зачастую возникает необходимость привести тот или иной кубит квантового регистра или квантовый регистр целиком в первоначальное состояние. Самый простой способ сделать это включает в себя два шага. 1) В начале мы измеряем кубит, который хотим "сбросить" и записываем результат измерения. 2) Если результат измерения 0, то кубит уже "сброшен", ничего не нужно делать, если результат измерения 1, то нужно применить гейт U(pi,0,pi) к кубиту, который перевернет вектор блоха кубита приведя его в нужное состояние. На языке QASM данное действие можно записать следующим образом:

qreg qbar[1];
creg cbar[1];
...
// Здесь мы делаем некоторые операции, которые выводят кубит qbar[0] из стандартного состояния.
// Далее следует "сброс".
...
measure qbar[0] -> cbar[0];
if (cbar==1)`U(pi,0,pi)` qbar[0];
На самом деле для "сброса" состояния кубита существует отдельная команда, которая не требует аллокации классического регистра. Именно ее нужно использовать при необходимости "сброса"

qreg qbar[1];
...
// Здесь мы делаем некоторые операции, которые выводят кубит qbar[0] из стандартного состояния.
// Далее следует "сброс".
...
reset qbar[0];
Как было замечено ранее, для команды reset также применим синтаксический сахар, т.е. одним вызовом команды reset можно "сбросить" все кубиты квантового регистра.
Создание и подлючение моделей

Язык QASM позволяет создавать модули, которые в дальнейшем можно подключать к вашим программам. Модуль представляет из себя набор определений гейтов, которые вы используете в своей программе, вынесенный в отдельный файл. Для подключения модуля к вашей программе достаточно сразу после строки OPENQASM 2.0; вставить директиву include "filename"; , где "filename" имя файла, в котором хранятся определения гейтов, т.е. имя вашего модуля.
Стандартная библиотека квантовых операций

Самый широко используемый модуль языка QASM это стандартный набор гейтов. Данный модуль имеет имя qelib1.inc и содержит в себе определения всех широко используемых гейтов. Все определения выраженны только через гейты CX и U встроенные в язык, т. е. qelib1.inc это своего рода стандартная библиотека языка QASM. Для подключения "стандартной библиотеки" достаточно сразу после строки OPENQASM 2.0; вставить директиву include "qelib1.inc"; . Ниже мы приведем полный код qelib1.inc представляющий из себя просто определения различных гейтов (код qelib1.inc взят из этой статьи)

// Quantum Experience (QE) Standard Header
// file: qelib1.inc
// --- QE Hardware primitives ---
// 3-parameter 2-pulse single qubit gate
gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }
// 2-parameter 1-pulse single qubit gate
gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }
// 1-parameter 0-pulse single qubit gate
gate u1(lambda) q { U(0,0,lambda) q; }
// controlled-NOT
10
gate cx c,t { CX c,t; }
// idle gate (identity)
gate id a { U(0,0,0) a; }
// --- QE Standard Gates ---
// Pauli gate: bit-flip
gate x a { u3(pi,0,pi) a; }
// Pauli gate: bit and phase flip
gate y a { u3(pi,pi/2,pi/2) a; }
// Pauli gate: phase flip
gate z a { u1(pi) a; }
// Clifford gate: Hadamard
gate h a { u2(0,pi) a; }
// Clifford gate: sqrt(Z) phase gate
gate s a { u1(pi/2) a; }
// Clifford gate: conjugate of sqrt(Z)
gate sdg a { u1(-pi/2) a; }
// C3 gate: sqrt(S) phase gate
gate t a { u1(pi/4) a; }
// C3 gate: conjugate of sqrt(S)
gate tdg a { u1(-pi/4) a; }
// --- Standard rotations ---
// Rotation around X-axis
gate rx(theta) a { u3(theta,-pi/2,pi/2) a; }
// rotation around Y-axis
gate ry(theta) a { u3(theta,0,0) a; }
// rotation around Z axis
gate rz(phi) a { u1(phi) a; }
// --- QE Standard User-Defined Gates ---
// controlled-Phase
gate cz a,b { h b; cx a,b; h b; }
// controlled-Y
// controlled-Y
gate cy a,b { sdg b; cx a,b; s b; }
// controlled-H
gate ch a,b {
h b; sdg b;
cx a,b;
h b; t b;
cx a,b;
t b; h b; s b; x b; s a;
}
11
// C3 gate: Toffoli
gate ccx a,b,c
{
h c;
cx b,c; tdg c;
cx a,c; t c;
cx b,c; tdg c;
cx a,c; t b; t c; h c;
cx a,b; t a; tdg b;
cx a,b;
}
// controlled rz rotation
gate crz(lambda) a,b
{
u1(lambda/2) b;
cx a,b;
u1(-lambda/2) b;
cx a,b;
}
// controlled phase rotation
gate cu1(lambda) a,b
{
u1(lambda/2) a;
cx a,b;
u1(-lambda/2) b;
cx a,b;
u1(lambda/2) b;
}
// controlled-U
gate cu3(theta,phi,lambda) c, t
{
// implements controlled-U(theta,phi,lambda) with target t and control c
u1((lambda-phi)/2) t;
cx c,t;
u3(-theta/2,0,-(phi+lambda)/2) t;
cx c,t;
u3(theta/2,phi,0) t;
}