Partilhar via


Tutorial: Implementar a transformada quântica de Fourier em Q#

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

Embora Q# tenha sido criada principalmente como uma linguagem de programação de alto nível para programas quânticos de grande escala, ela também pode ser usada para explorar o nível inferior de programação quântica, ou seja, abordar diretamente qubits específicos. Especificamente, este tutorial analisa mais de perto a Transformada Quântica de Fourier (QFT), uma sub-rotina que é parte integrante de muitos algoritmos quânticos maiores.

Neste tutorial, irá aprender a:

  • Definir operações quânticas em Q#.
  • Escreva o circuito da Transformada Quântica de Fourier
  • Simule uma operação quântica desde a alocação do qubit até a saída da medição.
  • Observe como a função de onda simulada do sistema quântico evolui ao longo da operação.

Nota

Esta visão de nível inferior do processamento quântico de informação é frequentemente descrita em termos de circuitos quânticos, que representam a aplicação sequencial de portas, ou operações, a qubits específicos de um sistema. Assim, as operações de qubit único e múltiplo que você aplica sequencialmente podem ser prontamente representadas em diagramas de circuito. Por exemplo, a transformada quântica de Fourier completa de três qubits usada neste tutorial tem a seguinte representação como um circuito: Diagrama de um circuito de transformada quântica de Fourier.

Gorjeta

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

Pré-requisitos

Criar um novo Q# ficheiro

  1. Em VS Code, selecione Arquivo > Novo Arquivo de Texto
  2. Salve o arquivo como QFTcircuit.qs. Este ficheiro irá conter o código para o Q# seu programa.
  3. Abra QFTcircuit.qs.

Escreva um circuito QFT em Q#

A primeira parte deste tutorial consiste em definir a Q# operação Perform3qubitQFT, que executa a transformada quântica de Fourier em três qubits. A DumpMachine função é usada para observar como a função de onda simulada do sistema de três qubits evolui ao longo da operação. Na segunda parte do tutorial, você adicionará a funcionalidade de medição e comparará os estados pré e pós-medição dos qubits.

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

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

Namespaces para acessar outras Q# operações

Dentro do seu Q# arquivo, defina o namespace NamespaceQFT, que é acessado pelo compilador. Para que esta operação faça uso de operações existentes Q# , abra os namespaces relevantes Microsoft.Quantum.* .

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 retornos

Em seguida, defina a Perform3qubitQFT operação:

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

Por enquanto, a operação não usa argumentos e retorna um Unit objeto, que é análogo ao retorno void em C# ou uma tupla vazia, Tuple[()]em Python. Mais tarde, você modificará 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 a use palavra-chave. Com useo , os qubits são alocados automaticamente no estado $\ket{0}$.

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

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

Como em cálculos quânticos reais, Q# não permite que você acesse diretamente estados de qubit. No entanto, a DumpMachine operação imprime o target estado atual da máquina, para que possa fornecer informações valiosas para depuração e aprendizagem quando usado em conjunto com o simulador de estado completo.

Aplique operações controladas e de qubit único

Em seguida, você aplica as operações que compõem a Perform3qubitQFT operação em si. Q# já contém essas e muitas outras operações quânticas básicas no Microsoft.Quantum.Intrinsic namespace.

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

Diagrama mostrando um circuito para QFT de três qubits através do primeiro Hadamard.

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

H(qs[0]);

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

Q# torna mais fácil condicionar a execução de uma operação sobre um, ou vários, qubits de controle. Em geral, a chamada é precedida de Controlled, e os argumentos da operação mudam da seguinte maneira:

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

Observe que o argumento qubit de controle deve ser uma matriz, mesmo que seja para um único 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 qubit Quantum Fourier Transform através do primeiro qubit.

Em seu Q# arquivo, chame estas 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 PI() função é usada para definir as rotações em termos de radianos pi.

Aplicar operação SWAP

Depois de aplicar as operações relevantes H e rotações controladas para o segundo e terceiro qubits, o circuito fica assim:

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

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

Finalmente, você aplica uma SWAP operação ao primeiro e terceiro qubits para completar o circuito. Isso é necessário porque a natureza da transformada quântica de Fourier produz os qubits em ordem inversa, de modo que as trocas permitem a integração perfeita da sub-rotina em algoritmos maiores.

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

Agora você terminou de escrever as operações de nível qubit da transformação quântica de Fourier em sua Q# operação:

Diagrama mostrando um circuito para a Transformada Quântica de Fourier de três qubits.

Desalocar qubits

A última etapa é ligar 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 precisam ser redefinidos para seu estado inicial usando a ResetAll operação.

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

Adicione as seguintes linhas ao seu Q# ficheiro:

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

ResetAll(qs); // deallocate qubits

A operação completa do QFT

O Q# programa está 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

    }
}

Execute o circuito QFT

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

  1. Ao executar um Q# programa, você precisa adicionar um EntryPoint ao Q# arquivo. Este atributo informa ao compilador que esta operação é o ponto de entrada para o programa. Adicione a seguinte linha à parte superior do ficheiro Q# 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, procure QIR, selecione Q#: Definir o perfil do Azure Quantum QIR target e, em seguida, selecione Q#: irrestrito.

  3. Para executar o programa, selecione Executar arquivo na lista suspensa do ícone de reprodução no canto superior direito ou pressione Ctrl+F5. Q# 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.

Nota

Se o target perfil não estiver definido como Sem restrições, você receberá um erro ao executar o programa.

Compreender a saída do circuito QFT

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

A primeira linha fornece um comentário com os IDs dos qubits correspondentes em sua ordem significativa. Qubit 2 sendo 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 resto das linhas descreve a amplitude de probabilidade de medir o vetor de estado base $\ket{i}$ em ambos os formatos cartesiano e polar. Examinando a primeira linha para o estado de entrada $\ket{000}$:

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

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

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

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

para

$$ \begin{align} \ket{\psi}_{final} &= \frac{1}{\sqrt{8}} \left( \ket{000} + \ket + \ket{001} + \ket{010} + \ket{011} + \{100} ket + \ket{101} + \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, você é encorajado a experimentar a aplicação de outras operações de qubit antes da transformação.

Adicionar medições ao circuito QFT

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

Existem muitos tipos de medições quânticas, mas o exemplo aqui se concentra no mais básico: medições projetivas em qubits únicos. Após a medição em uma determinada base (por exemplo, a base computacional $ { \ket{0}, \ket{1} } $), o estado qubit é projetado em qualquer estado base que foi medido, destruindo assim qualquer superposição entre os dois.

Modificar a operação QFT

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

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

operation Perform3QubitQFT() : Result[] {

Definir e inicializar Result[] matriz

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

mutable resultArray = [Zero, size = 3];

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

Execute medições em um for loop e adicione resultados ao array

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

Nota

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

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

Regresso resultArray

Com todos os três qubits medidos e os resultados adicionados ao resultArray, você está seguro para redefinir e desalocar os qubits como antes. Para devolver as medidas, inserir:

return resultArray;

Execute o circuito QFT com as medições

Agora altere o DumpMachine posicionamento das funções para produzir o estado antes e depois das medições. Seu 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;

    }
}

Gorjeta

Lembre-se de salvar seu arquivo sempre que 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 Sem restrições. Clique no botão QIR: Base na parte inferior da janela VS Code e selecione Irrestrito no menu suspenso. Se o target perfil não estiver definido como Sem restrições, você receberá um erro ao executar o programa.

  3. Para executar o programa, selecione Executar Q# arquivo na lista suspensa do í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]

Esta saída ilustra algumas coisas diferentes:

  1. Comparando o resultado retornado com a pré-medição DumpMachine, ele claramente não ilustra a sobreposição pós-QFT sobre os estados de base. Uma medição retorna apenas um único estado base, com uma probabilidade determinada pela amplitude desse estado na função de onda do sistema.
  2. A partir da pós-medição DumpMachine, você vê que a medição muda o próprio estado, projetando-o da superposição inicial sobre estados de base para o estado de base único que corresponde ao valor medido.

Se você repetir esta operação muitas vezes, verá que as estatísticas de resultados começam a ilustrar a superposição igualmente ponderada do estado pós-QFT que dá origem a um resultado aleatório em cada disparo. No entanto, além de ser ineficiente e ainda imperfeito, isso apenas reproduziria as amplitudes relativas dos estados-base, não as fases relativas entre eles. Este último não é um problema neste exemplo, mas você veria fases relativas aparecerem se fosse dada uma entrada mais complexa para o QFT do que $\ket{000}$.

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

Como mencionado na introdução, muito do poder do reside no fato de Q#que ele permite que você abstraia as preocupações de lidar com qubits individuais. De fato, se você quiser desenvolver programas quânticos aplicáveis em escala real, preocupar-se se uma H operação vai antes ou depois de uma determinada rotação só iria atrasá-lo.

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 operação, adicione open instrução ApplyQFT para o Microsoft.Quantum.Canon namespace no início do Q# arquivo:

    open Microsoft.Quantum.Canon;
    
  2. Substitua tudo, do primeiro H ao 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 de usar Q# operações, altere o número de qubits para algo diferente de 3:

mutable resultArray = [Zero, size = 4];

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

Assim, você pode aplicar o QFT adequado para qualquer número de qubits, sem ter que se preocupar com a bagunça de novas H operações e rotações em cada qubit.

Explore outros Q# tutoriais:

  • Quantum random number generator mostra como escrever um Q# programa que gera números aleatórios a partir de qubits em superposição.
  • O algoritmo de pesquisa de Grover mostra como escrever um Q# programa que usa o algoritmo de pesquisa de Grover.
  • O entrelaçamento quântico mostra como escrever um Q# programa que manipula e mede qubits e demonstra os efeitos da sobreposição e do emaranhamento.
  • Os Quantum Katas são tutoriais individualizados e exercícios de programação destinados a ensinar os elementos da computação quântica e Q# programação ao mesmo tempo.