Partilhar via


Tutorial: Implementar a Transformação Quantum Fourier no Q#

Nota

O Microsoft Quantum Development Kit (QDK Clássico) deixará de ser suportado após 30 de junho de 2024. Se for um programador de QDK existente, recomendamos que faça a transição para o novo Azure Quantum Development Kit (QDK Moderno) para continuar a desenvolver soluções quânticas. Para obter mais informações, veja Migrar o código Q# para o QDK Moderno.

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

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

Neste tutorial, irá aprender a:

  • Definir operações quânticas em Q#.
  • Escrever o circuito Quantum Fourier Transform
  • Simular uma operação quântica desde a alocação de qubits até à saída de medição.
  • Observe como a função de onda simulada do sistema quântico evolui ao longo da operação.

Nota

Esta vista de nível inferior do processamento de informações quânticas é frequentemente descrita em termos de circuitos quânticos, que representam a aplicação sequencial de portas, ou operações, para qubits específicos de um sistema. Assim, as operações de qubit único e multi-qubit que aplicar sequencialmente podem ser facilmente representadas em diagramas de circuitos. Por exemplo, a transformação fourier quântica completa de três qubits utilizada neste tutorial tem a seguinte representação como circuito: Diagrama de um circuito Quantum Fourier Transform.

Dica

Se quiser acelerar o seu percurso de computação quântica, consulte Código com o Azure Quantum, uma funcionalidade exclusiva do site do Azure Quantum. Aqui, pode executar exemplos incorporados Q# ou os seus próprios Q# programas, gerar novo Q# código a partir das suas instruções, abrir e executar o código no VS Code para a Web com um clique e fazer perguntas sobre computação quântica ao Copilot.

Pré-requisitos

Create um novo Q# ficheiro

  1. No VS Code, selecione Ficheiro > Novo Ficheiro de Texto
  2. Guarde o ficheiro como QFTcircuit.qs. Este ficheiro irá conter o Q# código do seu programa.
  3. Abra QFTcircuit.qs.

Escrever um circuito QFT em Q#

A primeira parte deste tutorial consiste na definição da Q# operação Perform3qubitQFT, que executa a transformação quântica fourier em três qubits. A DumpMachine função é utilizada 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, irá adicionar a funcionalidade de medição e comparar os estados pré e pós-medição dos qubits.

Irá criar a operação passo a passo. Copie e cole o código nas secções seguintes no ficheiro QFTcircuit.qs .

Pode ver o código completo Q# desta secção como referência.

Espaços de nomes para aceder a outras Q# operações

No seu Q# ficheiro, defina o espaço de nomes NamespaceQFT, que é acedido pelo compilador. Para que esta operação utilize operações existentes Q# , abra os espaços de nomes 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 devoluções

Em seguida, defina a Perform3qubitQFT operação:

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

Por agora, a operação não aceita argumentos e devolve um Unit objeto, que é análogo a regressar void em C# ou numa cadeia de identificação vazia, Tuple[()], em Python. Mais tarde, irá modificar a operação para devolver uma matriz de resultados de medição.

Alocar qubits

Na operação, aloque Q# um registo 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();

Tal como nos cálculos quânticos reais, Q# não lhe permite aceder diretamente a estados 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 utilizada em conjunto com o simulador de estado completo.

Aplicar operações de qubit único e controladas

Em seguida, aplique as operações que compõem a Perform3qubitQFT própria operação. Q# já contém estas e muitas outras operações quânticas básicas no Microsoft.Quantum.Intrinsic espaço de nomes.

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

Diagrama a mostrar um circuito para três QFT qubits através do primeiro Hadamard.

Para aplicar uma operação a um qubit específico a partir de um registo (por exemplo, um único Qubit de uma matriz Qubit[]), utilize a notação de índice padrão. Assim, a aplicação da H operação ao primeiro qubit do registo qs assume o formulário:

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 ao aplicar uma rotação de $e^{i\theta}$ ao componente $\ket{1}$.

Q# torna mais fácil condicionar a execução de uma operação num ou em múltiplos qubits de controlo. Em geral, a chamada é prefaciada com Controllede os argumentos de operação mudam da seguinte forma:

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

Tenha em atenção que o argumento qubit de controlo tem de 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 a mostrar um circuito para três qubit Quantum Fourier Transform através do primeiro qubit.

No seu Q# ficheiro, 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 é utilizada 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 ao segundo e terceiro qubits, o circuito tem o seguinte aspeto:

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

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

Por fim, aplica uma SWAP operação ao primeiro e terceiro qubits para concluir o circuito. Isto é necessário porque a natureza da transformação quântico Fourier produz os qubits por ordem inversa, pelo que as trocas permitem uma integração totalmente integrada da subroutina em algoritmos maiores.

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

Acabou de escrever as operações ao nível do qubit da transformação quantum Fourier na sua Q# operação:

Diagrama a mostrar um circuito para três qubit Quantum Fourier Transform.

Desalocar qubits

O último passo é ligar DumpMachine() novamente para ver o estado pós-operação e desalocar os qubits. Os qubits estavam no estado $\ket{0}$ quando os atribuiu e precisam de ser repostos para o estado inicial com a ResetAll operação.

Exigir que todos os qubits sejam explicitamente repostos para $\ket{0}$ é uma funcionalidade básica do Q#, uma vez que permite que outras operações conheçam o respetivo estado precisamente quando começam a utilizar esses mesmos qubits (um recurso escasso). Além disso, isto garante que não estão entrelaçados com outros qubits no sistema. Se a reposição não for efetuada no final de um use bloco de alocação, poderá ser emitido um erro de runtime.

Adicione as seguintes linhas ao seu Q# ficheiro:

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

ResetAll(qs); // deallocate qubits

A operação QFT completa

O Q# programa está concluído. O ficheiro QFTcircuit.qs deverá ter o seguinte aspeto:

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 QFT

Por enquanto, a Perform3qubitQFT operação não devolve nenhum valor – a operação devolve Unit o valor. Mais tarde, irá modificar a operação para devolver uma matriz de resultados de medição (Result[]).

  1. Ao executar um Q# programa, tem de adicionar um EntryPoint ao Q# ficheiro. Este atributo indica ao compilador que esta operação é o ponto de entrada do 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, tem de definir o target perfil como Sem Restrições. Selecione Ver -> Paleta de Comandos, procure QIR, selecione Q#: Defina o perfil QIR target do Azure Quantum e, em seguida, selecione Q#: sem restrições.

  3. Para executar o seu programa, selecione Executar Q# Ficheiro no menu pendente ícone de reprodução no canto superior direito ou prima Ctrl+F5. O programa executa a operação ou função marcada com o @EntryPoint() atributo no simulador predefinido.

  4. As Message saídas e DumpMachine aparecem na consola de depuração.

Nota

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

Compreender a saída do circuito QFT

Quando chamado no simulador de estado completo, DumpMachine() fornece estas múltiplas representações da função de onda do estado quântico. Os estados possíveis de um sistema de $n$-qubit podem ser representados por $2^n$ estados de base computacional, 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}$, em que cada dígito binário corresponde a um qubit individual.

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

As restantes linhas descrevem a amplitude de probabilidade de medir o vetor de estado de base $\ket{i}$ em formatos cartesianos e polares. A examinar a primeira linha do estado de entrada $\ket{000}$:

  • |0>: Esta linha corresponde ao 0 estado de base computacional (dado que o estado inicial após a alocação foi $\ket{000}$, espera-se que este seja o único estado com amplitude de probabilidade neste momento).
  • 1.000000 + 0.000000 i: a amplitude de probabilidade no formato cartesiano.
  • ==: o equal sinal separa ambas as representações equivalentes.
  • ********************: 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 apresentadas com uma representação gráfica. A representação de magnitude é simples: mostra uma barra de * e quanto maior for a probabilidade, mais longa será a barra.

O resultado apresentado 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}{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 transformação Fourier de três qubits.

Se estiver curioso sobre como outros estados de entrada são afetados, é recomendado experimentar a aplicação de outras operações de qubit antes da transformação.

Adicionar medidas ao circuito QFT

A apresentação da DumpMachine função 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 DumpMachine função. Em vez disso, as informações são extraídas através de medições que, em geral, não só não fornecem informações sobre o estado quântico completo, como também podem alterar drasticamente o próprio sistema.

Existem muitos tipos de medições quânticas, mas o exemplo aqui foca-se nas medidas mais básicas: projectivas em qubits únicos. Após a medição numa determinada base (por exemplo, a base computacional $ { \ket{0}, \ket{1} } $), o estado do qubit é projetado para o estado de base medido, destruindo assim qualquer sobreposição entre os dois.

Modificar a operação QFT

Para implementar medições num Q# programa, utilize a M operação, que devolve um Result tipo.

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

operation Perform3QubitQFT() : Result[] {

Definir e inicializar a Result[] matriz

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

mutable resultArray = [Zero, size = 3];

A mutable prefaciação de resultArray palavras-chave permite que a variável seja modificada mais tarde no código, por exemplo, ao adicionar os resultados da medição.

Executar medições num for ciclo 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 IndexRange função chamada numa matriz (por exemplo, a matriz de qubits, qs) devolve um intervalo sobre os índices da matriz. Aqui, é utilizado no for ciclo para medir sequencialmente cada qubit com a M(qs[i]) instrução . Cada tipo medido Result (ou ZeroOne) é adicionado à posição de índice correspondente em resultArray com uma instrução update-and-reassign.

Nota

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

A palavra-chave set é sempre utilizada para reatribuir variáveis vinculadas com mutable.

Devolver resultArray

Com os três qubits medidos e os resultados adicionados a resultArray, é seguro repor e desalocar os qubits como anteriormente. Para devolver as medidas, insira:

return resultArray;

Executar o circuito QFT com as medidas

Agora, altere o posicionamento das DumpMachine funções para exportar o estado antes e depois das medições. O código final Q# deverá ter o seguinte aspeto:

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 guardar o seu ficheiro sempre que introduzir uma alteração ao código antes de o executar 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 do VS Code e selecione Sem restrições no menu pendente. Se o target perfil não estiver definido como Sem restrições, receberá um erro quando executar o programa.

  3. Para executar o programa, selecione Executar Q# ficheiro no menu pendente do ícone de reprodução no canto superior direito ou prima Ctrl+5. O programa executa a operação ou função marcada com o @EntryPoint() atributo no simulador predefinido.

  4. As Message saídas e DumpMachine são apresentadas na consola de depuração.

O resultado deverá ter um aspeto semelhante ao resultado:

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]

Este resultado ilustra algumas coisas diferentes:

  1. Comparar o resultado devolvido com a pré-medição DumpMachine, não ilustra claramente a sobreposição pós-QFT sobre os estados de base. Uma medição devolve apenas um estado de base único, com uma probabilidade determinada pela amplitude desse estado na função de onda do sistema.
  2. A partir da pós-medição DumpMachine, verá que a medição altera o próprio estado, projetando-o da sobreposição inicial sobre os estados de base para o estado de base único que corresponde ao valor medido.

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

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

Como mencionado na introdução, grande parte do Q#poder depende do facto de lhe permitir abstrair as preocupações de lidar com qubits individuais. Na verdade, se quiser desenvolver programas quânticos aplicáveis em larga escala, preocupar-se com o facto de uma H operação ser executada antes ou depois de uma rotação específica apenas o atrasaria.

O Q# espaço de nomes Microsoft.Quantum.Canon contém a ApplyQFT operação, que pode utilizar e aplicar a qualquer número de qubits.

  1. Para aceder à operação, adicione open a ApplyQFT instrução para o Microsoft.Quantum.Canon espaço de nomes no início do Q# ficheiro:

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

    ApplyQFT(qs);
    
  3. Execute o Q# programa novamente e repare que o resultado é igual ao anterior.

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

mutable resultArray = [Zero, size = 4];

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

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

Passos seguintes

Explore outros Q# tutoriais:

  • O gerador quântico de números aleatórios mostra como escrever um Q# programa que gera números aleatórios a partir de qubits em sobreposição.
  • O algoritmo de pesquisa de Grover mostra como escrever um Q# programa que utiliza 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 entrelaçamento.
  • Os Quantum Katas são tutoriais personalizados e exercícios de programação destinados a ensinar os elementos da computação quântica e Q# da programação ao mesmo tempo.