Pular para o conteúdo principal

Computações clássicas em computadores quânticos

Vamos agora voltar nossa atenção para a implementação de algoritmos clássicos em computadores quânticos. Veremos que qualquer computação que pode ser realizada com um circuito booleano clássico também pode ser realizada por um circuito quântico com um custo computacional assintótico similar. Além disso, isso pode ser feito de forma "limpa", como descreveremos em breve, o que é um requisito importante para usar essas computações como sub-rotinas dentro de computações quânticas maiores.

Simulando circuitos booleanos com circuitos quânticos

Circuitos booleanos são compostos de portas AND, OR, NOT e FANOUT. Para simular circuitos booleanos com circuitos quânticos, começaremos mostrando como cada uma dessas quatro portas pode ser simulada por portas quânticas. Feito isso, converter um circuito booleano em um circuito quântico é uma simples questão de simular uma porta por vez. Precisaremos apenas de portas NOT, portas CNOT (NOT controlado) e portas de Toffoli para isso, que são operações determinísticas além de serem unitárias.

Portas de Toffoli

As portas de Toffoli podem ser descritas alternativamente como portas NOT duplamente controladas (controlled-controlled-NOT), cuja ação sobre estados da base padrão é mostrada na figura a seguir.

Porta de Toffoli

Tendo em mente que estamos usando a convenção de ordenação do Qiskit, onde os qubits são ordenados em ordem crescente de significância de cima para baixo, a representação matricial dessa porta é a seguinte.

(1000000001000000001000000000000100001000000001000000001000010000)\begin{pmatrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \end{pmatrix}

Outra forma de pensar sobre as portas de Toffoli é que elas são essencialmente portas de consulta para a função AND, no sentido de que seguem o padrão visto na lição anterior para implementações unitárias de porta de consulta de funções arbitrárias com entradas e saídas em strings binárias.

As portas de Toffoli não estão incluídas no conjunto de portas padrão discutido anteriormente na lição, mas é possível construir uma porta de Toffoli a partir de portas H,H, T,T, TT^{\dagger} e CNOT da seguinte forma.

Circuito quântico para uma porta de Toffoli

Simulando portas booleanas com portas de Toffoli, CNOT e NOT

Uma única porta de Toffoli, usada em conjunto com algumas portas NOT, pode implementar portas AND e OR, e portas FANOUT podem ser facilmente implementadas usando portas CNOT, como sugerem os diagramas a seguir.

Simulando portas AND e OR com portas de Toffoli

Nos três casos, os qubits sobre os quais as portas AND, OR e FANOUT atuam chegam pela esquerda como entradas, e também precisamos de um qubit de espaço de trabalho (workspace) inicializado no estado zero para cada uma delas. Esses qubits de espaço de trabalho aparecem dentro das caixas que representam as implementações das portas para sugerir que são novos e, portanto, fazem parte do custo dessas implementações.

Para as portas AND e OR, também ficam dois qubits sobrando, além do qubit de saída. Por exemplo, dentro da caixa no diagrama que representa a simulação de uma porta AND, os dois qubits superiores ficam nos estados a\vert a\rangle e b.\vert b\rangle. Esses qubits são ilustrados como permanecendo dentro das caixas porque não são mais necessários e não fazem parte da saída. Eles podem ser ignorados por ora, embora voltemos nossa atenção a eles em breve.

A porta booleana restante, a porta NOT, está incluída em nosso conjunto padrão de portas quânticas, portanto não precisamos de uma simulação para ela.

Simulação porta a porta de circuitos booleanos

Agora suponha que temos um circuito booleano ordinário chamado C,C, composto de portas AND, OR, NOT e FANOUT, com nn bits de entrada e mm bits de saída. Seja t=size(C)t = \operatorname{size}(C) o número de portas em C,C, e vamos dar o nome ff à função que CC computa, que tem a forma

f:ΣnΣmf:\Sigma^n\rightarrow\Sigma^m

para Σ={0,1}.\Sigma = \{0,1\}.

Agora considere o que acontece quando percorremos uma a uma as portas AND, OR e FANOUT de C,C, substituindo cada uma pela simulação correspondente descrita acima, incluindo a adição dos qubits de espaço de trabalho necessários. Vamos nomear o circuito resultante R,R, e ordenar os qubits de RR de modo que os nn bits de entrada de CC correspondam aos nn qubits superiores de RR e os qubits de espaço de trabalho fiquem na parte inferior.

Simulação de circuito reversível

Aqui, kk é o número de qubits de espaço de trabalho necessários — um para cada porta AND, OR e FANOUT de CC — e gg é uma função da forma g:ΣnΣn+kmg:\Sigma^n \rightarrow \Sigma^{n+k-m} que descreve os estados dos qubits restantes criados pelas simulações de porta após a execução de RR. Na figura, os qubits correspondentes à saída f(x)f(x) estão na parte superior e os qubits restantes que armazenam g(x)g(x) estão na parte inferior. Podemos forçar que isso aconteça, se desejarmos, reorganizando os qubits usando portas SWAP, que podem ser implementadas com três portas CNOT desta forma:

Troca com portas CNOT

Como veremos na próxima seção, não é realmente essencial reorganizar os qubits de saída dessa forma, mas é simples o suficiente fazê-lo se escolhermos.

A função gg que descreve os estados clássicos dos qubits restantes é determinada pelo circuito C,C, mas na verdade não precisamos nos preocupar muito com ela; não nos importamos especificamente com qual estado esses qubits estão quando a computação termina. A letra gg vem depois de f,f, então é um nome razoável para essa função por conta disso, mas há uma razão melhor para escolher o nome gg — é abreviação de lixo (garbage).

Limpando o lixo

Se nosso único interesse é avaliar a função ff computada por um dado circuito booleano CC com um circuito quântico, não precisamos ir além da simulação porta a porta recém-descrita. Isso significa que, além da saída da função, teremos uma porção de lixo sobrando.

No entanto, isso não é suficiente se quisermos realizar computações clássicas como sub-rotinas dentro de computações quânticas maiores, pois esses qubits de lixo causarão problemas. O fenômeno de interferência é de importância crítica para algoritmos quânticos, e os qubits de lixo podem arruinar os padrões de interferência necessários para que os algoritmos quânticos funcionem.

Felizmente, não é difícil limpar o lixo, por assim dizer. A chave é usar o fato de que, como RR é um circuito quântico, podemos executá-lo em sentido inverso, simplesmente substituindo cada porta pelo seu inverso e aplicando-as na ordem inversa, obtendo assim um circuito quântico para a operação R.R^{\dagger}. As portas de Toffoli, CNOT e NOT são, na verdade, seus próprios inversos, então executar RR em sentido inverso é realmente apenas uma questão de aplicar as portas na ordem inversa — mas, de forma mais geral, qualquer circuito quântico pode ser invertido como descrito.

Especificamente, o que podemos fazer é adicionar mais mm qubits (lembrando que a função ff tem mm bits de saída), usar portas CNOT para copiar a saída de RR para esses qubits e inverter RR para limpar o lixo. A figura a seguir ilustra o circuito resultante e descreve sua ação sobre estados da base padrão.

Computação sem lixo

Se colocarmos uma caixa em volta de todo o circuito e o chamarmos de Q,Q, ele tem esta aparência:

Simulação como porta de consulta

Dado que CC tem tt portas, o circuito QQ terá O(t)O(t) portas.

Se desconsiderarmos os kk qubits adicionais de espaço de trabalho, o que temos é um circuito QQ que funciona exatamente como uma porta de consulta para a função f.f. Se simplesmente quisermos calcular a função ff em alguma string x,x, podemos definir y=0my = 0^m e o valor resultante f(x)f(x) aparecerá nos mm qubits inferiores — ou podemos alimentar um estado diferente nos mm qubits inferiores se desejarmos (talvez para fazer uso de um phase kickback, como no algoritmo de Deutsch ou no algoritmo de Deutsch-Jozsa).

Isso significa que, para qualquer algoritmo de consulta, se tivermos um circuito booleano que computa a função de entrada, podemos substituir cada porta de consulta por uma implementação em circuito, e o algoritmo de consulta funcionará corretamente.

Observe que os qubits de espaço de trabalho são necessários para fazer esse processo funcionar, mas são devolvidos aos seus estados iniciais após a execução do circuito combinado. Isso permite que esses qubits sejam usados novamente como qubits de espaço de trabalho para outros fins. Também existem estratégias conhecidas para reduzir o número de qubits de espaço de trabalho necessários (com o custo de tornar os circuitos maiores), mas não discutiremos essas estratégias aqui.

Implementando funções invertíveis

A construção recém-descrita nos permite simular qualquer circuito booleano com um circuito quântico de forma livre de lixo. Se CC é um circuito booleano que implementa uma função f:ΣnΣm,f:\Sigma^n \rightarrow \Sigma^m, obtemos um circuito quântico QQ que opera da seguinte forma sobre estados da base padrão.

Q(y0kx)=yf(x)0kxQ \bigl( \vert y \rangle \vert 0^k \rangle \vert x\rangle\bigr) = \vert y \oplus f(x) \rangle \vert 0^k \rangle \vert x\rangle

O número kk indica quantos qubits de espaço de trabalho são necessários no total. Isso é suficiente para os propósitos deste curso, mas é possível levar essa metodologia um passo adiante quando a própria função ff é invertível.

Para ser preciso, suponha que a função ff tenha a forma f:ΣnΣn,f:\Sigma^n \rightarrow \Sigma^n, e suponha também que existe uma função f1f^{-1} tal que f1(f(x))=xf^{-1}(f(x)) = x para todo xΣnx\in\Sigma^n (que é necessariamente única quando existe). Isso significa que a operação que transforma x\vert x \rangle em f(x)\vert f(x) \rangle para todo xΣnx\in\Sigma^n é unitária, então podemos esperar construir um circuito quântico que implementa a operação unitária definida por

Ux=f(x)U \vert x \rangle = \vert f(x) \rangle

para todo xΣn.x\in\Sigma^n.

Para ficar claro, o fato de que essa é uma operação unitária depende de ff ser invertível — não é unitária quando ff não é invertível. Desconsiderando os qubits de espaço de trabalho, UU é diferente da operação que o circuito QQ implementa porque não estamos mantendo uma cópia da entrada e fazendo XOR com uma string arbitrária, estamos substituindo xx por f(x).f(x).

A questão é: quando ff é invertível, podemos fazer isso?

A resposta é sim, desde que possamos usar qubits de espaço de trabalho e, além de ter um circuito booleano que computa f,f, também tenhamos um que computa f1.f^{-1}. Portanto, isso não é um atalho para inverter funções computacionalmente quando ainda não sabemos como fazer isso! O diagrama a seguir ilustra como isso pode ser feito compondo dois circuitos quânticos, QfQ_f e Qf1,Q_{f^{-1}}, que são obtidos individualmente para as funções ff e f1f^{-1} pelo método descrito acima, juntamente com nn portas swap, tomando kk como o máximo dos números de qubits de espaço de trabalho necessários por QfQ_f e Qf1.Q_{f^{-1}}.

Simulação de uma função invertível