Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Conforme explicado na seção sobre declarações chamáveis, atualmente não há razão para declarar explicitamente especializações para funções. Este tópico se aplica a operações e detalha como declarar as especializações necessárias para dar suporte a determinados functors.
É um problema bastante comum na computação quântica exigir o adjacente de uma determinada transformação. Muitos algoritmos quânticos exigem uma operação e seu adjacente para executar uma computação.
Q# emprega a computação simbólica que pode gerar automaticamente a implementação adjacente correspondente para uma implementação de corpo específica. Essa geração é possível até mesmo para implementações que misturam livremente cálculos clássicos e quânticos. No entanto, há algumas restrições que se aplicam nesse caso. Por exemplo, não há suporte para geração automática por motivos de desempenho se a implementação usa variáveis mutáveis. Além disso, cada operação chamada dentro do corpo gera as necessidades adjacentes correspondentes para dar suporte ao próprio Adjoint functor.
Embora não seja possível desfazer facilmente as medidas no caso de vários qubits, é possível combinar medidas para que a transformação aplicada seja unitária. Nesse caso, significa que, embora a implementação do corpo contenha medidas que por si só não dão suporte ao functor Adjoint, o corpo em sua totalidade é adjacente. No entanto, a geração automática da implementação adjacente falha nesse 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 correção de uma implementação especificada manualmente.
No exemplo a seguir, a declaração de uma operação SWAP, que troca o estado de dois qubits q1 e q2, declara uma especialização explícita para sua versão adjacente e sua versão controlada. Embora as implementações para Adjoint SWAP e Controlled SWAP sejam 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 adjacente for definida pelo usuário e uma especialização controlada for gerada automaticamente, a especialização adjacente controlada será gerada com base no adjacente definido pelo usuário e vice-versa. Nesse caso, ambas as especializações são definidas pelo usuário. Como a geração automática de uma implementação adjacente está sujeita a mais limitações, a especialização adjacente controlada é padrão para gerar a especialização controlada da implementação explicitamente definida da especialização adjacente.
No caso da implementação do SWAP, a melhor opção é se associar à especialização controlada para evitar condicioná-lo desnecessariamente à execução da primeira e da última CNOT no estado dos qubits de controle.
Adicionar uma declaração explícita para a versão adjacente controlada que especifica uma diretiva de geração de adequada força o compilador a gerar a especialização adjacente controlada com base na implementação especificada manualmente da versão controlada. Essa declaração explícita de uma especialização gerada pelo compilador assume o formulário
controlled adjoint invert;
e é inserido dentro da declaração de SWAP.
Por outro lado, inserindo a linha
controlled adjoint distribute;
força o compilador a gerar a especialização com base na especialização adjacente definida (ou gerada). Para obter mais informações, consulte esta inferência parcial de especialização proposta para obter mais detalhes.
Para a operação SWAP, há uma opção melhor.
SWAP é auto-adjacente, ou seja, é seu próprio inverso; a implementação definida do adjacente apenas chama o corpo de SWAP e é expressa com a diretiva
adjoint self;
Declarar a especialização adjacente dessa maneira garante que a especialização adjacente 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 | Directivas |
|---|---|
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 adjacente controlada não é uma coincidência; Enquanto os functors se deslocarem, o conjunto de diretivas de geração válidas para implementar a especialização para uma combinação de functors é sempre a união do conjunto de geradores válidos para cada um deles.
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 { }
O is Adj + Ctl de anotação neste exemplo especifica as características da operação , que contêm as informações sobre quais functors uma operação específica dá suporte.
Embora, para fins de legibilidade, é recomendável anotar cada operação com uma descrição completa de suas características, o compilador insere ou conclui automaticamente a anotação com base em especializações declaradas explicitamente. 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 fornecida declara implicitamente essas especializações. O compilador gera automaticamente as especializações necessárias se puder, escolhendo uma diretiva adequada. Q#, portanto, dá suporte à 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 a quais sobrecargas você pode declarar.