Partilhar via


Declarações de especialização

Conforme explicado na secção sobre declarações callable, atualmente não há razão para declarar explicitamente especializações para funções. Este tópico aplica-se a operações e explica como declarar as especializações necessárias para suportar determinados funtores.

É um problema bastante comum na computação quântica exigir o contíguo de uma determinada transformação. Muitos algoritmos quânticos requerem uma operação e o respetivo adjacente para realizar uma computação. Q# utiliza computação simbólica que pode gerar automaticamente a implementação adjacente correspondente para uma implementação corporal específica. Esta geração é possível mesmo para implementações que misturam gratuitamente cálculos clássicos e quânticos. No entanto, existem algumas restrições que se aplicam neste caso. Por exemplo, a geração automática não é suportada por motivos de desempenho se a implementação utilizar variáveis mutáveis. Além disso, cada operação chamada dentro do corpo gera as necessidades adjacentes correspondentes para suportar o Adjoint próprio functor.

Embora não se possa anular facilmente as medições no caso de vários qubits, é possível combinar medidas para que a transformação aplicada seja unitária. Neste caso, significa que, embora a implementação do corpo contenha medidas que por si só não suportam o Adjoint functor, o corpo na sua totalidade é adjacente. No entanto, a geração automática da implementação adjacente falhará neste caso. Por este motivo, é possível especificar manualmente a implementação. O compilador gera automaticamente implementações otimizadas para padrões comuns, como conjugações. No entanto, uma especialização explícita pode ser desejável para definir manualmente uma implementação mais otimizada. É possível especificar explicitamente qualquer implementação e qualquer número de implementações.

Nota

A correção de uma implementação especificada manualmente não é verificada pelo compilador.

No exemplo seguinte, a declaração para uma operação SWAP, que troca o estado de dois qubits q1 e q2, declara uma especialização explícita para a versão adjacente e a versão controlada. Embora as implementações para Adjoint SWAP e Controlled SWAP sejam, portanto, definidas pelo utilizador, o compilador ainda precisa de gerar a implementação para a combinação de ambos os functores (Controlled Adjoint SWAPque é o mesmo Adjoint Controlled SWAPque ).

    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);            
        } 
    }

Diretivas de geração automática

Ao determinar como gerar uma especialização específica, o compilador prioriza as implementações definidas pelo utilizador. Isto significa que, se uma especialização adjacente for definida pelo utilizador e uma especialização controlada for gerada automaticamente, a especialização adjacente controlada é gerada com base no adjacente definido pelo utilizador e vice-versa. Neste caso, ambas as especializações são definidas pelo utilizador. Uma vez que a geração automática de uma implementação adjacente está sujeita a mais limitações, a especialização adjacente controlada é predefinida para gerar a especialização controlada da implementação explicitamente definida da especialização adjacente.

No caso da SWAP implementação, a melhor opção é anexar a especialização controlada para evitar condicionar desnecessariamente a execução do primeiro e o último CNOT no estado dos qubits de controlo. Adicionar uma declaração explícita para a versão adjacente controlada que especifica uma diretiva de geração adequada força o compilador a gerar a especialização adjacente controlada com base na implementação especificada manualmente da versão controlada. Tal declaração explícita de uma especialização que deve ser gerada pelo compilador assume o formulário

    controlled adjoint invert;

e é inserido dentro da declaração de SWAP. Por outro lado, inserir a linha

    controlled adjoint distribute;

força o compilador a gerar a especialização com base na especialização (ou gerada) definida. Veja esta proposta de inferência de especialização parcial para obter mais detalhes.

Para a operação SWAP, existe uma opção melhor. SWAP é auto-adjacente, ou seja, é o seu próprio inverso; a implementação definida pelo contíguo chama apenas o corpo de SWAP. Expressa isto com a diretiva

    adjoint self;

Declarar a especialização adjacente desta forma garante que a especialização controlada adjacente que é inserida automaticamente pelo compilador apenas invoca a especialização controlada.

As seguintes diretivas de geração existem e são válidas:

Especialização Diretivas
body especialização: -
adjoint especialização: self, invert
controlled especialização: distribute
controlled adjoint especialização: self, invert, distribute

O facto de todas as directivas de geração serem válidas para uma especialização adjacente controlada não é uma coincidência; enquanto os functores se deslocam, o conjunto de diretivas de geração válidas para implementar a especialização para uma combinação de functores é sempre a união do conjunto de geradores válidos para cada um.

Para além das diretivas listadas anteriormente, a diretiva auto é válida para todas as especializações, exceto body; indica que o compilador deve escolher automaticamente uma diretiva de geração adequada. A declaração

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

é equivalente a

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

A anotação is Adj + Ctl neste exemplo especifica as características da operação, que contêm as informações sobre os functores suportados por uma determinada operação.

Embora, por uma questão de legibilidade, seja recomendado anotar cada operação com uma descrição completa das suas características, o compilador insere ou conclui automaticamente a anotação com base em especializações explicitamente declaradas. Por outro lado, o compilador também gera especializações que não foram declaradas explicitamente, mas que precisam de existir com base nas características anotadas. Dizemos que a anotação especificada declarou implicitamente estas especializações. O compilador gera automaticamente as especializações necessárias, se possível, escolhendo uma diretiva adequada. Q# deste modo, suporta a inferência de características de operação e especializações existentes com base em anotações (parciais) e especializações explicitamente definidas.

De certa forma, as especializações são semelhantes a sobrecargas individuais para o mesmo callable, com a ressalva de que determinadas restrições se aplicam às sobrecargas que pode declarar.