Симуляторы — это особенно универсальная особенность QDK. Они позволяют вам выполнять различные задачи в программе на Q#, не меняя ее. Такие задачи включают симуляцию полного состояния, оценку ресурса или симуляцию трассировки. Новый интерфейс IQuantumProcessor позволяет очень легко создавать собственные симуляторы и интегрировать их в свои проекты на Q#.

Этот пост является первым в серии, посвященной этому интерфейсу. Мы начнем с реализации обратимого симулятора в качестве первого примера, который мы расширим в будущих публикациях в блоге. Обратимый симулятор может моделировать квантовые программы, которые состоят только из классических операций: X, CNOT, CCNOT (Toffoli gate) или произвольно управляемых X-операций. Поскольку обратимый симулятор может представлять квантовое состояние, присваивая одно булево значение каждому кубиту, он может запускать даже квантовые программы, состоящие из тысяч кубитов. Этот симулятор очень полезен для тестирования квантовых операций, которые оценивают булевы функции.



Реализация симулятора в C#


Этот пост в блоге освещает основные фрагменты кода. Полный исходный код можно найти в репозитории Microsoft QDK Samples.

Вы начинаете писать свой собственный симулятор, расширяя класс QuantumProcessorBase:

class ReversibleSimulatorProcessor : QuantumProcessorBase {
    private IDictionary<Qubit, bool> simulationValues = new Dictionary<Qubit, bool>();

    // определить пользовательские действия для внутренних операций (intrinsic operations)...
}

Словарь, в котором будет храниться текущее значение моделирования для каждого кубита в программе, уже добавлен в класс. Классические квантовые состояния |0? и |1? представлены в виде булевых значений false и true соответственно. Симуляторы определяют внутренние операции в программе Q#. QuantumProcessorBase содержит метод для каждой внутренней операции, который вы можете переопределить, чтобы определить его поведение в новом симуляторе. Обратите внимание, что нереализованные методы выдают исключение по умолчанию. Это поможет вам определить случаи, когда операция все еще должна быть реализована, и сообщит пользователю симулятора о том, что встроенная операция не поддерживается симулятором. Например, обратимый симулятор не может быть использован для моделирования квантовых программ, которые содержат неклассические операции, такие как H или T.

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

public override void OnAllocateQubits(IQArray qubits) {
    foreach (var qubit in qubits) {
        simulationValues[qubit] = false;
    }
}

public override void OnReleaseQubits(IQArray qubits) {
    foreach (var qubit in qubits) {
        simulationValues.Remove(qubit);
    }
}

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

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

public override void X(Qubit qubit) {
    simulationValues[qubit] = !simulationValues[qubit];
}

public override void ControlledX(IQArray controls, Qubit qubit) {
    simulationValues[qubit] ^= And(controls);
}

Наконец, измерение кубита в программе Q# возвращает результат (One или Zero), который может быть вычислен на основе текущего значения моделирования кубита. Мы также реализуем метод Reset, который вызывается, когда операция сброса вызывается в программе Q#. Последний не удаляет кубит из текущей области, но сбрасывает значение моделирования обратно к его начальному значению, которое является ложным.

public override Result M(Qubit qubit) {
    return simulationValues[qubit] ? Result.One : Result.Zero;
}

public override void Reset(Qubit qubit) {
    simulationValues[qubit] = false;
}

ReversibleSimulatorProcessor может использоваться в качестве симулятора путем создания экземпляра QuantumProcessorDispatcher. Рекомендуемый стиль дизайна — предоставить специальный класс для симулятора:

public class ReversibleSimulator : QuantumProcessorDispatcher {
    public ReversibleSimulator() : base(new ReversibleSimulatorProcessor()) {}
}

Использование нового симулятора


Давайте попробуем новый симулятор на следующей операции Q#:

operation ApplyMajority(a : Qubit, b : Qubit, c : Qubit, f : Qubit) : Unit {
    within {
        CNOT(b, a);
        CNOT(b, c);
    } apply {
        CCNOT(a, c, f);
        CNOT(b, f);
    }
}

Мы также пишем операцию, которая выполняет мажоритарную операцию, предоставляя три логических входных значения:

operation RunMajority(a : Bool, b : Bool, c : Bool) : Bool {
    using ((qa, qb, qc, f) = (Qubit(), Qubit(), Qubit(), Qubit())) {
        within {
            ApplyPauliFromBitString(PauliX, true, [a, b, c], [qa, qb, qc]);
        } apply {
            ApplyMajority(qa, qb, qc, f);
        }
        return MResetZ(f) == One;
    }
}

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

public static void Main(string[] args) {
    var sim = new ReversibleSimulator();
    var bits = new[] {false, true};

    foreach (var a in bits) {
        foreach (var b in bits) {
            foreach (var c in bits) {
                var f = RunMajority.Run(sim, a, b, c).Result;
                Console.WriteLine($"Majority({a,5}, {b,5}, {c,5})  =  {f,5}");
            }
        }
    }
}

Готовы написать собственный симулятор?


Мы надеемся, что этот пост вдохновит вас на создание собственного симулятора. На следующих шагах мы обсудим, как улучшить производительность текущей реализации (например, не используя словарь для хранения значений моделирования), как превратить его в самостоятельный проект Q#, как предоставить настраиваемые действия для non-intrinsic-операций, и как обеспечить диагностические операции, которые помогают отладке.