Учебник. Реализация квантового генератора случайных чисел на языке Q#

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

При работе с этим руководством вы сделаете следующее:

  • Q# Создайте программу.
  • Просмотрите основные компоненты Q# программы.
  • Определите логику проблемы.
  • Объединение классических и квантовых операций для решения проблемы.
  • Работа с кубитами и суперпозицией для создания квантового генератора случайных чисел.

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

Чтобы разработать и запустить пример кода в Visual Studio Code (VS Code) и Jupyter Notebook, установите следующее:

Определение проблемы

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

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

Кубит — это единица квантовой информации, которая может находиться в суперпозиции. При измерении кубит может находиться только в состоянии 0 или в 1 состоянии. Однако перед измерением состояние кубита представляет собой вероятность получить при измерении либо 0, либо 1.

Для начала возьмите кубит в базовом состоянии, например 0. Первый шаг генератора случайных чисел — использовать оператор Адамара, чтобы ввести кубит в равновесную суперпозицию. Измерение этого состояния дает 0 или 1 с вероятностью 50% для каждого из исходов, то есть истинно случайный бит.

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

Предположим, что вы повторяете этот процесс четыре раза, создав следующую последовательность двоичных цифр:

$${0, 1, 1, 0}$$

Если сцепить (или объединить) эти биты в битовую строку, можно получить большее число. В этом примере битовая последовательность ${0110}$ эквивалентна шести в десятичном формате.

$${0110_{\ binary} \equiv 6_{\ decimal}} $$

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

Определение логики генератора случайных чисел

Давайте рассмотрим логику генератора случайных чисел:

  1. Определите max как максимальное число, которое требуется создать.
  2. Определите количество случайных битов, которые требуется создать. Это делается путём вычисления того, сколько битов, nBits, необходимо, чтобы представить целые числа вплоть до max.
  3. Создание случайной битовой строки, длина которой равна nBits.
  4. Если битовая строка представляет число больше max, выполняется возврат к шагу 3.
  5. В противном случае процесс завершается. Возврат созданного числа в виде целого числа.

В качестве примера зададим для max значение 12. То есть 12 — это наибольшее число, которое вы хотите использовать в качестве пароля.

Для представления числа от 0 до 12 требуется ${\lfloor ln(12) / ln(2) + 1 \rfloor}$ или 4 бита. Мы можем использовать встроенную функцию BitSizeI, которая принимает любое целое число и возвращает количество битов, необходимых для представления.

Предположим, что вы создаете битовую строку $${1101_{\ binary}}$, которая эквивалентна ${13_{\ decimal}}$. Так как 13 больше 12, вы повторяете процесс.

Затем создается битовая строка ${0110_{\ binary}}$, которая эквивалентна ${6_{\ decimal}}$. Так как 6 меньше 12, процесс завершен.

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

Напишите генератор случайных битов

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

operation GenerateRandomBit() : Result {
    // Allocate a qubit.
    use q = Qubit();

    // Set the qubit into superposition of 0 and 1 using the Hadamard 
    H(q);

    // At this point the qubit `q` has 50% chance of being measured in the
    // |0〉 state and 50% chance of being measured in the |1〉 state.
    // Measure the qubit value using the `M` operation, and store the
    // measurement value in the `result` variable.
    let result = M(q);

    // Reset qubit to the |0〉 state.
    // Qubits must be in the |0〉 state by the time they are released.
    Reset(q);

    // Return the result of the measurement.
    return result;
}

Теперь давайте изучим этот новый код.

  • Вы определяете операцию GenerateRandomBit, которая не принимает никаких входных данных и формирует значение типа Result. Тип Result представляет результат измерения и может иметь два возможных значения: Zero или One.
  • Вы выделяете один кубит с ключевым словом use . При выделении кубит всегда находится в состоянии |0〉.
  • Операция H используется для перевода кубита в равную суперпозицию.
  • Вы используете операцию M для измерения кубита и возврата результата измерения (Zero или One).
  • Для сброса кубита в состояние |0〉 используется операция Reset.

Если мы переводим кубит в состояние суперпозиции с помощью операции H и измеряем его с помощью операции M, при каждом вызове кода возвращаются разные результаты.

Визуализация Q# кода с помощью области Blockh

В области Блокха северный полюс представляет классическое значение 0, а южный полюс представляет классическое значение 1. Любое состояние суперпозиции обозначается точкой на этой сфере (или стрелкой на схеме). Чем ближе конец этой стрелки к одному из полюсов, тем выше вероятность схлопывания этого кубита при измерении в то классическое состояние, которое соответствует этому полюсу. Например, состояние кубита, представленное стрелкой на следующем рисунке, имеет более высокую вероятность предоставления значения 0 при измерении.

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

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

  1. Сначала начните с кубита, инициализированного в состоянии |0〉 и примените H операцию для создания равной суперпозиции, в которой вероятности для 0 и 1 совпадают.

    Схема, показывающая подготовку кубита в состояние суперпозиции путём применения вентиля Адамара.
  2. Затем измерьте состояние кубита и сохраните выходное значение:

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

Так как результат измерения является случайным, а вероятности измерения 0 и 1 одинаковы, вы получили совершенно случайный бит. Вызывая эту операцию несколько раз, вы можете создавать большие целые числа. Например, три вызова этой операции предоставят вам три случайных бита, из которых можно собрать трехбитовое число (то есть случайное число в диапазоне от 0 до 7).

Создание полного генератора случайных чисел

  1. Сначала необходимо импортировать необходимые пространства имен из стандартной Q# библиотеки в программу. Компилятор Q# загружает множество распространенных функций и операций автоматически, однако для полного генератора случайных чисел требуются некоторые дополнительные функции и операции из двух Q# пространств имен: Std.Mathи Std.Convert.

    import Std.Convert.*;
    import Std.Math.*;
    
  2. Затем определите GenerateRandomNumberInRange операцию. Эта операция многократно вызывает операцию GenerateRandomBit для создания строки битов.

    /// Generates a random number between 0 and `max`.
    operation GenerateRandomNumberInRange(max : Int) : Int {
        // Determine the number of bits needed to represent `max` and store it
        // in the `nBits` variable. Then generate `nBits` random bits which will
        // represent the generated random number.
        mutable bits = [];
        let nBits = BitSizeI(max);
        for idxBit in 1..nBits {
            bits += [GenerateRandomBit()];
        }
        let sample = ResultArrayAsInt(bits);
    
        // Return random number if it is within the requested range.
        // Generate it again if it is outside the range.
        return sample > max ? GenerateRandomNumberInRange(max) | sample;
    }
    
    

    Рассмотрим этот новый код.

    • Необходимо вычислить количество битов, необходимых для выражения целых чисел до max. Функция BitSizeI из Std.Math пространства имен преобразует целое число в число битов, необходимых для представления.
    • Операция SampleRandomNumberInRange использует цикл for для создания случайных чисел до тех пор, пока не будет получено значение, которое меньше или равно max. Цикл for работает точно так же, как for цикл на других языках программирования.
    • Переменная bits является изменяемой переменной. Изменяемая переменная может изменяться во время вычисления. Для изменения значения изменяемой переменной используется директива set.
    • Функция ResultArrayAsInt из пространства имен по умолчанию Std.Convert преобразует битовую строку в положительное целое число.
  3. Наконец, вы добавляете точку входа в программу. По умолчанию Q# компилятор ищет Main операцию и начинает обработку там. Она вызывает GenerateRandomNumberInRange операцию для создания случайного числа от 0 до 100.

    operation Main() : Int {
        let max = 100;
        Message($"Sampling a random number between 0 and {max}: ");
    
        // Generate random number in the 0..max range.
        return GenerateRandomNumberInRange(max);
    }
    

    Директива let объявляет переменные, которые не меняются во время вычисления. Здесь вы определяете максимальное значение как 100.

    Дополнительные сведения об Main операции см. в разделе "Точки входа".

  4. Полный код для генератора случайных чисел выглядит следующим образом:

import Std.Convert.*;
import Std.Math.*;

operation Main() : Int {
    let max = 100;
    Message($"Sampling a random number between 0 and {max}: ");

    // Generate random number in the 0..max range.
    return GenerateRandomNumberInRange(max);
}

/// Generates a random number between 0 and `max`.
operation GenerateRandomNumberInRange(max : Int) : Int {
    // Determine the number of bits needed to represent `max` and store it
    // in the `nBits` variable. Then generate `nBits` random bits which will
    // represent the generated random number.
    mutable bits = [];
    let nBits = BitSizeI(max);
    for idxBit in 1..nBits {
        bits += [GenerateRandomBit()];
    }
    let sample = ResultArrayAsInt(bits);

    // Return random number if it is within the requested range.
    // Generate it again if it is outside the range.
    return sample > max ? GenerateRandomNumberInRange(max) | sample;
}

operation GenerateRandomBit() : Result {
    // Allocate a qubit.
    use q = Qubit();

    // Set the qubit into superposition of 0 and 1 using a Hadamard operation
    H(q);

    // At this point the qubit `q` has 50% chance of being measured in the
    // |0〉 state and 50% chance of being measured in the |1〉 state.
    // Measure the qubit value using the `M` operation, and store the
    // measurement value in the `result` variable.
    let result = M(q);

    // Reset qubit to the |0〉 state.
    // Qubits must be in the |0〉 state by the time they are released.
    Reset(q);

    // Return the result of the measurement.
    return result;
}

Запуск программы генератора случайных чисел

Выберите предпочитаемую среду разработки, расширение QDK в VS Code или пакет QDK Python в Jupyter Notebook.

  1. В VS Code откройте меню "Файл " и выберите новый текстовый файл , чтобы создать новый файл.

  2. Сохраните файл как RandomNumberGenerator.qs. Этот файл будет содержать Q# код для программы.

  3. Скопируйте следующий код в RandomNumberGenerator.qs файле.

    import Std.Convert.*;
    import Std.Math.*;
    
    operation Main() : Int {
        let max = 100;
        Message($"Sampling a random number between 0 and {max}: ");
    
        // Generate random number in the 0..max range.
        return GenerateRandomNumberInRange(max);
    }
    
    /// # Summary
    /// Generates a random number between 0 and `max`.
    operation GenerateRandomNumberInRange(max : Int) : Int {
        // Determine the number of bits needed to represent `max` and store it
        // in the `nBits` variable. Then generate `nBits` random bits which will
        // represent the generated random number.
        mutable bits = [];
        let nBits = BitSizeI(max);
        for idxBit in 1..nBits {
            bits += [GenerateRandomBit()];
        }
        let sample = ResultArrayAsInt(bits);
    
        // Return random number if it is within the requested range.
        // Generate it again if it is outside the range.
        return sample > max ? GenerateRandomNumberInRange(max) | sample;
    }
    
    /// # Summary
    /// Generates a random bit.
    operation GenerateRandomBit() : Result {
        // Allocate a qubit.
        use q = Qubit();
    
        // Set the qubit into superposition of 0 and 1 using the Hadamard 
        // operation `H`.
        H(q);
    
        // At this point the qubit `q` has 50% chance of being measured in the
        // |0〉 state and 50% chance of being measured in the |1〉 state.
        // Measure the qubit value using the `M` operation, and store the
        // measurement value in the `result` variable.
        let result = M(q);
    
        // Reset qubit to the |0〉 state.
        // Qubits must be in the |0〉 state by the time they are released.
        Reset(q);
    
        // Return the result of the measurement.
        return result;
    
        // Note that Qubit `q` is automatically released at the end of the block.
    }
    
  4. Чтобы запустить программу, выберите команду "Выполнить " из списка команд, описанных выше Main(), или нажмите клавиши CTRL+F5. Программа выполняет Main() операцию на симуляторе по умолчанию.

    Снимок экрана Visual Studio Code, на котором показано, где найти команду запуска в CodeLens.

  5. Выходные данные будут отображаться в консоли отладки.

  6. Запустите программу еще раз, чтобы увидеть другой результат.

Примечание.

Если для профиля QIR target не задано значение "Неограниченный", при запуске программы возникает ошибка. В этой программе компилятор автоматически устанавливает target профиль в значение "Неограниченный ", если только вы не задали профиль самостоятельно.

График гистограммы частоты

Давайте визуализируем распределение результатов, полученных при выполнении квантовой программы несколько раз. Гистограмма частоты помогает визуализировать распределение вероятностей этих результатов.

  1. Выберите Вид —> командная палитра и введите гистограмма, которая открывает команду QDK: выполнить файл и показать гистограмму. Вы также можете выбрать гистограмму из списка команд, описанных выше Main(). Выберите этот параметр, чтобы открыть Q# окно гистограммы.

    Снимок экрана Visual Studio Code, на котором показано, где найти команду гистограммы в CodeLens.

  2. Введите количество запусков для выполнения программы, например 100 запусков, и нажмите Enter. Гистограмма будет отображаться в Q# окне гистограммы.

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

    Сделайте снимок экрана Q# окна гистограммы в Visual Studio Code.

    Совет

    Вы можете увеличить гистограмму с помощью колесика прокрутки мыши или жеста трекпада. При увеличении масштаба диаграмму можно сдвигать, нажав клавиши ALT при прокрутке.

  4. Выберите столбец, чтобы отобразить процент для этого результата.

  5. Щелкните значок параметров вверху слева, чтобы отобразить параметры. Вы можете отобразить первые 10 результатов, 25 лучших результатов или все результаты. Вы также можете отсортировать результаты по убыванию или по возрастанию.

    Снимок экрана окна гистограммы Q# в Visual Studio Code, на котором показаны параметры отображения.

Примечание.

Этот фрагмент кода в настоящее время не выполняется на любом доступном оборудовании targetsAzure Quantum, так как вызываемому ResultArrayAsInt объекту требуется QPU с полным профилем вычислений.

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

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