Объявления специализаций

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

В квантовых вычислениях часто требуется сопряжение определенного преобразования. Для выполнения вычислений многие квантовые алгоритмы требуют как операции, так и ее сопряжения. В Q# применяются символьные вычисления, позволяющие автоматически создавать соответствующую реализацию сопряжения для определенной реализации тела. Это возможно даже для реализаций, в которых свободно сочетаются классические и квантовые вычисления. Однако в этом случае действуют некоторые ограничения. Например, если в реализации используются изменяемые переменные, автоматическое создание не поддерживается из соображений производительности. Более того, каждая операция, вызываемая внутри тела, создает соответствующее сопряжение, необходимое для поддержки самого функтора Adjoint.

Хотя в случае с несколькими кубитами нельзя легко отменять измерения, можно объединить измерения, чтобы применяемое преобразование было унитарным. В данном случае это означает, что хотя реализация тела содержит измерения, которые сами по себе не поддерживают функтор Adjoint, тело в целом является сопрягаемым. Тем не менее в этом случае автоматическое создание реализации сопряжения завершится ошибкой. По этой причине можно указать реализацию вручную. Компилятор автоматически создает оптимизированные реализации для распространенных шаблонов, таких как конъюгации. Тем не менее может быть желательна явная специализация для определения оптимизированной реализации вручную. Явным образом можно задать любую реализацию и любое количество реализаций.

Примечание

Правильность указанной вручную реализации не проверяется компилятором.

В приведенном ниже примере в объявлении операции SWAP, с помощью которой два кубита q1 и q2 обмениваются состояниями, явно объявляется специализация для сопряженной версии и контролируемой версии. Хотя реализации для Adjoint SWAP и Controlled SWAP являются пользовательскими, компилятору по-прежнему требуется создать реализацию для сочетания обоих функторов (Controlled Adjoint SWAP, что аналогично Adjoint Controlled SWAP).

    operation SWAP (q1 : Qubit, q2 : Qubit) : Unit
    is Adj + Ctl { 

        body ... {
            CNOT(q1, q2);
            CNOT(q2, q1);
            CNOT(q1, q2);
        }

        adjoint ... { 
            SWAP(q1, q2);
        }

        controlled (cs, ...) { 
            CNOT(q1, q2);
            Controlled CNOT(cs, (q2, q1));
            CNOT(q1, q2);            
        } 
    }

Директивы автоматического создания

При определении способа создания конкретной специализации компилятор отдает приоритет пользовательским реализациям. Это означает, что если сопряженная специализация определена пользователем, а контролируемая создана автоматически, то контролируемая сопряженная специализация создается на основе пользовательского сопряжения и наоборот. В этом случае обе специализации пользовательские. Так как на автоматическое создание сопряженной реализации налагается больше ограничений, для контролируемой сопряженной специализации по умолчанию создается контролируемая специализация явно определенной реализации сопряженной специализации.

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

    controlled adjoint invert;

и вставляется внутри объявления SWAP. С другой стороны, добавление строки

    controlled adjoint distribute;

заставляет компилятор создать специализацию на основе определенной (или созданной) сопряженной специализации. Дополнительные сведения см. в предложении по частичному выводу специализации.

Для операции SWAP есть вариант лучше. Операция SWAP является самосопряженной, то есть обратной для самой себя. Определенная реализация сопряжения просто вызывает тело операции SWAP. Это выражается с помощью следующей директивы:

    adjoint self;

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

Следующие директивы создания являются допустимыми:

Специализация Директивы
Специализация body: -
Специализация adjoint: self, invert
Специализация controlled: distribute
Специализация controlled adjoint: self, invert, distribute

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

Помимо ранее перечисленных директив, директива auto допустима для всех специализаций, за исключением body; она указывает, что компилятор должен автоматически выбрать подходящую директиву создания. Объявление

    operation DoNothing() : Unit {
        body ... { }
        adjoint auto;
        controlled auto;
        controlled adjoint auto;
    }

эквивалентно

    operation DoNothing() : Unit 
    is Adj + Ctl { }

В аннотации is Adj + Ctl в этом примере указаны характеристики операции, которые содержат сведения о том, какие функторы поддерживает данная операция.

Хотя для удобства чтения рекомендуется самостоятельно сопровождать каждую операцию аннотацией с полным описанием ее характеристик, компилятор автоматически вставляет или дополняет аннотацию с учетом явно объявленных специализаций. И наоборот, компилятор создает специализации, которые не были объявлены явно, но должны существовать согласно характеристикам в аннотации. Мы говорим, что эти специализации неявно объявлены в аннотации. Компилятор автоматически создает необходимые специализации, если это возможно, выбирая подходящую директиву. Таким образом, Q# поддерживает вывод как характеристик операции, так и существующих специализаций на основе (частичных) аннотаций и явно определенных специализаций.

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