Управление квантовой памятью

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

Вместо поддержки отдельных операторов выделения и освобождения квантовой памяти Q# поддерживает выделение квантовой памяти в виде блочных операторов, то есть память доступна только в пределах области действия данного оператора. Блок инструкций можно определить неявно при выделении кубит на срок действия текущей области, как описано в разделах об операторах use и borrow. Попытка доступа к выделенным кубитам после завершения оператора приводит к возникновению исключения в среде выполнения.

В Q# есть два оператора для создания экземпляров значений кубит, массивов кубит или любого их сочетания: use и borrow. Эти операторы можно использовать только в операциях. Они собирают созданные экземпляры значений кубит, привязывают их к переменным, указанным в операторе, а затем выполняют блок операторов. В конце блока привязанные переменные выходят за пределы области и больше не определены.

Q# различает выделение чистых и грязных кубит. "Чистые" кубиты не запутаны и не используются другой частью вычисления. "Грязные" кубиты — это кубиты, состояние которых неизвестно. Они даже могут быть запутаны с другими частями памяти квантового процессора.

Оператор use

"Чистые" кубиты выделяются оператором use.

  • Он состоит из ключевого слова use, за которым следует привязка и необязательный блок операторов.
  • Если имеется блок операторов, кубиты доступны только в его пределах. В противном случае кубиты доступны до конца текущей области.
  • Привязка строится по той же схеме, что и в операторе let: это либо один символ, либо кортеж символов, за которым следует знак равенства =, а также одиночный кортеж или соответствующий кортеж инициализаторов.

Доступны инициализаторы для одного кубита (обозначается как Qubit()) или для массива кубитов (Qubit[n], где n — это выражение типа Int). Например,

use qubit = Qubit();
// ...

use (aux, register) = (Qubit(), Qubit[5]);
// ...

use qubit = Qubit() {
    // ...
}

use (aux, register) = (Qubit(), Qubit[5]) {
    // ...
}

После выделения кубиты гарантированно находятся в состоянии |0⟩. Они освобождаются в конце область и должны находиться в состоянии |0⟩ после освобождения. Это требование не применяется компилятором, так как для этого потребовалось бы символьное вычисление, которое очень быстро загружает ресурсы. При выполнении в симуляторах это требование можно применять во время выполнения. В квантовых процессорах это требование нельзя применять во время выполнения. Не измеренный кубит можно сбросить в состояние |0⟩ посредством унитарного преобразования. Если этого не сделать, поведение будет неправильным.

Оператор use выделяет кубиты из кучи свободных кубит квантового процессора и возвращает их в кучу не позднее, чем по достижении конца области, в которой они были привязаны.

Оператор borrow

Оператор borrow предоставляет доступ к кубитам, которые уже выделены, но не используются в настоящий момент. Эти кубиты могут находиться в произвольном состоянии и должны вернуться к нему после завершения оператора borrow. Некоторые квантовые алгоритмы могут использовать кубиты без точных сведений об их состоянии и без требования к отсутствию запутанности с остальной системой. То есть им временно требуются дополнительные кубиты, и они могут гарантировать, что эти кубиты будут возвращены в исходное состояние (каким бы оно ни было).

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

Оператор borrow строится по той же схеме, которая описана ранее для оператора use, с теми же доступными инициализаторами. Например,

borrow qubit = Qubit();
// ...

borrow (aux, register) = (Qubit(), Qubit[5]);
// ...

borrow qubit = Qubit() {
    // ...
}

borrow (aux, register) = (Qubit(), Qubit[5]) {
    // ...
}

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

Оператор borrow получает используемые кубиты, которые гарантированно не будут использоваться программой с момента привязки до последнего использования кубита. Если кубит для заимствования недостаточно, они будут выделяться из кучи и возвращаться в нее так же, как при использовании оператора use.

Примечание

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