Partilhar via


Declarações de especialização

Conforme explicado na seção sobre declarações exigíveis, atualmente não há razão para declarar explicitamente especializações para funções. Este tópico se aplica a operações e elabora sobre como declarar as especializações necessárias para dar suporte a determinados functores.

É um problema bastante comum na computação quântica exigir o adjunto de uma determinada transformação. Muitos algoritmos quânticos requerem uma operação e seu adjunto para realizar um cálculo. Q# emprega computação simbólica que pode gerar automaticamente a implementação adjunta correspondente para uma implementação de corpo específico. Esta geração é possível mesmo para implementações que misturam livremente cálculos clássicos e quânticos. Existem, no entanto, 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 fizer uso de variáveis mutáveis. Além disso, cada operação chamada dentro do corpo gera as necessidades adjuntas correspondentes para apoiar o próprio functor Adjoint.

Mesmo que não se possa facilmente desfazer medições no caso de vários qubits, é possível combinar medições para que a transformação aplicada seja unitária. Neste caso, significa que, mesmo que a implementação do corpo contenha medidas que, por si só, não suportam o functor Adjoint, o corpo em sua totalidade é adjunto. No entanto, a geração automática da implementação conjunta falha neste caso. Por esse 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 uma implementação mais otimizada manualmente. É possível especificar qualquer implementação e qualquer número de implementações explicitamente.

Observação

O compilador não verifica a exatidão de tal implementação especificada manualmente.

No exemplo a seguir, 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 sua versão adjoint e sua versão controlada. Enquanto as implementações para Adjoint SWAP e Controlled SWAP são, portanto, definidas pelo usuário, o compilador ainda precisa gerar a implementação para a combinação de ambos os functors (Controlled Adjoint SWAP, que é o mesmo que 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);            
        } 
    }

Diretivas de geração automática

Ao determinar como gerar uma especialização específica, o compilador prioriza implementações definidas pelo usuário. Isso significa que, se uma especialização adjoint é definida pelo usuário e uma especialização controlada é gerada automaticamente, então a especialização adjoint controlada é gerada com base no adjoint definido pelo usuário e vice-versa. Neste caso, ambas as especializações são definidas pelo usuário. Como a geração automática de uma implementação adjoint está sujeita a mais limitações, a especialização adjunta controlada usa como padrão gerar a especialização controlada da implementação explicitamente definida da especialização adjunta.

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

    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 adjoint definida (ou gerada). Para obter mais informações, consulte esta inferência de especialização parcial proposta para obter mais detalhes.

Para a operação SWAP, há uma opção melhor. SWAP é autoadjunto, isto é, é o seu próprio inverso; A implementação definida do adjunto limita-se a chamar o corpo de SWAP e é expressa com a diretiva

    adjoint self;

Declarar a especialização adjoint dessa maneira garante que a especialização adjoint controlada que o compilador insere automaticamente apenas invoque 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

Que todas as diretivas de geração são válidas para uma especialização adjunta controlada não é uma coincidência; Enquanto os functores se deslocam, o conjunto de diretrizes 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 anteriormente enumeradas, a diretiva auto é válida para todas as especializações, exceto body; ele 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 { }

O is Adj + Ctl de anotação neste exemplo especifica as características operação, que contêm as informações sobre quais functores uma determinada operação suporta.

Enquanto que, por uma questão de legibilidade, recomendamos que você anote cada operação com uma descrição completa de suas características, o compilador insere ou completa automaticamente a anotação com base em especializações explicitamente declaradas. Por outro lado, o compilador também gera especializações que não são declaradas explicitamente, mas precisam existir com base nas características anotadas. Dizemos que a anotação dada declara implicitamente essas especializações. O compilador gera automaticamente as especializações necessárias, se puder, escolhendo uma diretiva adequada. Q#, portanto, 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.

Em certo sentido, as especializações são semelhantes a sobrecargas individuais para o mesmo chamável, com a ressalva de que certas restrições se aplicam a quais sobrecargas você pode declarar.