Поделиться через


Руководство: Реализация квантового преобразования Фурье в Q#

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

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

В этом руководстве описано, как:

  • Определение квантовых операций в Q#.
  • Напишите схему квантового преобразования Фурье.
  • Симулируйте квантовую операцию, начиная с выделения кубитов и до получения результатов измерений.
  • Узнайте, как смоделированная волновая функция квантовой системы развивается во время операции.

Примечание.

Это низкоуровневое представление обработки квантовой информации часто рассматривается в терминах квантовых цепей, которые представляют последовательное применение вентилей или операций к конкретным кубитам системы. Таким образом, последовательно применяемые одно- и многокубитовые операции можно легко представить в виде схем цепи. Например, полное трехкубитное квантовое преобразование Фурье, используемое в этом руководстве, имеет следующее представление в качестве схемы: Схема квантового преобразования Фурье.

Совет

Если вы хотите ускорить путешествие квантовых вычислений, ознакомьтесь с кодом с помощью Microsoft Quantum, уникальной функцией веб-сайта Microsoft Quantum. Здесь можно запускать встроенные Q# примеры или Q# собственные программы, создавать новый Q# код из запросов, открывать и запускать код в VS Code для Интернета с помощью одного щелчка мыши и задавать вопросы о квантовых вычислениях.

Предварительные условия

Создайте новый файл Q#

  1. В VS Code откройте меню "Файл " и выберите новый текстовый файл.
  2. Сохраните файл как QFTcircuit.qs. Этот файл содержит код Q# для программы.
  3. Откройте QFTcircuit.qs.

Написать схему QFT в Q#

В первой части этого руководства определяется операция Q#Main, которая выполняет квантовое преобразование Фурье с тремя кубитами. Функция DumpMachine используется для наблюдения за развитием имитируемой волновой функции для системы из трех кубитов в ходе выполнения операции. Во второй части руководства вы добавите функциональные возможности измерения и сравниваете состояния до и после измерения кубитов.

Вы строите операцию шаг за шагом. Скопируйте и вставьте код в приведенные ниже разделы в файл QFTcircuit.qs .

Полный Q# код этого раздела можно просмотреть в качестве ссылки.

Импорт обязательных Q# библиотек

В вашем файле Q# импортируйте соответствующие пространства имен Std.*.

import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;

// operations go here

Определение операций с аргументами и возвращаемыми значениями

Теперь определите операцию Main:

operation Main() : Unit {
    // do stuff
}

Операция Main никогда не принимает аргументы и в настоящее время возвращает объект Unit, который аналогичен возвращению void в C# или пустого кортежа Tuple[()] в Python. Позже вы измените операцию, чтобы вернуть массив результатов измерения.

Выделите кубиты

Q# В рамках операции выделите регистр из трех кубитов с ключевым словом use. При использовании ключевого слова use кубиты автоматически выделяются в состоянии $\ket{0}$.

use qs = Qubit[3]; // allocate three qubits

Message("Initial state |000>:");
DumpMachine();

Как и в реальных квантовых вычислениях, Q# не позволяет напрямую обращаться к состояниям кубитов. Однако операция DumpMachine печатает текущее состояние компьютера target, поэтому она может обеспечить ценные аналитические сведения об отладке и обучении при использовании вместе с полным симулятором состояния.

Применяйте однокубитные и управляемые операции

Затем примените операции, составляющие саму операцию Main. Q# уже содержит многие из них и другие основные квантовые операции в Std.Intrinsic пространстве имен.

Примечание.

Std.Intrinsic не был импортирован в предыдущем фрагменте кода с другими пространствами имен, так как он загружается автоматически компилятором для всех Q# программ.

Прежде всего при применим операцию H (операция Адамара) к первому кубиту:

Схема, показывающая цепь для трех кубитов QFT через первый Хадамард.

Для применения операции к определенному кубиту из реестра (например, к одному Qubit из массива Qubit[]) используется стандартная нотация индекса. Таким образом, применение операции H к первому кубиту из реестра qs записывается так:

H(qs[0]);

Помимо применения операции H к отдельным кубитам, цепь QFT состоит в основном из контролируемых вращений R1. Операция R1(θ, <qubit>) в целом оставляет компонент $\ket{0}$ кубита без изменений при применении поворота $e^{i\theta}$ к компоненту $\ket{1}$.

Q# облегчает условное выполнение операции на основе одного или нескольких управляющих кубитов. По сути, достаточно указать перед вызовом префикс Controlled, и аргументы операции изменяются следующим образом:

Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))

Обратите внимание, что аргумент управляющих кубитов должен иметь формат массива, даже если он содержит всего один кубит.

Контролируемые операции в QFT — это R1 операции, которые действуют на первом кубите (и управляются вторыми и третьими кубитами):

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

Вызовите эти операции в файле Q# с помощью следующих инструкций:

Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));

Функция PI() используется для определения вращений в терминах радиан, кратных π (пи).

Применение операции SWAP

После выполнения соответствующих H операций и управляемых поворотов со вторым и третьим кубитами схема выглядит следующим образом:

//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));

//third qubit:
H(qs[2]);

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

SWAP(qs[2], qs[0]);

Теперь операция Q# включает операции преобразования квантового фурье на уровне кубитов:

Схема, показывающая схему для трехкубитного квантового преобразования Фурье.

Освободить кубиты

Последним шагом будет повторный вызов DumpMachine() для проверки состояния после операции и освобождения кубитов. При выделении все кубиты находились в состоянии $\ket{0}$, и теперь их нужно вернуть в исходное состояние с помощью операции ResetAll.

Требование в явной форме сбросить все кубиты на $\ket{0}$ — это базовая функция Q#, поскольку она позволяет другим операциям точно знать их состояние, когда они начинают использовать те же кубиты (дефицитный ресурс). Кроме того, перезагрузка кубитов гарантирует, что они не запутаны с другими кубитами в системе. Если сброс не выполняется в конце блока выделения use, может возникнуть ошибка среды выполнения.

Добавьте в файл Q# следующие строки:

Message("After:");
DumpMachine();

ResetAll(qs); // deallocate qubits

Полная операция QFT

Программа Q# завершена. Теперь файл QFTcircuit.qs должен выглядеть следующим образом:

import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;

operation Main() : Unit {

    use qs = Qubit[3]; // allocate three qubits

    Message("Initial state |000>:");
    DumpMachine();

    //QFT:
    //first qubit:
    H(qs[0]);
    Controlled R1([qs[1]], (PI()/2.0, qs[0]));
    Controlled R1([qs[2]], (PI()/4.0, qs[0]));

    //second qubit:
    H(qs[1]);
    Controlled R1([qs[2]], (PI()/2.0, qs[1]));

    //third qubit:
    H(qs[2]);

    SWAP(qs[2], qs[0]);

    Message("After:");
    DumpMachine();

    ResetAll(qs); // deallocate qubits

}                                                                                                                                                                               

Запуск канала QFT

В настоящее время Main операция не возвращает никакое значение. Операция возвращает Unit значение. Позже вы измените операцию, чтобы вернуть массив результатов измерения (Result[]).

  1. Чтобы запустить программу, выберите команду "Запустить Q# файл " в меню, которое предшествует Main, или нажмите клавиши CTRL+F5. Программа выполняет Main операцию на симуляторе по умолчанию.
  2. Message Выходные DumpMachine данные отображаются в консоли отладки.

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

Добавление измерений в канал QFT

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

Существует множество видов квантовых измерений, из которых мы рассмотрим здесь только самые основные: проективные измерения отдельных кубитов. При измерении в указанном базисе (например, в вычислительном базисе $ { \ket{0}, \ket{1} } $), состояние кубита проецируется на измеренное базисное состояние, уничтожая любую существующую между кубитами суперпозицию.

Измените операцию QFT

Чтобы реализовать измерения в программе Q#, используйте операцию M, которая возвращает данные типа Result.

Сначала измените операцию Main так, чтобы вместо Result[] возвращался массив результатов измерения Unit.

operation Main() : Result[] {

Определение и инициализация массива Result[]

Перед выделением кубитов объявите и привяжите массив из трех элементов (по одному Result для каждого кубита).

mutable resultArray = [Zero, size = 3];

Префикс mutableresultArray ключевого слова позволяет изменять переменную позже в коде, например при добавлении результатов измерения.

Выполнение измерений в цикле for и добавление результатов в массив

После операций преобразования QFT вставьте следующий код:

for i in IndexRange(qs) {
    resultArray w/= i <- M(qs[i]);
}

Вызываемая для массива (например, для массива кубитов IndexRange) функция qs возвращает диапазон индексов массива. Здесь он используется в цикле for для последовательной меры каждого кубита с помощью инструкции M(qs[i]). Затем каждый измеренный тип Result (Zero или One) добавляется в соответствующую позицию индекса в resultArray с помощью инструкции обновления и повторного назначения.

Примечание.

Синтаксис этого выражения уникален для Q#, но соответствует подобному переопределению переменной resultArray[i] <- M(qs[i]), которое используется в других языках, таких как F# и R.

Ключевое слово set всегда используется для повторного присвоения переменных, привязанных с помощью mutable.

Возвращение resultArray

После измерения всех трех кубитов и добавления результатов в resultArray, можно безопасно сбросить и освободить кубиты, как и раньше. Чтобы вернуть измерения, вставьте:

return resultArray;

Запуск канала QFT с измерениями

Давайте изменим размещение функций DumpMachine так, чтобы выводить состояние до и после измерений. Итоговый код Q# должен выглядеть следующим образом:

import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;

operation Main() : Result[] {

    mutable resultArray = [Zero, size = 3];

    use qs = Qubit[3];

    //QFT:
    //first qubit:
    H(qs[0]);
    Controlled R1([qs[1]], (PI()/2.0, qs[0]));
    Controlled R1([qs[2]], (PI()/4.0, qs[0]));

    //second qubit:
    H(qs[1]);
    Controlled R1([qs[2]], (PI()/2.0, qs[1]));

    //third qubit:
    H(qs[2]);

    SWAP(qs[2], qs[0]);

    Message("Before measurement: ");
    DumpMachine();

    for i in IndexRange(qs) {
        resultArray w/= i <- M(qs[i]);
    }

    Message("After measurement: ");
    DumpMachine();

    ResetAll(qs);
    Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
    return resultArray;

}

Совет

Не забудьте сохранить файл каждый раз, когда вы вводите изменения в код перед его повторной запуском.

  1. Чтобы запустить программу, выберите команду "Выполнить Q# " в меню, которое предшествует Main, или нажмите клавиши CTRL+F5. Программа выполняет Main операцию на симуляторе по умолчанию.
  2. Выходные данные Message и DumpMachine отображаются в консоли отладки.

Выходные данные должны выглядеть следующим образом:

Before measurement: 

 Basis | Amplitude      | Probability | Phase
 -----------------------------------------------
 |000⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |001⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |010⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |011⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |100⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |101⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |110⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |111⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000

After measurement: 

 Basis | Amplitude      | Probability | Phase
 -----------------------------------------------
 |010⟩ |  1.0000+0.0000𝑖 |   100.0000% |   0.0000

Post-QFT measurement results [qubit0, qubit1, qubit2]: 

[Zero, One, Zero]

В этих выходных данных показан ряд отличительных моментов.

  1. При сравнении возвращаемого результата с результатом предварительного измерения DumpMachine, он явно не демонстрирует суперпозицию после выполнения QFT над основными состояниями. Измерение возвращает только одно базисное состояние с вероятностью, определяемой амплитудой этого состояния в волновой функции системы.
  2. После измерения DumpMachine, измерение изменяет состояние, проецируя его из начальной суперпозиции на одно базисное состояние, соответствующее измеренному значению.

Если повторять эту операцию много раз, статистика результатов начинает иллюстрировать одинаково взвешенную суперпозицию состояния после QFT, что приводит к случайному результату на каждом выстреле. Однако помимо того, что этот способ является неэффективным и несовершенным, он, тем не менее, будет воспроизводить только относительные амплитуды базисных состояний, а не относительные фазы между ними. Последний не является проблемой в этом примере, но относительные этапы отображаются, если заданы более сложные входные данные QFT, чем $\ket{000}$.

Использование операций Q# для упрощения канала QFT

Как упоминалось во введении, значительная сила Q# заключается в том, что она позволяет абстрагироваться от забот, связанных с работой с отдельными кубитами. Если вы хотите разрабатывать полномасштабные и действенные квантовые программы, озабоченность по поводу того, когда выполняется операция H — до или после конкретного поворота, — только замедлит выполнение задач. Azure Quantum предоставляет ApplyQFT операцию, которую можно использовать и применять для любого количества кубитов.

  1. Замените все операции от первой H операции до SWAP операции включительно:

    ApplyQFT(qs);
    
  2. Теперь код должен выглядеть следующим образом.

    import Std.Diagnostics.*;
    import Std.Math.*;
    import Std.Arrays.*;
    
    operation Main() : Result[] {
    
        mutable resultArray = [Zero, size = 3];
    
        use qs = Qubit[3];
    
        //QFT:
        //first qubit:
    
        ApplyQFT(qs);
    
        Message("Before measurement: ");
        DumpMachine();
    
        for i in IndexRange(qs) {
            resultArray w/= i <- M(qs[i]);
        }
    
        Message("After measurement: ");
        DumpMachine();
    
        ResetAll(qs);
        Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
        return resultArray;
    
    }
    
  3. Запустите Q# программу еще раз и обратите внимание, что выходные данные такие же, как и раньше.

  4. Чтобы увидеть реальную выгоду использования Q# операций, измените количество кубитов на что-то другое, кроме 3:

mutable resultArray = [Zero, size = 4];

use qs = Qubit[4];
//...

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

Ознакомьтесь с другими учебниками по Q#:

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