Tutorial: Implementar a transformação quantum fourier em Q#

Observação

O Microsoft Quantum Development Kit (QDK clássico) não terá mais suporte após 30 de junho de 2024. Se você for um desenvolvedor de QDK existente, recomendamos fazer a transição para o novo QDK (QDK Moderno) do Azure Quantum Development Kit para continuar desenvolvendo soluções quânticas. Para obter mais informações, consulte Migrar seu Q# código para o QDK moderno.

Este tutorial mostra como escrever e simular um programa quântico básico que opera em qubits individuais.

Embora Q# tenha sido criado principalmente como uma linguagem de programação de alto nível para programas quânticos de grande escala, ele também pode ser usado para explorar o nível inferior da programação quântica, ou seja, lidar diretamente com qubits específicos. Especificamente, este tutorial analisa mais detalhadamente a QFT (Quantum Fourier Transform), uma sub-rotina que é parte integrante de muitos algoritmos quânticos maiores.

Neste tutorial, você aprenderá como:

  • Definir operações quânticas em Q#.
  • Gravar o circuito de Transformação do Quantum Fourier
  • Simule uma operação quântica da alocação de qubit para a saída de medida.
  • Observe como a função de onda simulada do sistema quântico evolui ao longo da operação.

Observação

Essa exibição de nível mais baixo do processamento de informações quânticas geralmente é descrita em termos de circuitos quânticos, que representam a aplicação sequencial de portões ou as operações a qubits específicos de um sistema. Assim, as operações de um e de vários qubits que aplicamos sequencialmente podem ser prontamente representadas em diagramas de circuito. Por exemplo, a transformação fourier quântica completa de três qubits usada neste tutorial tem a seguinte representação como um circuito: Diagrama de um circuito de Transformação Quantum Fourier.

Dica

Se você quiser acelerar sua jornada de computação quântica, marcar Código com o Azure Quantum, um recurso exclusivo do site do Azure Quantum. Aqui, você pode executar exemplos internos Q# ou seus próprios Q# programas, gerar um novo Q# código de seus prompts, abrir e executar seu código no VS Code para a Web com um clique e fazer perguntas ao Copilot sobre computação quântica.

Pré-requisitos

Create um novo Q# arquivo

  1. No VS Code, selecione Arquivo > Novo Arquivo de Texto
  2. Salve o arquivo como QFTcircuit.qs. Esse arquivo conterá o código do Q# programa.
  3. Abra QFTcircuit.qs.

Gravar um circuito de QFT em Q#

A primeira parte deste tutorial consiste em definir a operação Q#Perform3qubitQFT, que executa a transformação quântica de Fourier em três qubits. A função DumpMachine será usada para observar como a wavefunction simulada de nosso sistema de três qubit evolui na operação. Na segunda parte do tutorial, você adicionará a funcionalidade de medida e vai comparar os estados pré e pós-medida dos qubits.

Você criará a operação passo a passo. Copie e cole o código nas seções a seguir no arquivo QFTcircuit.qs .

Você pode exibir o código completo Q# desta seção como referência.

Namespaces para acessar outras operações Q#

Dentro do seu arquivo Q#, defina o namespace NamespaceQFT que será acessado pelo compilador. Para que essa operação use operações Q# existentes, abra os namespaces Microsoft.Quantum.* relevantes.

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    // operations go here
}

Definir operações com argumentos e devoluções

Em seguida, defina a operação Perform3qubitQFT:

operation Perform3qubitQFT() : Unit {
    // do stuff
}

Por enquanto, a operação não usa argumentos e retorna um objeto Unit, que é análogo a retornar void em C# ou uma tupla vazia, Tuple[()], em Python. Primeiro, modifique a operação para retornar uma matriz de resultados de medição.

Alocar qubits

Dentro da Q# operação, aloque um registro de três qubits com o use palavra-chave. Com use, os qubits são alocados automaticamente no estado $\ket {0}$.

use qs = Qubit[3]; // allocate three qubits

Message("Initial state |000>:");
DumpMachine();

Assim como uma computação quântica real, Q# não nos permite acessar diretamente os estados de qubit. No entanto, a DumpMachine operação imprime o target estado atual do computador, para que ele possa fornecer insights valiosos para depuração e aprendizado quando usado em conjunto com o simulador de estado completo.

Aplicar operações controladas e qubit único

Em seguida, aplique as operações que compõem a Perform3qubitQFT própria operação. Q# já contém essas e muitas outras operações quantum básicas no namespace Microsoft.Quantum.Intrinsic.

A primeira operação aplicada é a H operação (Hadamard) no primeiro qubit:

Diagrama mostrando um circuito para três QFT qubit até o primeiro Hadamard.

Para aplicar uma operação a um qubit específico de um registro (por exemplo, um só Qubit de uma matriz Qubit[]), use a notação de índice padrão. Portanto, aplicar a operação H ao primeiro qubit do registro qs assume a forma:

H(qs[0]);

Além de aplicar a operação H a qubits individuais, o circuito QFT consiste principalmente em rotações R1controladas. Uma R1(θ, <qubit>) operação em geral deixa o componente $\ket{0}$ do qubit inalterado ao aplicar uma rotação de $e^{i\theta}$ ao componente $\ket{1}$.

Q# torna extremamente fácil condicionar a execução de uma operação em um ou vários qubits de controle. Em geral, a chamada é simplesmente precedida com Controlled, e os argumentos da operação são alterados como abaixo:

Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))

Observe que os qubits de controle devem ser fornecidos como uma matriz, mesmo que sejam apenas um qubit.

As operações controladas no QFT são as R1 operações que atuam no primeiro qubit (e controladas pelo segundo e terceiro qubits):

Diagrama mostrando um circuito para três qubits Quantum Fourier Transform por meio do primeiro qubit.

No seu arquivo Q#, chame essas operações com estas instruções:

Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));

A função PI() é usada para definir as rotações em termos de pi radianos.

Aplicar operação SWAP

Depois de aplicar as operações relevantes H e as rotações controladas ao segundo e terceiro qubits, o circuito tem esta aparência:

//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));

//third qubit:
H(qs[2]);

Por fim, você aplica uma SWAP operação ao primeiro e terceiro qubits para concluir o circuito. Isso é necessário porque a natureza da transformação quântica de Fourier gera o qubits na ordem inversa, portanto, as trocas permitem a integração direta da sub-rotina a algoritmos maiores.

SWAP(qs[2], qs[0]);

Agora, você terminou de escrever as operações em nível de qubit da Transformada Quântica de Fourier na operação Q#:

Diagrama mostrando um circuito para três qubits Quantum Fourier Transform.

Desalocar qubits

A última etapa é chamar DumpMachine() novamente para ver o estado pós-operação e desalocar os qubits. Os qubits estavam no estado $\ket{0}$ quando você os alocou e precisa ser redefinido para o estado inicial usando a operação ResetAll.

Exigir que todos os qubits sejam redefinidos explicitamente para $\ket{0}$ é um recurso básico do Q#, pois permite que outras operações conheçam seu estado precisamente quando começam a usar esses mesmos qubits (um recurso escasso). Além disso, isso garante que eles não sejam entrelaçados com nenhum outro qubit no sistema. Se a redefinição não for executada no final de um bloco de alocação use, um erro de runtime poderá ser gerado.

Adicione as seguintes linhas ao seu arquivo Q#:

Message("After:");
DumpMachine();

ResetAll(qs); // deallocate qubits

A operação de QFT completa

O Q# programa foi concluído. Seu arquivo QFTcircuit.qs agora deve ter esta aparência:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3qubitQFT() : Unit {

        use qs = Qubit[3]; // allocate three qubits

        Message("Initial state |000>:");
        DumpMachine();

        //QFT:
        //first qubit:
        H(qs[0]);
        Controlled R1([qs[1]], (PI()/2.0, qs[0]));
        Controlled R1([qs[2]], (PI()/4.0, qs[0]));

        //second qubit:
        H(qs[1]);
        Controlled R1([qs[2]], (PI()/2.0, qs[1]));

        //third qubit:
        H(qs[2]);

        SWAP(qs[2], qs[0]);

        Message("After:");
        DumpMachine();

        ResetAll(qs); // deallocate qubits

    }
}

Executar o circuito de QFT

Por enquanto, a Perform3qubitQFT operação não retorna nenhum valor – a operação retorna Unit valor. Posteriormente, você modificará a operação para retornar uma matriz de resultados de medida (Result[]).

  1. Ao executar um Q# programa, você precisa adicionar um EntryPoint ao Q# arquivo. Esse atributo informa ao compilador que essa operação é o ponto de entrada do programa. Adicione a seguinte linha à parte superior do Q# arquivo antes da Perform3qubitQFT operação :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Antes de executar o programa, você precisa definir o target perfil como Irrestrito. Selecione Exibir –> Paleta de Comandos, pesquise QIR, selecioneQ# : Defina o perfil qir target do Azure Quantum e selecione Q#: irrestrito.

  3. Para executar o programa, selecione Executar Q# Arquivo na lista suspensa ícone de reprodução no canto superior direito ou pressione Ctrl+F5. O programa executa a operação ou função marcada com o @EntryPoint() atributo no simulador padrão.

  4. As Message saídas e DumpMachine aparecem no console de depuração.

Observação

Se o target perfil não estiver definido como Irrestrito, você receberá um erro ao executar o programa.

Entender a saída do circuito de QFT

Quando chamado no simulador de estado completo, DumpMachine() fornece essas várias representações da wavefunction do estado quântico. Os possíveis estados de um sistema de $n$-qubit podem ser representados por estados de base computacional $2^n$, cada um com um coeficiente complexo correspondente (simplesmente uma amplitude e uma fase). Os estados de base computacional correspondem a todas as possíveis cadeias de caracteres binárias de comprimento $n$. Ou seja, todas as combinações possíveis de estados qubit $\ket{0}$ e $\ket{1}$, em que cada dígito binário corresponde a um qubit individual.

A primeira linha fornece um comentário com as IDs dos qubits correspondentes na ordem significativa. O qubit 2 ser o "mais significativo" significa que, na representação binária do vetor de estado base $\ket{i}$, o estado de qubit 2 corresponde ao dígito mais à esquerda. Por exemplo, $\ket{6} = \ket{110}$ compreende qubits 2 e 1, ambos em $\ket{1}$ e qubit 0 em $\ket{0}$.

O restante das linhas descreve a amplitude de probabilidade de medir o vetor de estado base $\ket{i}$ nos formatos cartesiano e polar. Como examinar a primeira linha do estado de entrada $\ket{000}$:

  • |0>: Essa linha corresponde ao estado de base computacional 0 (considerando que nossa pós-alocação de estado inicial era $\ket{000}$, esperamos que esse seja o único estado com amplitude de probabilidade neste ponto).
  • 1.000000 + 0.000000 i: a amplitude de probabilidade no formato cartesiano.
  • == : o sinal equal separa as representações equivalentes.
  • ********************: uma representação gráfica da magnitude. O número de * é proporcional à probabilidade de medir esse vetor de estado.
  • [ 1.000000 ]: o valor numérico da magnitude.
  • --- : uma representação gráfica da fase de amplitude.
  • [ 0.0000 rad ]: o valor numérico da fase (em radianos).

Tanto a magnitude quanto a fase são exibidas com uma representação gráfica. A representação de magnitude é simples: ela mostra uma barra de * e, quanto maior a probabilidade, mais comprida a barra.

A saída exibida ilustra que as operações programadas transformaram o estado de

$$ \ket{\psi}_{initial} = \ket{000} $$

como

$$ \begin{align} \ket{\psi}_{final} &= \frac{1}{\sqrt{8}} \left( \ket{000} + \ket{001} + \ket{010} + \ket + \ket{011} + \ket{100} + \ket{101} + \ket + \ket{110} + \ket{111} \right) \\ &= \frac{1}{\sqrt{2^n}}\sum_{j=0}^{2^n-1} \ket{j}, \end{align} $$

que é precisamente o comportamento da Transformada de Fourier de três qubits.

Se você estiver curioso sobre como outros estados de entrada são afetados, experimente aplicar outras operações qubit antes da transformada.

Adicionar medidas ao circuito QFT

A exibição da função DumpMachine mostrou os resultados da operação, mas, infelizmente, uma pedra angular da mecânica quântica afirma que um sistema quântico real não pode ter tal função DumpMachine. Em vez disso, as informações são extraídas por meio de medidas, o que, em geral, não apenas falha em fornecer informações sobre o estado quântico completo, como também pode alterar drasticamente o próprio sistema.

Há muitos tipos de medidas quânticas, mas este exemplo se concentra nas medidas mais básicas: medições projetivas em qubits únicos. Na medida em uma determinada base (por exemplo, a base computacional $ { \ket{0}, \ket{1} } $), o estado qubit é projetado para o estado base que foi medido, destruindo qualquer sobreposição entre os dois.

Modificar a operação de QFT

Para implementar medidas dentro de um programa Q#, use a operação M, que retorna um tipo Result.

Primeiro, modifique a operação Perform3QubitQFT para retornar uma matriz de resultados de medição, Result[] em vez de Unit.

operation Perform3QubitQFT() : Result[] {

Definir e inicializar a matriz Result[]

Antes de alocar qubits, declare e associe uma matriz de três elementos (uma Result para cada qubit):

mutable resultArray = [Zero, size = 3];

A palavra-chave mutable que precede resultArray permite que a variável seja reassociada posteriormente no código, por exemplo, ao adicionar os resultados da medição.

Executar medidas em um loop for e adicionar resultados à matriz

Após as operações de transformação QFT, insira o seguinte código:

for i in IndexRange(qs) {
    set resultArray w/= i <- M(qs[i]);
}

A função IndexRange chamada em uma matriz (por exemplo, a matriz de qubits, qs) retorna um intervalo sobre os índices da matriz. Aqui, ele é usado no loop for para medir sequencialmente cada qubit usando a instrução M(qs[i]). Cada tipo medido Result (Zero ou One) é então adicionado à posição de índice correspondente em resultArray com uma instrução update-and-reassign.

Observação

A sintaxe dessa instrução é exclusiva para Q#, mas corresponde à reatribuição de variável semelhante resultArray[i] <- M(qs[i]) vista em outras linguagens, como F# e R.

A palavra-chave set é sempre usada para reatribuir variáveis vinculadas usando mutable.

Retornar resultArray

Com todos os três qubits medidos e os resultados adicionados ao resultArray, é seguro redefinir e desalocar os qubits como antes. Para retornar as medições, insira:

return resultArray;

Executar o circuito de QFT com as medidas

Agora altere o posicionamento das funções DumpMachine para gerar como saída o estado antes e depois das medidas. O código final Q# deve ter esta aparência:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3QubitQFT() : Result[] {

        mutable resultArray = [Zero, size = 3];

        use qs = Qubit[3];

        //QFT:
        //first qubit:
        H(qs[0]);
        Controlled R1([qs[1]], (PI()/2.0, qs[0]));
        Controlled R1([qs[2]], (PI()/4.0, qs[0]));

        //second qubit:
        H(qs[1]);
        Controlled R1([qs[2]], (PI()/2.0, qs[1]));

        //third qubit:
        H(qs[2]);

        SWAP(qs[2], qs[0]);

        Message("Before measurement: ");
        DumpMachine();

        for i in IndexRange(qs) {
            set resultArray w/= i <- M(qs[i]);
        }

        Message("After measurement: ");
        DumpMachine();

        ResetAll(qs);
        Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
        return resultArray;

    }
}

Dica

Lembre-se de salvar o arquivo sempre que você introduzir uma alteração no código antes de executá-lo novamente.

  1. Adicione um EntryPoint antes da Perform3qubitQFT operação :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Defina o target perfil como Irrestrito. Clique no botão QIR: Base na parte inferior da janela do VS Code e selecione Irrestrito no menu suspenso. Se o target perfil não estiver definido como Irrestrito, você receberá um erro ao executar o programa.

  3. Para executar o programa, selecione Executar Q# arquivo na lista suspensa ícone de reprodução no canto superior direito ou pressione Ctrl+5. O programa executa a operação ou função marcada com o @EntryPoint() atributo no simulador padrão.

  4. As Message saídas e DumpMachine aparecem no console de depuração.

Sua saída deve ser semelhante à saída:

Before measurement: 
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|1>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|2>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|3>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|4>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|5>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|6>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|7>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
After measurement:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|1>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|2>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|3>:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
|4>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|5>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|6>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|7>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

Post-QFT measurement results [qubit0, qubit1, qubit2]: 
[One,One,Zero]

Essa saída ilustra alguns pontos diferentes:

  1. Comparando o resultado retornado à medida anterior DumpMachine, ele claramente não ilustra a sobreposição QFT em relação a estados de base. Uma medida retorna apenas um estado de base, com uma probabilidade determinada pela amplitude desse estado na wavefunction do sistema.
  2. Da pós-medida DumpMachine, você verá que a medição altera o estado em si, projetando-a da sobreposição inicial até os estados de base e até o estado de base único que corresponde ao valor medido.

Se repetir essa operação muitas vezes, verá que as estatísticas de resultado começam a ilustrar a superposição de mesmo peso do estado pós-QFT que dá origem a um resultado aleatório em cada imagem. No entanto, além de ineficientes e ainda imperfeitos, isso só reproduziria as amplitudes relativas dos estados de base, não as fases relativas entre eles. Este não é um problema neste exemplo, mas veja que as fases relativas apareceriam se uma entrada mais complexa do que $\ket{000}$ fosse fornecida ao QFT.

Usar as Q# operações para simplificar o circuito de QFT

Como mencionado na introdução, grande parte do poder de Q# está no fato de que ele permite abstrair as preocupações de lidar com qubits individuais. Na verdade, se você quiser desenvolver programas quânticos que se aplicam em escala completa, se preocupar com o fato de que uma operação H vai antes ou depois de uma rotação específica servirá apenas para desacelerar o processo.

O Q# namespace Microsoft.Quantum.Canon contém a ApplyQFT operação , que você pode usar e aplicar para qualquer número de qubits.

  1. Para acessar a ApplyQFT operação, adicione open a instrução para o Microsoft.Quantum.Canon namespace no início do Q# arquivo:

    open Microsoft.Quantum.Canon;
    
  2. Substitua tudo do primeiro H para o SWAP substituído por:

    ApplyQFT(qs);
    
  3. Execute o Q# programa novamente e observe que a saída é a mesma de antes.

  4. Para ver o benefício real do uso Q# de operações, altere o número de qubits para algo diferente 3de :

mutable resultArray = [Zero, size = 4];

use qs = Qubit[4];
//...

Portanto, você pode aplicar o QFT apropriado a qualquer número determinado de qubits sem precisar se preocupar com confusão de novas operações H e rotações em cada qubit.

Próximas etapas

Explore outros tutoriais Q#:

  • O gerador de número aleatório quântico mostra como gravar um Q# programa que gera números aleatórios de qubits na superposição.
  • O algoritmo de pesquisa de Grover mostra como escrever um Q# programa que usa o algoritmo de pesquisa de Grover.
  • O emaranhamento quântico mostra como escrever um Q# programa que manipula e mede qubits e demonstra os efeitos da superposição e do emaranhamento.
  • As Katas Quânticas são tutoriais e exercícios de programação com o objetivo de ensinar os elementos da computação quântica e Q# da programação ao mesmo tempo.