Você está na página 1de 79

MESTRADO INTEGRADO PROFISSIONAL EM

COMPUTAÇÃO APLICADA – MPCOMP

Universidade Estadual do Ceará - UECE


Centro de Ciências Tecnológicas - CCT

Instituto Federal de Educação, Ciência e Tecnologia


do Ceará - IFCE
Pró-Reitoria de Pós-Graduação – ProPG

Flavio Jesus de Souza

Emprego da Computação Quântica em Problemas de


Difícil Decisão

Rio de Janeiro
2015

i
Flavio Jesus de Souza

Emprego da Computação Quântica em Problemas de


Difícil Decisão

Dissertação apresentada ao Programa de


Mestrado Integrado Profissional em
Computação Aplicada da Universidade
Estadual do Ceará e do Instituto Federal
de Educação, Ciência e Tecnologia do
Ceará, como requisito parcial para
obtenção do Grau de Mestre em
Computação Aplicada.
Orientador: Prof. DSc. Flávio Luis de
Mello.

Rio de Janeiro
2015

ii
Dados Internacionais de Catalogação na Publicação
Universidade Estadual do Ceará
Biblioteca Central Prof. Antônio Martins Filho

A999x Souza, Flavio Jesus


Emprego da Computação Quântica em Problemas de Difícil Decisão/ Flavio
Jesus de Souza. – 2015.
XXf. :il. color., enc. ; 30 cm.

Dissertação (Mestrado) – Universidade Estadual do Ceará, Centro de Ciências e


Tecnologia, Curso de Mestrado Profissional em Computação Aplicada, Fortaleza,
2015.
Área de concentração: Sistemas de Apoio a Decisão
Orientação: Prof. D. Sc. Flávio Luis de Mello
Título. Emprego da Computação Quântica em Problemas de Difícil Decisão
Introdução. 2. Complexidade. 3. Algoritmos Quânticos. 4. Problemas
Experimentais. 5. Problema das N-Rainhas. 6. Conclusão

CDD: 999.99

iii
FLAVIO JESUS DE SOUZA

EMPREGO DA COMPUTAÇÃO QUÂNTICA EM


PROBLEMAS DE DIFÍCIL DECISÃO

Dissertação apresentada ao Curso de Mestrado


Profissional em Computação Aplicada da
Universidade Estadual do Ceará, como requisito
parcial para a obtenção do grau de Mestrado
em Computação.

Defesa em: 04/03/2015

BANCA EXAMINADORA

Flávio Luis de Mello, D. Sc. (UFRJ)


Presidente (Orientador)

Marcos José Negreiros Gomes, D. Sc. (UECE)


Membro Interno

Julio Cesar Duarte, D. Sc. (IME)


Membro externo

Francisco Henrique de Freitas Viana, D. Sc. (CEFET)


Membro externo

iv
RESUMO

O presente trabalho consiste no uso da computabilidade quântica para solucionar


problemas de difícil decisão. Dessa forma, a computação quântica foi amplamente
trabalhada em problemas de busca com o intuito de gerar uma familiaridade com esse
tipo de computação. Com isso, foi possível a construção de um algoritmo quântico para
resolver um problema sofisticado da recreação matemática chamado N-Rainhas. O
algoritmo foi implementado em um simulador de computação quântica, permitindo
encontrar soluções para problemas com até 128 (cento e vinte oito) rainhas.

Palavras-Chave: Computação Quântica, Processo de Tomada de Decisão, Problema das


N-Rainhas.

v
ABSTRACT

This work aims to use quantum computability to solve difficult decision problems.
Therefore, quantum computing was apply to search problems in order to generate a
knowledge with this type of computing. Moreover, a quantum algorithm was construct in
order to solve a problem of sophisticated mathematical exercise called N-Queens. The
algorithm was implemented in a quantum computing simulator obtaining solutions for
problems with up to 128 (one hundred twenty eight) queens.

Key Words: Quantum Computing, Decision-Making Process, N-Queens problem.

vi
Sumário
Capítulo I ......................................................................................................................... 1
1.1 – Tema .................................................................................................................... 1
1.2 – Objetivos.............................................................................................................. 1
1.3 – Justificativa .......................................................................................................... 1
1.4 – Metodologia ......................................................................................................... 2
1.5 – Descrição ............................................................................................................. 3
Capítulo II ....................................................................................................................... 4
2.1 – Complexidade Computacional ............................................................................ 4
2.2 – Indecidibilidade ................................................................................................... 6
2.3 – Mecânica Quântica .............................................................................................. 7
2.4 – Evolução da Máquina de Turing ....................................................................... 11
Capítulo III.................................................................................................................... 14
3.1 – Pré-requisitos Algébricos .................................................................................. 14
3.2 – Algoritmo de Deutsch ........................................................................................ 16
3.3– Algortimo de Shor .............................................................................................. 19
3.4 – Algortimo de Grover ......................................................................................... 21
Capítulo IV .................................................................................................................... 25
4.1 – QCL ................................................................................................................... 25
4.2 – Prova de Conceito .............................................................................................. 27
Capítulo V ..................................................................................................................... 44
5.1 – Definição do Problema ...................................................................................... 44
5.2 – Soluções Clássicas ............................................................................................. 45
5.3 – Solução Híbrida ................................................................................................. 47
Capítulo VI .................................................................................................................... 56
6.1 – Conclusão .......................................................................................................... 56
6.2 – Trabalhos Futuros .............................................................................................. 58
Bibliografia .................................................................................................................... 59
Apêndice A - QCL ........................................................................................................ 62
Apêndice B – Algoritmo de Deutsch ........................................................................... 66
Apêndice C – Busca Quântica N-Queens ................................................................... 67
Apêndice D – Backtraking N-Queens ......................................................................... 70

vii
Capítulo I

Introdução

1.1 – Tema

O tema do trabalho consiste no uso da computabilidade quântica para solucionar


problemas de difícil decisão. Deste modo, o problema específico a ser resolvido é
entender como empregar a computação quântica na solução de problemas tradicionais da
computação e aplicar este entendimento em um problema emblemático de análise e
otimização.

1.2 – Objetivos

Avaliar o emprego da computação como alternativa mais ampla para resolver


problemas da natureza tidos como de difícil decisão. Sob essa ótica, serão avaliados os
seguintes objetivos específicos:
 Compreender os conceitos de complexidade computacional, indecidibilidade
e mecânica quântica.
 Estudar o entendimento sobre Máquinas de Turing clássicas e quânticas.
 Realizar uma explicação mais detalhadas e didáticas sobre os algoritmos de
Deutsch, Grover e Shor.
 Efetuar experimentos com a Quantum Computing Language.
 Propor uma solução do problema das N-Rainhas utilizando um algoritmo
quântico.

1.3 – Justificativa

A computação quântica fornece, em alguns casos, meios menos complexos para


solução de problemas. Desta forma torna-se interessante a criação de um algoritmo
híbrido (quântico-clássico) para ajudar na tomada de decisão de um problema específico
da recreação matemática chamado N-Rainhas.

1
Se for possível encontrar soluções menos custosas em computadores clássicos
para o referido problema, ter-se-á um ganho no tempo de decisão nas diferentes questões.
 Esquemas de armazenamento de memória paralela
 Testes de VLSI (Very-large-scale Integration)
 Controle de tráfego
 Prevenção de deadlocks em Sistemas Operacionais
 Processamento de imagem
Portanto os ramos de desenvolvimento de Sistemas Operacionais, a indústria de
vídeos e as empresas que fabricam processadores serão beneficiados caso o objetivo do
trabalho seja alcançado.

1.4 – Metodologia

Com base nos conhecimentos assimilados nas áreas de neurociência, mecânica


quântica e nos estudos mais avançados de Métodos Formais de Lógica Matemática,
sugere-se a criação de um algoritmo híbrido com Inputs/Outputs clássicos e
processamento quântico para solucionar um problema NP-Completo.
A abordagem utilizada será a computação quântica, realizada através de pesquisa
bibliográfica em literatura especializada, empregando ferramentas disponíveis neste
contexto.
Houveram tentativas sem sucesso de obter auxílio no Laboratório Nacional de
Computação Científica para a abordagem computacional do problema, mas foi de grande
proveito as apostilas utilizadas pelo professor Renato Portugal da Sociedade Brasileira de
Matemática Aplicada e Computacional (SBMAC). No Centro Brasileiro de Pesquisas
Físicas foi possível obter apoio no entendimento dos algebrismos relacionados com a
mecânica quântica. Neste sentido, a estratégia foi compreender os fundamentos teóricos
sobre o tema, exercitar e instruir a capacidade de implementar algoritmos quânticos em
QCL, e enfim, implementar uma solução quântica para o problema das N-Rainhas.
É relevante que se diga que o trabalho não atingiria o sucesso desejado se não
existisse o simulador criado por Bernard Ömer. Pois sem a QCL (Quantum Computation
Language) a compreensão da funcionalidade de um algoritmo quântico não seria possível.

2
1.5 – Descrição

No Capitulo II é introduzindo o conceito de indecidibilidade no mundo da ciência


da computação e a mais importante criação humana: o computador (Máquina de Turing).
No entanto, é elucidado de forma bem clara, suas vantagens e seus limites por conta do
Teorema de Gödel bem como a evolução da Máquina de Turing como sendo o resultado
direto da física-matemática e a teoria quântica necessária para a compreensão da
computação quântica.
No Capitulo III são apresentados os principais algoritmos quânticos existentes
mostrando os pilares da computação quântica. Nele o leitor tem a oportunidade de se
familiarizar com conceitos algébricos que são indispensáveis para o entendimento da
computação quântica.
O Capitulo IV consiste numa comparação entre algoritmos clássicos e um
algoritmo quântico padrão. Na prova de conceito são apresentadas quatro soluções para
um mesmo problema. Onde, depois de repetidas execuções das mesmas, chega-se em
algumas conclusões importantes segundo a eficiência e complexidade dos mesmos
mostrando as vantagens do quântico em relação ao mais eficiente algoritmo clássico.
No Capitulo V é apresentado uma nova solução quântica para o problema
complexo das N-Queens.
Para finalizar são apresentadas as conclusões e os trabalhos futuros relacionados
com essa dissertação.

3
Capítulo II

Complexidade

2.1 – Complexidade Computacional

Sob o ponto de vista computacional, complexidade é o estudo dos recursos de


espaço e tempo necessários para resolver problemas computáveis. Alguns problemas são
bem comportados independentemente do algoritmo e do caminho percorrido na busca da
solução. Portanto, alguns problemas permitem que se chegue a limites de complexidades
bem definidos, enquanto outros se encontram em classes com contornos não tão claros
assim (Toscani, 2001).
São definidas várias classes quanto à complexidade do problema: P, NP, NP-
Completo, entre outras. Um problema é considerado fácil, tratável ou solúvel se existir
um algoritmo para resolvê-lo usando recursos polinomiais (P). Um problema é
considerado difícil, intratável e não razoável se o melhor algoritmo existente requer
recursos exponenciais (NP) (Nielsen, 2005). O subconjunto de problemas NP, aos quais
todos os problemas de NP são redutíveis em tempo polinomial, dá-se o nome de NP-
Completo (NPCo).
Em síntese, P é a classe de problemas que podem ser resolvidos rapidamente por
um computador clássico. NP é a classe de problemas cujas soluções podem ser verificadas
rapidamente em um computador clássico.
O problema do caixeiro viajante (cálculo do menor percurso entre um conjunto
qualquer de cidades) pode ser encarado como um elemento dos NPCo. Se o mesmo for
submetido a um algoritmo clássico, dependendo do número a ser fatorado, torna-se
intratável. Desta forma, não se chegará a nenhuma solução nem mesmo utilizando o
melhor computador clássico existente.
Alternativamente, o algoritmo Shor (que será discutido em detalhes a posteriori)
tem o potencial para acelerar de forma quadrática a solução do problema, diminuindo sua
complexidade e por vezes, aumentando sua eficiência. Duas condições de contorno
devem ser levadas em consideração para que isso ocorra. Primeiro, o algoritmo deve ser

4
executado num computador quântico. Segundo, a quantidade de qubits deve estar na
mesma ordem de grandeza que necessita a solução. Para um computador clássico, por
exemplo, com o número de cidades maior do que 25, o cálculo do menor percurso entre
as cidades torna-se praticamente impossível. Já para um computador quântico, utilizando
o algoritmo de Shor, basta a criação de uma matriz na ordem de 25 (5 qubits) para
representar o emaranhado que o problema exige. Esse dado é relevante, pois para um
computador quântico, 5 qubits não representam uma alocação de recursos proibitiva.
Entretanto, observe que não há aqui uma defesa de que o algoritmo de Shor tornará
o problema do caixeiro viajante tratável. Apenas é uma evidência do benefício que
algoritmos quânticos podem trazer a essa classe de problema. Ainda que este algoritmo
tenha potencial para reduzir de forma quadrática a complexidade do problema, o resultado
continua sendo exponencial. Mesmo reduzindo a complexidade, a ordem de grandeza
permanece similar. Portanto, há sim um incremento da capacidade de computar soluções
para uma quantidade maior de cidades do que a atual.
A construção de um algoritmo quântico resultará, na maioria das vezes, numa
solução mais rápida com menos complexidade que seu equivalente clássico. Entretanto,
a construção desse tipo de algoritmo não é trivial. E como já foi mencionado antes, é
interessante que um programa quântico seja executado num computador 100% quântico.
Atualmente no mundo existem várias iniciativas e poucos computadores realmente
quânticos no mundo. O Nobel de Física de 2012 (Sciam, 2012) gerou uma grande
expectativa nesse sentido, pois já é possível realizar medições em jaulas de fótons e em
armadilhas de íons aprisionados sem que os mesmos sejam observados. O que permite
que suas propriedades quânticas não sejam perdidas, um ponto nefrálgico na construção
de computadores quânticos. Essas experiências são animadoras, pois devem servir como
base para a construção de novos computadores quânticos (Sciam, 2012).

5
2.2 – Indecidibilidade

A indecidibilidade pode ser conceituada de modo indireto através daquilo que não
é decidível, que por sua vez é o resultado de uma computação que retorna apenas “sim”
ou “não”. Logo, todo problema pode ser encarado como decidível ou indecidível.
Contudo, além da sua decidibilidade, um problema pode ser classificado quanto a
sua tratabilidade. O problema da primacidade, do logaritmo discreto, do caixeiro viajante
e o da busca desordenada num banco de dados são problemas decidíveis, porém também
são considerados problemas decidíveis de difícil solução clássica (Nielsen, 2005) ou
intratáveis.
A matemática clássica assume que quaisquer funções, quando submetidas a uma
entrada, produzem algum resultado, seja ele qual for. Entretanto, a computabilidade não
compartilha esse grau de segurança proposto pela matemática clássica, pois um algoritmo
pode receber uma determinada entrada e nunca encerrar o processamento da mesma
(Mello, 2014). É nesse cenário que surge o problema indecidível mais famoso que se tem
conhecimento, uma situação conhecida como o “Problema da Parada”. O Problema da
Parada postula que existem problemas que não são resolvidos por uma Máquina de
Turing, e ainda que, nem uma segunda Máquina da Turing (Prova de Turing) é capaz de
avaliar se a primeira vai parar ou não, caso esta tente resolver um problema indecidível.
A possibilidade de sistematizar a mente humana através da computabilidade
passou a ser uma incógnita devido a essa limitação mesmo com a demonstração do
Problema através do Teorema da Incompletude de Gödel (Goldstein, 2005). Porém sabe-
se que muitas verdades dentro de sistemas complexos que a ciência tanto procurou
demonstrar, nada mais são do que inferências impostas pelo sistema que o encapsula. Em
parte, essa consequência do Teorema da Incompletude trouxe consigo uma frustração aos
matemáticos e um melhor entendimento sobre essa limitação.
Além disso, como certas verdades possuem demonstração, a automatização de
alguns processos de raciocínio verdadeiros continua sendo perfeitamente cabível (Mello,
2014). Por isso, várias vertentes da ciência vêm utilizando cada vez mais o computador
para ajudar o ser humano em tarefas que a mente levaria muito tempo para calcular; sendo
que, em alguns casos, a solução nos é inviável.
Por conta do exposto acima, as pesquisas sobre computabilidade devem ter em
mente que sempre existirão mais funções a computar do que possíveis programas para
computá-las (Cafezeiro e Heausler, 2007). Logo, contrapondo à Inteligência Artificial,

6
trata-se de uma Estupidez Natural (McDermott, 1976) achar que se pode, somente com a
computabilidade clássica, computar habilidades humanas tais como: entendimento,
desejo, compreensão e intuição (McDermott, 1976). Sabiamente ridicularizou o uso
indiscriminado da computação como solução para todos os problemas, construindo assim
uma maquiagem bem feita em algumas soluções de problemas, de modo que estas
soluções se parecessem inteligentes.

2.3 – Mecânica Quântica

A Mecânica Quântica foi desenvolvida no primeiro quarto do século XX para


explicar os fenômenos do mundo microscópico: moléculas, átomos e partículas. Contrária
à intuição clássica dos físicos, ela revolucionou a maneira pela qual a natureza é vista e
interpretada. E ao longo das décadas, adquiriu o status de suprema teoria da matéria e
suas interações.
Paralelamente, surgiu outra revolução ainda na década de 1930, sendo no campo
da matemática pura. Seu início se deu através de Kurt Gödel, sendo criada por Alan
Turing e ratificada por Alonzo Church. A Teoria da Computação introduziu o conceito
de algoritmo e fundou as bases teóricas da revolução tecnológica promovida pelos
computadores, conhecida e utilizada atualmente no mundo inteiro.
A visão quântica do mundo teve seu início na distinção entre partículas e os
fenômenos de ondas. Os dois conceitos são diferentes, pois ninguém trata uma projetil
voando como se fosse um pacote de onda ou a propagação do som como se fosse uma
partícula. Porém quando partículas e comprimentos de onda ficam bem pequenos (na
ordem da constante de Plank) as coisas não são bem assim (Ömer, 1998).
No século XVII, Newton usou as duas teorias para definir os diferentes aspectos
da luz, explicando a periodicidade e a interferência das ondas. Mais tarde, a teoria onda-
luz foi amplamente aceita pelos cientistas da época.
Em 1888, Hertz demonstrou que uma placa carregada negativamente pode
descarregar energia quando exposta à luz ultravioleta. Logo depois, Lenard descobriu que
a energia cinética dos elétrons é independente da intensidade da luz, mas está
correlacionado com a sua frequência.

7
Em 1900, Max Plank explicou o espectro de energia do corpo negro pressupondo
que os estados de energia possíveis estão restritos à equação 𝐸 = 𝑛ℎ𝑓, onde 𝑛 é um
número inteiro, 𝑓 a frequência e ℎ = 6,626075 . 10−34 𝐽𝑠.
Em 1905, Einstein reformulou a equação acima dando origem a famosa relação
entre Energia, a constante de Plank e a frequência da luz. Interpretando 𝐸 como a energia
da partícula luz, mais tarde chamada de fóton.

𝐸 = ℎ𝑓 = 𝜔
2𝜋
A descoberta acima sugeriu que um elétron limita-se a certos tipos de energia,
entrando em contradição com a mecânica clássica. Com isso, o modelo de Born-
Sommerfeld introduziu a condição quântica, pois o momento dos elétrons em torno do
núcleo atômico passou a ser múltiplos de ℎ. Tal restrição pode ser justificada pela
atribuição de propriedades de onda para o elétron, mas esse tipo de teoria híbrida mostrou-
se insatisfatória.
Esse problema veio a ser resolvido somente em 1923, quando Heisenberg
utilizando um formalismo baseado em matrizes criou o que se chama de Princípio da
Incerteza de Heisenberg. Em 1925, Schrödinger publicou uma solução alternativa
utilizando funções de ondas complexas segundo a evolução temporal da partícula-onda,
determinando assim a dinâmica do sistema de partículas.
𝑑
𝐻𝜓 = 𝑖ħ (𝜓)
𝑑𝑡
Na equação de Schrödinger, 𝐻 é o operador Halmitoniano que representa a energia
total do sistema e ħ = ℎ/2𝜋. Dois anos mais tarde, Dirac uniu os dois formalismos dando
origem à álgebra computacional utilizada na computação quântica.
A combinação de computação e mecânica quântica começou a despontar no início
dos anos da década 1960, com a descoberta de lei de Moore, levando à inevitável
conclusão de que, por volta do ano 2020 cada bit em um computador será codificado em
apenas um átomo. (Nielsen, 2005)
Para os físicos no início da década de 1980, a observação da lei de Moore tornou
clara a noção de que a física traduz-se em computação. Essa nova noção abriu um espaço
intangível para a computação, para a informação e também para a própria mecânica
quântica. Surgiram assim a computação quântica e a informação quântica. Num mesmo
patamar da história científica surgiram um novo paradigma de computação, um imenso
desafio tecnológico e uma fascinante área de pesquisa da física. Desde então o que se vê

8
é a proliferação de resultados teóricos e experimentais, tanto na física quanto na ciência
da computação e na teoria da informação. O emaranhado mudou-se do espaço de Hilbert
para os laboratórios de física, transformando-se em um novo e estranho recurso da
natureza para o processamento da computação, da informação e da comunicação.
A evolução da computabilidade está caminhando para o mundo aleatório e em
consequência disso consegue-se diminuir a complexidade de determinados problemas.
Mas o movimento natural da ciência de se aprofundar no conceito do vetor de estado em
qualquer base com números complexos não impedirá que a computação quântica continue
restrita aos limites de Gödel.
A teoria da Mecânica Quântica utiliza um instrumental matemático baseado em
combinações lineares de vetores de estados com coeficientes complexos tendo sua
probabilidade calculada a partir dos quadrados dos módulos desses números complexos.
O modelo matemático onde reside a Mecânica Quântica em termos formais é o que se
chama de Espaço de Hilbert (Griffiths, 2004).
Neste sentido, um estado quântico |𝜓⟩ qualquer pode ser escrito da seguinte
forma:
|𝜓⟩ = 𝛼|0⟩ + 𝛽|1⟩
Esta fórmula descreve o estado de um sistema fechado composto por dois vetores
de estado |0⟩ 𝑒 |1⟩ ditos em superposição, isto é, que existem simultaneamente até que se
efetue alguma medição, quando então |𝜓⟩ é colapsado sobre algum dos vetores de estado.
Além disto, 𝛼 𝑒 𝛽 ∈ ℂ são as amplitudes de cada vetor sendo |𝛼|2 + |𝛽|2 = 1.
Como:
𝛼 = 𝑎 + 𝑏𝑖, 𝑎, 𝑏 ∈ ℝ
𝛽 = 𝑐 + 𝑑𝑖, 𝑐, 𝑑 ∈ ℝ

Tem-se que:
A probabilidade de |𝜓⟩ = |0⟩ é igual a |𝛼|2 = 𝑎2 + 𝑏 2 .
A probabilidade de |𝜓⟩ = |1⟩ é igual a |𝛽|2 = 𝑐 2 + 𝑑 2 .

A Mecânica Quântica teve seu início comprovado através do experimento da


dupla fenda de Young (Griffiths, 2004). Neste experimento, elétrons são arremessados
contra um anteparo de duas fendas gerando um modelo de interferência similar a uma

9
onda. Contudo, quando são observados, eles comportam-se como matéria. O simples fato
de se observar faz com o que a menor partícula fundamental perca a sua função de onda.
A Mecânica Quântica traz consigo quatro conceitos básicos que são utilizados nos
modelos computacionais existentes, a saber.
 Paralelismo.
 Superposição.
 Interferência.
 Emaranhado.
O entendimento do Paralelismo quântico está diretamente relacionado com a
evolução temporal de um estado quântico, sendo seu comportamento regido pela equação
de Schrödinger. Sem que se aprofunde no conceito físico, em computação quântica as
operações são feitas simultaneamente segundo um operador unitário reversível que é
traduzido em forma de função para determinados problemas.
A Superposição acontece no mundo aleatório. Os vetores de estado |0⟩ e
|1⟩ representam superposições dos vetores 0 e 1. O vetor |0⟩ pode se tornar 0 e 1,
enquanto que o vetor |1⟩ pode se tornar 1 e 0 após uma medida. Em computação quântica
os operadores unitários mexem nas probabilidades de se ocorrer 0 e 1 após a medida. Por
exemplo, o operador unitário de Hadamard (H) é responsável por colocar os vetores de
estado |0⟩ e |1⟩ em equiprobabilidade (50% cada uma) de ocorrem medições 0 e 1.
O fenômeno da Interferência tem uma explicação simples, mas um entendimento
difícil, pois como a mente humana está presa à realidade, é complicado para qualquer ser
humano compreender que a simples observação de um sistema faz com que o mesmo
passe a existir por conta dessa medida. Em termos computacionais, podemos determinar
o valor de uma função para dois valores diferentes de um vetor de estado avaliando a
função apenas uma vez. Isso é mais rápido de que qualquer aparato clássico que se possa
imaginar, no qual seriam necessárias duas avaliações. (Sciam, 2012).
Por fim, um estranho fenômeno da natureza quântica das partículas que até hoje
não foi decifrado por completo é o Emaranhado, ou entrelaçamento quântico. Esse estado
do sistema é caracterizado por pertencer somente ao mundo aleatório sem que sejam
geradas representações na realidade. Dois ou mais objetos podem estar ligados de uma
forma tão forte, ao ponto que um não pode ser descrito completamente sem que sua outra
parte não seja mencionada, ainda que estes objetos estejam separados fisicamente.

10
Mas por razões desconhecidas, tais estados não impedem o importantíssimo papel
do emaranhado na computação quântica e na informação quântica (Nielsen, 2005).

2.4 – Evolução da Máquina de Turing

A Tese de Alonso Church e Alan Turing nos diz que. ”Se um problema é resolvido
através de uma função computável num dado domínio, então existe uma Máquina de
Turing que pode calcular o valor da função para todos os argumentos desse domínio
(Palazzo, 2007).” Consequentemente, existe um dispositivo abstrato capaz de determinar
a solução de um problema decidível.
Essa descoberta foi concebida pelo matemático inglês chamado Alan Mathieson
Turing em 1936 e desde então a Máquina de Turing é definida como o modelo da
formalização do conceito de procedimento (Palazzo, 2007), isto é, uma sequencia finita
de instruções que pode ser realizada num tempo finito.

Como foi dito, Alan Turing contribuiu com o aparato abstrato da realidade, mas
foi Deutsch (1985) que introduziu a mecânica quântica nesse contexto. A Computação
Quântica surgiu da simbiose entre a Máquina de Turing e a Mecânica Quântica. Essa idéia
foi proclamada por Deutsch em 1985 (Deutsch, 1985) que na ocasião criou o primeiro
algoritmo quântico.

Na Máquina de Turing Quântica, a fita e o cabeçote de leitura/gravação existem


em um estado quântico. Neste sentido, os valores da fita podem ser {0, 1} ou uma
superposição de valores 0 e 1. Assim, os símbolos {|0⟩, |1⟩} representam todos os
gradientes entre eles ao mesmo tempo. Desta forma, se uma Máquina de Turing Clássica
produz sempre um único resultado, a Máquina de Turing Quântica produz vários
resultados simultaneamente, ou seja, efetua vários cálculos (paralelismo quântico).

11
A sequência lógica de raciocínio a seguir exprime, em poucas palavras, o
entendimento da Máquina de Turing Quântica:
1. A Máquina de Turing Clássica representa uma máquina abstrata usada para
modelar o efeito de computar.
2. A Máquina de Turing Probabilística representa uma Máquina de Turing não
determinística.
3. A Máquina de Turing Quântica representa uma Máquina de Turing Probabilística,
onde não é possível realizar o monitoramento da fita, pois o simples ato de medir
acabaria por perturbar o sistema.

O poder computacional de máquinas de Turing quântica é objeto de estudo de vários


pesquisadores. Bernstein e Vazirani (1993; 1997) fazem algumas comparações com os
modelos de classes de complexidade clássicos com aqueles criados com base nas
evoluções obtidas na computação quântica. As classes BQP, EQP e ZQP acabaram por
fundamentar as bases da teoria da complexidade quântica. Como indicado por Tetsushi
(2004):
 Uma classe BQP (Bounded Error Quantum Polynomial Time) é aquela em
que se existe uma máquina de Turing quântica tal que para qualquer entrada
x uma observação de uma célula da fita após um processamento de tempo
polinomial, ela retorna 1 com probabilidade maior que 2/3 se x pertencer a
L ou 0 com probabilidade maior que 2/3 caso contrário.
 Uma classe EQP (Exact Quantum Polynomial Time) é aquela em que se
existe uma máquina de Turing quântica e um polinomial p, tal que, para
qualquer entrada x uma observação de uma célula da fita após um
processamento de p(|x|) passos retorna 1 com probabilidade 1 se x pertencer
a L ou 0 com probabilidade1, caso contrário.
 Uma classe ZQP (Zero Error Quantum Polynomial Time) é aquela em que
se existe uma máquina de Turing quântica tal que para qualquer entrada x
uma observação de uma célula da fita (célula indicadora de parada da
máquina) após um processamento de tempo polinomial de seu tamanho ela
retorna 1 com probabilidade maior que 1/2 e então se uma observação de
outra célula (célula de decisão) retorna 1 com probabilidade 1 se x pertencer
a L, 0 com probabilidade 1, caso contrário.

12
Atualmente existem três importantes algoritmos quânticos, e no total não passam
de dez no mundo inteiro, são eles: Deutsch, Shor e Grover. Estes algoritmos serão
abordados em detalhes no capítulo a seguir.
Sob esta ótica, o avanço dos algoritmos quânticos está na compreensão dos
fenômenos físicos propostos pela mecânica quântica e no domínio da álgebra tensorial
utilizada pela mesma. Se os físicos, matemáticos e cientistas da computação conseguirem
encontrar combinações de operadores unitários que traduzam o comportamento do mundo
pequeno que gera a realidade, consequentemente este entendimento permitirá o
surgimento de mais algoritmos quânticos eficientes.

13
Capítulo III

Algoritmos Quânticos

3.1 – Pré-requisitos Algébricos

Fazer computação quântica nada mais é do que manipular os operadores unitários


diante dos vetores de estado seguindo o conceito da equação de Schrödinger. A principal
característica desses operadores é que 𝐴𝑡 𝐴 = 𝐼, onde 𝐴𝑡 é matriz transposta conjugada de
𝐴. A seguir, são elencadas algumas propriedades importantes que serão utilizadas nesta
dissertação. Algumas explicações pormenorizadas serão realizadas quando houver
pertinência direta com o entendimento da matemática envolvida neste trabalho, outras
explicações deverão ser consultadas em Nielsen (2005).
Inicialmente são necessárias algumas observações a respeito dos vetores de
estado, a saber:
1
|0⟩ = [ ]
0
0
|1⟩ = [ ]
1
𝛼
|𝜓⟩ = 𝛼|0⟩ + 𝛽|1⟩ = [ ] , 𝑜𝑛𝑑𝑒 𝛼 2 + 𝛽 2 = 1
𝛽
⟨𝜓| = [𝛼 𝛽]
⟨𝜓|𝜓⟩ = ‖|𝜓⟩‖ = √𝛼 2 + 𝛽 2

1 − 3𝑖 1 − 𝑖
Em seguida, seja 𝐴 = [ ]. Neste caso, tem-se que a matriz
−2𝑖 1 + 4𝑖
1 − 3𝑖 −2𝑖
transposta 𝐴𝑇 = [ ]. Calculando-se o conjugado desta matriz, tem-se
1 − 𝑖 1 + 4𝑖
1 + 3𝑖 2𝑖
que(𝐴𝑇 )∗ = [ ] = 𝐴𝑡 . Dessa forma 𝐴 é considerado um operador
1 + 𝑖 1 − 4𝑖
hermitiano e 𝐴𝑡 é o conjugado hermitiano da matriz 𝐴.
Se 𝐴𝑡 𝐴 = 𝐴𝐴𝑡 , diz-se que 𝐴 é normal. Portanto todo operador hermitiano é
normal.
Se 𝐴𝐴𝑡 = 𝐼, diz-se que 𝐴 é unitário e que o operador hermitiano é unitário.

14
Além disso é importante observar que o complemento ortogonal de 𝐴 é definido
como 𝑄 = 𝐼 − 𝐴.
0 1
Nesse sentido, considere também uma das Matrizes de Pauli 𝑋 = [ ]. Temos
1 0
0 1 𝛼
que 𝑋|𝜓⟩ = [ ] [ ] = [𝛽𝛼]. Sob esta ótica, nota-se facilmente que esse operador 𝑋
1 0 𝛽
inverte os coeficientes do vetor de estado.
𝑎𝑐
Por fim, seja 𝐴 = [𝑎𝑏] 𝑒 𝐵 = [𝑑𝑐 ]. Definimos 𝐴 ⊗ 𝐵 = [𝑎𝑑
𝑏𝑐 ] como sendo o produto
𝑏𝑑

tensorial entre A e B, onde segundo (Nielsen, 2005) esse produto é a forma de se expandir
espaços vetoriais.
Há que se fazer aqui uma ressalva notacional sobre a questão do produto tensorial.
A representação matemática deste tipo de operação é de fato dada por 𝐴 ⊗ 𝐵. Contudo,
a Física adota outra notação para denotar a mesma operação, isto é |𝐴⟩|𝐵⟩. Nesta
dissertação será adotada a notação oriunda da Física uma vez que na literatura relacionada
com Computação Quântica há o predomínio desta última em detrimento da notação
matemática.
Por exemplo, o estado quântico |01⟩ pode ser expandido da seguinte forma.
0
|01⟩ = |0⟩|1⟩ = [10] ⊗ [01]=[10]
0

1 1 1
O operador hermitiano 𝐻 = [ ] coloca os estados |0⟩ e |1⟩ em eqüiprobabilidade
√2 1 −1
de ser 0 ou 1. Deste modo, a aplicação deste operador sobre |0⟩ e |1⟩ é calculada da
seguinte forma:
1 1 1 1 1 1 1 1 0 (|0⟩ + |1⟩)
𝐻|0⟩ = [ ][ ] = [ ]= ([ ] + [ ]) = = |+⟩
√2 1 −1 0 √2 1 √2 0 1 √2
1 1 1 0 1 1 1 1 0 (|0⟩ − |1⟩)
𝐻|1⟩ = [ ][ ] = [ ]= ([ ] + [ ]) = = |−⟩
√2 1 −1 1 √2 −1 √2 0 −1 √2
onde 𝐻 é chamado de operador de Hadamard.

15
3.2 – Algoritmo de Deutsch

É possível explicar o algoritmo de Deutsch analisando as faces de uma moeda.


Suponha que se deseja saber se uma moeda possui de um lado uma cara e do outro uma
coroa com apenas uma observação. Classicamente, este problema seria trivial, haja vista
que, se uma face da moeda é cara, é esperado que a outra face seja coroa. Contudo, há
uma pressuposição importante para que isto aconteça. A moeda deve possuir uma face
com coroa e outra com cara. No entanto, é possível existirem moedas cunhadas com duas
faces iguais. Dessa forma, diz-se que moedas com duas faces iguais são constantes e
moedas com faces diferentes são chamadas de balanceadas.
O proposta de Deutsch foi justamente saber se uma dada função de leitura f:{0,
1} → {0, 1} é balanceada ou constante.
De forma clássica, é óbvio que são necessárias duas leituras para saber o
comportamento da função, pois jamais se tem o conhecimento se as faces são diferentes
ou não. Porém Deutsch desenvolveu um algoritmo quântico cuja solução se dá com
apenas uma medida, conseguindo asim, observar ambos os lados da moeda de uma só
vez. Na tabela 3.1 tem-se todas as possibilidades do resultado dessas funções. As funções
de leitura 𝑓0 , 𝑓1 , 𝑓2 e 𝑓3 correspondem às respectivas leituras dos casos em que a moeda
é composta por cara-cara, coroa-coroa (funções constantes), coroa-cara ou cara-coroa
(funções balanceadas).

Tabela 3.1 – Resultado das funções segundo o valor de x

𝒇𝟎 (𝒙) 𝒇𝟏 (𝒙) 𝒇𝟐 (𝒙) 𝒇𝟑 (𝒙)


1 – cara 0 – coroa 0 - coroa 1 – cara
1 – cara 0 – coroa 1 – cara 0 – coroa

É evidente que a quantidade mínima de qubits para armazenar todas as


combinações de resultados para representar o estado inicial da moeda é dois, porque
existem quatro combinações possíveis e 22 = 4, onde o primeiro qubit representa estado
inicial da moeda e segundo o resultado da operação.
Como estados quânticos só podem ser modificados segundo operadores unitários,
logo existe um operador 𝑈𝑓 , tal que, 𝑈𝑓 que transforma 𝑥 ∈ {0, 1} em 𝑦 ∈ {0, 1} e 𝑓(𝑥) ∈

16
{0, 1}. Desta forma, |𝑥, 𝑦⟩ → |𝑥, 𝑦⨁𝑓(𝑥)⟩, onde 𝑦⨁𝑓(𝑥) representa o (𝑦 +
𝑓(𝑥))𝑚𝑜𝑑2=resto da divisão de (𝑦 + 𝑓(𝑥)) por 2, representado pelo o esquema a seguir.

A tabela 3.2 mostra com mais clareza o resultado da operação.

Tabela 3.2 – Operação módulo 2 segundo Deutsch

|𝒙⟩|𝒚⟩ 𝒚 𝒇(𝒙) 𝒚⨁𝒇(𝒙)


|𝟎𝟎⟩ 0 0 0
|𝟎𝟏⟩ 1 0 1
|𝟏𝟎⟩ 0 1 1
|𝟏𝟏⟩ 1 1 0

𝑦, 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑒 0, 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑒
Como 𝑓(𝑥) = { , tem-se 𝑦⨁𝑓(𝑥) = { ou seja,
𝑦̅, 𝑏𝑎𝑙𝑎𝑛𝑐𝑒𝑎𝑑𝑎 1, 𝑏𝑎𝑙𝑎𝑛𝑐𝑒𝑎𝑑𝑎
somando os elementos da coluna 𝑦 mais os da coluna 𝑓(𝑥) e dividindo por 2, o resto
representa todas as possibilidades da outra face da moeda após o lançamento.
Dado que |0⟩|1⟩ = |01⟩ e partindo-se do estado inicial |𝜓⟩ = |0⟩|1⟩ = |01⟩,
aplicando o operador de Hadamard no vetor de estado inicial |𝜓⟩, tem-se:
1 1 1 1 1 1 1 0 (|0⟩ + |1⟩) (|0⟩ − |1⟩)
|𝜓1 ⟩ = 𝐻|0⟩𝐻|1⟩ = ( )( ) ( )( ) =
√2 1 −1 0 √2 1 −1 1 √2 √2
1
= [ |0⟩|0⟩ − |0⟩|1⟩ + |1⟩|0⟩ − |1⟩|1⟩ ]
2
1 1
= [|0⟩(|0⟩ − |1⟩) + |1⟩(|0⟩ − |1⟩)] = |𝑥⟩(|0⟩ − |1⟩)
2 √2

17
A operação utilizando o operador de Hadamard coloca o emaranhado em
equiprobabilidade e definindo.
|𝜓2 ⟩ = 𝑈𝑓 |𝜓1 ⟩
0, 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑒
Como 𝑦⨁𝑓(𝑥) = {
1, 𝑏𝑎𝑙𝑎𝑛𝑐𝑒𝑎𝑑𝑎
Logo
1
|𝜓2 ⟩ = 𝑈𝑓 |𝑥⟩(|0 ⊕ 𝑓(𝑥)⟩ − |1 ⊕ 𝑓(𝑥)⟩)
√2
Como os valores possíveis de 𝑓(𝑥) = {0,1}, dessa forma pode-se substituir |𝜓2 ⟩
pela forma homomórfica a seguir:
1
|𝜓2 ⟩ = (−1) 𝑓(𝑥) |𝑥⟩[|0⟩ − |1⟩]
√2
1
|𝜓2 ⟩ = [(−1)𝑓(0) |0⟩(|0⟩ − |1⟩) + (−1) 𝑓(1) |1⟩(|0⟩ − |1⟩)]
√2
1
± [(|0⟩ + |1⟩)(|0⟩ − |1⟩)] 𝑠𝑒 𝑓(0) = 𝑓(1)
|𝜓2 ⟩ = { 2
1
± [(|0⟩ − |1⟩)(|0⟩ − |1⟩)] 𝑠𝑒 𝑓(0) ≠ 𝑓(1)
2

Utilizando o formalismo acima, Deutsch concebeu o primeiro algoritmo quântico


cuja a proposta é colocar 2 qubits em superposição num emaranhado e com apenas uma
medida do primeiro qubit é possível obter o valor da outra face após o lançamento. No
apêndice B é mostrado o algoritmo em QCL.

18
3.3– Algortimo de Shor

O algoritmo de Shor (Nielsen, 2005) resolve de forma exponencial o cálculo da


ordem de um determinado número, fazendo com que a complexidade do problema da
fatoração diminua de forma quadrática.
Uma abordagem inicial para explicar o algoritmo Shor passa por uma tentativa de
fatorar um número 𝑁. O problema da fatoração de números é bastante importante para a
criptografia, pois trata-se de um recurso que fatora um número em números primos.
Observe que para isto basta checar o resto da divisão de um número natural 𝑝 por algum
número menor ou igual que √𝑁. Se o resto é 0, conclui-se que 𝑝 é um fator 𝑁. Em uma
primeira análise, esta propriedade parece ser despretensiosa, mas ela permite uma
manipulação importante.
Por exemplo, para 𝑁 = 15, √𝑁 = 3,87, toma-se o número primo mais próximo e
menor ou igual a √𝑁. Neste caso 𝑝 = 3 (outro número primo é o 2, porém não é o mais
próximo). Observe que 𝑁 𝑚𝑜𝑑 3 = 0, logo 3 divide 15. Consequentemente, o MDC(3,
15) = 3, portanto 3 é um divisor primo de 15. Após essa divisão o que resta é o número
5. Portanto resta o cálculo para 𝑁 = 5, cuja raiz √𝑁 = 2,24. Desta forma, o número
primo mais próximo e menor ou igual a √𝑁 é 2, porém 𝑁 𝑚𝑜𝑑 2 ≠ 0. Como o MDC(5,
15) = 5, logo 5 é divisor de 15.
Considerando valores de 𝑁 pequenos, este método é interessante, mas para 𝑁
maiores, esse método se torna inviável. Uma vez que sua solução depende da solução de
diversos outros problemas similares de menor ordem. Além disso, há que se levar em
consideração que esse problema trata-se de uma fatoração exata em dois números primos
sem expoente.
A perspicácia de Shor foi perceber que na verdade isto permite uma redução do
problema da fatoração para um problema de periodicidade. Além disso, transformar uma
sequência numa função periódica é uma tarefa relativamente fácil. Por exemplo, suponha:
𝑥 ∈ {0,1,2,3,4,5,6,7 … } 𝑒 𝑟 = 15
Ao se tentar calcular 2𝑥 𝑚𝑜𝑑 𝑟 para cada valor de x obtém-se o seguinte vetor de
soluções:
[1, 2, 4, 8, 1, 2, 4, 8, … . ]
O que de fato pode ser considerado uma função de período 4.

19
Para 𝑥 ∈ {0,1,2,3,4,5,6,7,8,9,10,11,12, … } 𝑒 𝑟 = 21, com a mesma função de
cálculo 2𝑥 𝑚𝑜𝑑 𝑟, tem-se:
[1, 2, 4, 8, 16, 11, 1, 2, 4, 8, 16, 11, … ]
Nesse caso o período da função é 6.

Fazendo uso da periodicidade, pode-se prosseguir numa substituição dada por 𝑟 =


2𝑝 e generalizando o raciocínio acima, utilizando um número qualquer de uma potência
qualquer, tem-se.
𝑟 𝑥 𝑚𝑜𝑑 𝑁, 𝑥 ∈ {0,1,2, … }
Nota-se que, quando o 𝑥 fornecido não é divisível por p ou por q, a sequência
acima irá se repetir com um período que eventualmente divide (𝑝 − 1)(𝑞 − 1).
Ou seja, o problema se traduz em descobrir um período e depois fatorá-lo em dois
outros números e depois somar mais 1 e descobrir dois fatores primos de 𝑁.
Voltando para os casos acima.
Para 𝑁 = 15 o período é 4, que por sua vez divide (𝑝 − 1)(𝑞 − 1) = 2 ∗ 4 = 8.
𝑝 = 2 + 1 = 3 𝑒 𝑞 = 4 + 1 = 5.
Para 𝑁 = 21 o período é 6, que por sua vez divide (𝑝 − 1)(𝑞 − 1) = 2 ∗ 6 = 12
2 ∗ 6 = (𝑝 − 1)(𝑞 − 1), 𝑙𝑜𝑔𝑜 𝑝 = 3 𝑒 𝑞 = 7.
E assim sucessivamente.
A quantização do raciocínio disposto pode ser posta numa superposição de
𝑛 𝑥 𝑚𝑜𝑑 𝑁 ≡ 𝑟. Onde 𝑟 é o período a ser encontrado na tentativa de descobrir o produto
(𝑝 − 1)(𝑞 − 1) e consequentemente os fatores de 𝑁. Desta forma, em linhas gerais, basta
encontrar um método de cálculo eficiente no emaranhado para obter o período que
satisfaça a sequência. Nesse ponto surge o conceito da Transformada de Fourier Quântica
(QFT).
Trazendo o conceito da transformada em si, tem-se.
2𝑛 −1
1 𝑛
|𝑗⟩ → ∑ 𝑒 2𝜋𝑖𝑗𝑘/2 |𝑘⟩
√2𝑛 𝑘=0

onde |𝑗⟩ = |𝑗1 𝑗2 … 𝑗𝑛 ⟩ = 2 𝑗𝑛 + 21 𝑗𝑛−1 + ⋯ + 2𝑛−1 𝑗1 será encarado como o


0

emaranhado da sequencia 𝑛 𝑥 𝑚𝑜𝑑 𝑁 com período 𝑟.


De posse desse conhecimento, Shor define seu algoritmo como se segue.
1. Verificar se 𝑁 é par, e caso afirmativo retornar 2.
2. Determinar se 𝑁 = 𝑎𝑏 para 𝑏 ≥ 2 e se a resposta for positiva retornar a.

20
3. Escolher um número aleatório x (1 ≤ x ≤ N) e se 𝑀𝐷𝐶(𝑥, 𝑁) > 1 retornar
𝑀𝐷𝐶(𝑥, 𝑁).
4. Encontrar a ordem r de x, onde 𝑥 𝑟 = 1 𝑚𝑜𝑑 𝑁
𝑟 𝑟 𝑟
5. Se r for par e 𝑥 2 ≠ 1𝑚𝑜𝑑 𝑁, calcule 𝑀𝐷𝐶(𝑥 2 −1,N) e 𝑀𝐷𝐶(𝑥 2 + 1, 𝑁)
6. Se são soluções não triviais, retorne-as.
O cálculo da ordem de forma quântica é realizado colocando o emaranhado em
equiprobabilidade através da aplicação do operador de Hadamard.
Depois aplica-se o operador 𝑈 controlado pelo primeiro qubit no estado |1⟩
chegando no estado abaixo.
2𝑡 −1
1
|𝜓2 ⟩ = ∑ |𝑘⟩|𝑥 𝑘 𝑚𝑜𝑑 𝑁⟩
√2𝑡 𝑘=0

Inverte-se esse estado e realiza-se uma medida com o objetivo de encontrar o


maior número da sequência.
De posse disso chega-se na ordem que divide os fatores. Se esses fatores
obedecerem aos cálculos dos máximos divisores comuns, encontra-se os fatores primos
de 𝑁.

3.4 – Algortimo de Grover

O algoritmo de Grover consegue de forma otimizada efetuar buscas numa base de


dados desordenada com mais eficiência do que um algoritmo clássico equivalente
(Nielsen, 2005). Supondo que haja somente uma solução 𝑖0 , a busca na lista {0,1,2, … , 𝑁},
𝑜𝑛𝑑𝑒 𝑁 = 2𝑛 𝑐𝑜𝑚 𝑛 > 0 será realizada através da função.
1, 𝑠𝑒 𝑖 = 𝑖0
𝑓(𝑖) = {
0, 𝑠𝑒 𝑖 ≠ 𝑖0
É fácil notar que a complexidade desse algoritmo será dada pela quantidade de
vezes que a função for executada. Para solucionar o problema, Grover criou um
registrador com a quantidade de qubits suficiente para armazenar o emaranhado de todos
os 𝑁 elementos. Neste sentido, Grover utiliza 𝑁 = 2𝑛 𝑐𝑜𝑚 𝑛 > 2, onde n é a quantidade
de qubits e mais um qubit para armazenar o resultado da função 𝑓. O passo seguinte foi
a criação de um operador para procurar a informação quântica desejada. Na verdade esse
operador levou seu nome e consiste na aplicação de um operador de negação do vetor

21
inicial seguido do complemento normal do resultado, conforme será melhor detalhado a
seguir. Grover demonstra em seu trabalho (Portugal, 2012), através de uma análise de
grafos, que a complexidade dessa solução quântica é pelo menos quadraticamente menor
do que a aleatória clássica equivalente (Portugal, 2012). Portanto pode-se afirmar que
Grover acelerou, de forma quadrática, a procura da solução |𝑖0 ⟩.
Aplicando o operador de Hadamard nos dois registradores, tem-se.
𝑁−1
1
𝐻|𝜓⟩ = ∑ |𝑖⟩
√𝑁 𝑖=0

e
1
𝐻|1⟩ = (|0⟩ − |1⟩) = |−⟩
√2
Partindo da premissa de que existe um operador unitário 𝑈𝑓 com a seguinte
característica

|𝑖⟩|1⟩, 𝑠𝑒 𝑖 = 𝑖0
𝑈𝑓 (|𝑖⟩|0⟩) = {
|𝑖⟩|0⟩, 𝑠𝑒 𝑖 ≠ 𝑖0
Ou seja, ele altera o estado do registrador para encontrar o valor procurado e de
forma análogo, quando isso não ocorre.
|𝑖⟩|0⟩, 𝑠𝑒 𝑖 ≠ 𝑖0
𝑈𝑓 (|𝑖⟩|1⟩) = {
|𝑖⟩|1⟩, 𝑠𝑒 𝑖 = 𝑖0

Utilizando o mesmo artifício algébrico do algoritmo de Deutsch para os dois casos


acima 𝑈𝑓 (|𝑖⟩|𝑗⟩) = |𝑖⟩|𝑗⨁𝑓(𝑖)⟩ e aplicando o operador de Hadamard nos dois
registradores, tem-se:
𝑁−1 𝑁−1 𝑁−1
1 1 1 1
𝑈𝑓 (|𝜓⟩|−⟩) = 𝑈𝑓 ( ∑ |𝑖⟩ |−⟩) = ∑ 𝑈𝑓 |𝑖⟩|−⟩ = ∑ 𝑈𝑓 |𝑖⟩ (|0⟩ − |1⟩)
√𝑁 𝑖=0
√𝑁 𝑖=0
√𝑁 𝑖=0
√2
𝑁−1
1 1
= ∑ (𝑈𝑓 |𝑖⟩|0⟩ − 𝑈𝑓 |𝑖⟩|1⟩)
√𝑁 𝑖=0
√2
Lembrando que 𝑈𝑓 (|𝑖⟩|𝑗⟩) = |𝑖⟩|𝑗⨁𝑓(𝑖)⟩, logo
𝑈𝑓 |𝑖⟩|0⟩ = |𝑖⟩|0⨁𝑓(𝑖)⟩ = |𝑖⟩|𝑓(𝑖)⟩ e 𝑈𝑓 |𝑖⟩|1⟩ = |𝑖⟩|1⨁𝑓(𝑖)⟩

22
Logo
𝑁−1
1 1
𝑈𝑓 (|𝜓⟩|−⟩) = ∑ (|𝑖⟩|𝑓(𝑖)⟩ − |𝑖⟩|1⨁𝑓(𝑖)⟩)
√𝑁 𝑖=0
√2
𝑁−1
1 1
= ∑ |𝑖⟩(|𝑓(𝑖)⟩ − |1⨁𝑓(𝑖)⟩)
√𝑁 𝑖=0
√2

Analisando a expressão |𝑖⟩(|𝑓(𝑖)⟩ − |1⨁𝑓(𝑖)⟩) e lembrando que


1, 𝑠𝑒 𝑖 = 𝑖0
𝑓(𝑖) = {
0, 𝑠𝑒 𝑖 ≠ 𝑖0
|𝑖⟩(|1⟩ − |0⟩), 𝑠𝑒 𝑖 = 𝑖0
|𝑖⟩(|𝑓(𝑖)⟩ − |1⨁𝑓(𝑖)⟩) = {
|𝑖⟩(|0⟩ − |1⟩), 𝑠𝑒 𝑖 ≠ 𝑖0
Portanto
𝑁−1
1 1 1
𝑈𝑓 (|𝜓⟩|−⟩) = (( ∑ |𝑖⟩(|0⟩ − |1⟩)) + |𝑖0 ⟩(|1⟩ − |0⟩))
√𝑁 𝑖=0,𝑖≠𝑖0
√2 √2

𝑁−1
1
𝑈𝑓 (|𝜓⟩|−⟩) = (( ∑ |𝑖⟩|−⟩) − |𝑖0 ⟩|−⟩)
√𝑁 𝑖=0,𝑖≠𝑖0

𝑁−1
1
𝑈𝑓 (|𝜓⟩|−⟩) = (∑(−1) 𝑓(𝑖) |𝑖⟩|−⟩)
√𝑁 𝑖=0
𝑁−1
1
|𝜓1 ⟩ = (∑(−1) 𝑓(𝑖) |𝑖⟩) |−⟩
√𝑁 𝑖=0

O resultado obtido pode ser interpretado geometricamente com sendo o a reflexão


do vetor |𝜓⟩ em relação à |0⟩ segundo à figura 3.1.

Fig 3.1 – 3º Passo do Algoritmo de Grover

23
E resultado do segundo registrador permanece inalterado mesmo depois da
aplicação do operador.
Com o resultado do segundo não se altera e, de forma análoga, o resultado do
primeiro operador, Grover utilizou um segundo operador que fosse uma nova reflexão,
mas usou como base o próprio estado inicial.
Esse operador corresponde ao complemento normal, que é dado por |𝜓1 ⟩ = 𝐼 −
|𝜓⟩ (ver sessão 3.1), mostrada na Figura 3.2.

Fig 3.2 – 4º Passo do Algoritmo de Grover

Tal como descrito na estratégia adotada por Grover, estes procedimentos são
𝜋√𝑁
realizados sucessivamente até encontrar a solução, ou até 8
iterações.

Fig 3.3 – Sucessivas aplicações do Operador de Grover

Todo este processo pode ser escrito segundo a estratégia:


Repita
1.Reset de todos os qubits para |0⟩
2.Superposição do emaranhado
3.Negação do vetor
4.Complemento normal
5.Medida
Até encontrar a solução
𝜋√𝑁
ou até no máximo vezes.
8

24
Capítulo IV

Experimentos Iniciais

4.1 – QCL

O acrônimo QCL significa Quantum Computation Language e representa uma


linguagem de programação quântica (QPL) experimental e estruturada criada na Tese de
Doutorado de Bernard Ömer, Ph.D. (Ömer, 1998).
Por ser estruturada, a programação quântica em QCL procura manter os conceitos
da semântica clássica sob a forma de programas estruturados. Que por sua vez são
sequências de declarações e definições que são processados de cima para baixo utilizando
blocos como rotinas e sub-rotinas. Uma descrição mais detalhada sobre QCL é
apresentada no Apêndice A. A tabela 3.1 apresenta a referência entre os conceitos
quânticos e seus homólogos clássicos.
Tabela 4.1 – Analogia entre QCL e uma linguagem clássica

Conceito Clássico Análogo Quântico


modelo de máquina clássica arquitetura quântica híbrida
Variáveis registrador quântico
sub-rotinas operadores unitários
argumentos e tipos de tipos de dados quânticos
retorno
variáveis locais Registradores
memória dinâmica gestão do espaço
expressões booleanas condições quântica
execução condicional operadores condicionais
seleção afirmação SE quântica
laços condicionais bifurcação quântica
Não disponível execução de operadores inversos
Não disponível medição quântica

O registrador quântico é o conceito mais importante em computação quântica.


Pois enquanto num computador clássico, um bit necessita de milhares de elétrons para
sua representação, sendo seu estado lógico determinado pelo valor da expectativa de seu
conteúdo, em um computador quântico a informação é representada diretamente como o

25
estado comum de vários qubits. Essa representação pode ser realizada através do spin de
uma partícula, da polarização de um fóton ou do estado de excitação de um íon (Ömer,
1998).
Por se tratar de uma estrutura híbrida, para o desenvolvedor, o programa funciona
exatamente como qualquer outro programa clássico, pois ele receberá entradas clássicas
e produzirá saídas clássicas também, mas seu processamento será quantizado.
A QCL será utiliza nessa dissertação para a confecção de um algoritmo quântico
baseado no algoritmo de Grover. Como um número complexo pode ser representado na
forma ortogonal e na geométrica, recomenda-se que a funcionalidade deum programa
quântico seja interpretada através da visualização gráfica dos vetores de estado em bases
ortogonais.
Na figura 3.1 tem-se a representação de um vetor de estado qualquer através da
esfera de Bloch.

Fig 4.1 – Esfera de Bloch.

Como |𝜓⟩ = 𝛼|0⟩ + 𝛽|1⟩, onde 𝛼 2 + 𝛽 2 = 1


Com ajuda da Esfera de Bloch, um vetor de estado qualquer pode ser reescrito da
seguinte forma:
𝜃 𝜃
|𝜓⟩ = 𝑒 𝑖𝛾 (𝑐𝑜𝑠 |0⟩ + 𝑒 𝑖𝜑 𝑠𝑒𝑛 |1⟩)
2 2
Como o fator 𝑒 𝑖𝛾 pode ser descartado por não ser observável em mecânica
quântica, logo:
𝜃 𝜃
|𝜓⟩ = 𝑐𝑜𝑠 |0⟩ + 𝑒 𝑖𝜑 𝑠𝑒𝑛 |1⟩
2 2

26
Mas as bases ortogonais em espaços vetoriais complexos de duas dimensões é o
quadro matemático e conceitual mais utilizado para representar um estado quântico de
um vetor de estado qualquer.

Fig 4.2 – Vetor de estado na base ortogonal.

Sendo,
1
|0⟩ = ( )
0
0
|1⟩ = ( )
1
Logo,

|𝜓⟩ = 𝛼|0⟩ + 𝛽|1⟩ = 𝛼(10) + 𝛽(01) = (𝛽𝛼)

4.2 – Prova de Conceito

Para que haja uma análise entre as vantagens e desvantagens quanto à utilização
das duas formas de computação, será proposto um problema básico que pode ser resolvido
pelos dois métodos computacionais: o clássico e o quântico. O problema abordado será a
busca de um elemento numa base de dados desordenada. Serão apresentadas quatro
soluções para o problema, sendo as três primeiras clássicas e a quarta quântica. As
clássicas são de fácil compreensão, mas a quântica exigirá do leitor o conhecimento
prévio do algoritmo quântico de Grover, descrito na seção 3.4.
A primeira solução desta prova de conceito consiste numa busca sequencial numa
massa de dados aleatórios com complexidade 𝑂(𝑁), 𝑐𝑜𝑚 𝑁 > 0. Trata-se apenas de um

27
experimento de controle no qual se implementa uma busca segundo as técnicas
tradicionais.
A segunda solução, por sua vez, consiste numa busca sequencial numa massa de
dados aleatórios com uma procura aleatória também. Nesse tipo de solução deve-se
respeitar os elementos procurados para que os mesmos não se repitam, evitando assim o
problema da parada. Neste caso, a solução também é clássica, porém começa a utilizar
um fator de aleatoriedade no processo de localização do elemento a ser encontrado.
A terceira mostrará a mesma solução, porém realizada através de uma amostra
previamente ordenada para futura pesquisa binária.
Por fim, a solução quântica é calcada no algoritmo de Grover.
Neste sentido, foi criado um cenário composto por quatro programas em Linux
Centos 6.3, cada um contendo uma solução. Depois de uma bateria de execuções, as
interações foram coletadas para futura análise.
A primeira solução clássica, confeccionada em gcc, faz uma busca sequencial
simples nos dados aleatórios com complexidade 𝑂(𝑁), 𝑐𝑜𝑚 𝑁 > 0. A seguir é
apresentada a solução codificada em “C” desenvolvida para esse trabalho.
classical_search.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

void find(int no, int n) {


int i=0,j=0,x;
int q[n];
bool found;

for(i=0;i<n;i++) q[i]= -1;

while (j < n) {
x=rand()%n;

i=0;
found = false;
for(i=0; (i<n); i++)
if (!found && q[i]==x) {found = true;}
if (!found) { q[j]=x; j++; }
}

printf("Range into 0 and %i\n", (n-1));


printf("\n");

printf("Searching for %d...\n", no);


i=0;
found=false; 28
while (i<n && !found) {
if (q[i] == no) {
found=true;
printf("%d found\n", no);
}
else {
printf("%da interacao q=%d\n",i, q[i]);
i++;
}
}
}

void main(int argc, const char* argv[]) {

if (argc != 3) {
fprintf(stderr, "Use: %s <numero de procura><quantidade de numeros>\n",
argv[0]);
exit(EXIT_FAILURE);
}

int No = atoi(argv[1]);
int N = atoi(argv[2]);

system("clear");
printf("N = %i\n\n", N);
find(No, N);
exit(EXIT_SUCCESS);
}

29
A segunda solução clássica faz a mesma procura nos dados, mas de forma mais
otimizada, pois além de ser randômica, ela sempre procura em itens que ainda não foram
procurados, a fim de evitar o problema da parada. O código implementado encontra-se
descrito a seguir.

random_search.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

void random_vector(int *v, int n) {


int i=0,j=0,x;
bool found;

for(i=0;i<n;i++) v[i]= -1;

while (j < n) {
x=random()%n;

i=0;
found = false;
for(i=0; (i<n); i++)
if (!found && v[i]==x) {found = true;}
if (!found) { v[j]=x; j++; }
}
}

void main(int argc, const char* argv[]) {


if (argc != 3) {
fprintf(stderr, "Use: %s <numero de procura><quantidade de numeros>\n",
argv[0]);
exit(EXIT_FAILURE);
}

int no = atoi(argv[1]);
int n = atoi(argv[2]);
int q[n], r[n];
bool found;

srand(time(NULL));
system("clear");
printf("N = %i\n\n", n);
printf("Range into 0 and %i\n", (n-1));
printf("\n");

30
random_vector(q, n);
random_vector(r, n);

printf("Searching for %d...\n", no);


int i=0;
found=false;
while (i<n && !found) {
if (q[r[i]] == no) {
found=true;
printf("%d found\n", no);
}
else {
printf("%da interacao q=%d\n",i, q[r[i]]);
i++;
}
}

exit(i);
}

31
A terceira solução foi implementada numa lista ordenada com pesquisa binária.
De forma clássica, com complexidade 𝑂(𝑙𝑜𝑔2 𝑁), não existe nada mais rápido do que
esse mecanismo de procura. Segue a implementação construída para esse trabalho.
bin_search.sh
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

void find(int no, int n) {


int i=0,j=0,x;
int q[n];
bool found;

for(i=0;i<n;i++) q[i]= i;

printf("Range into 0 and %i\n", (n-1));


// for(i=0;i<n;i++) { printf("%d\n",q[i]); }
printf("\n");

printf("Searching for %d...\n", no);


int e, m, d;
e = 0;
d = n-1;
i=0;
while (e <= d) {
i++;
m = (e + d)/2;
if (q[m] == no) {
printf("%d found\n", no);
return;
}
else
printf("%da interacao q=%d\n",i, q[m]);

if (q[m] < no) e = m + 1;


else d = m - 1;
}
}

32
void main(int argc, const char* argv[]) {

if (argc != 3) {
fprintf(stderr, "Use: %s <numero de procura><quantidade de numeros>\n",
argv[0]);
exit(EXIT_FAILURE);
}

int No = atoi(argv[1]);
int N = atoi(argv[2]);

system("clear");
printf("N = %i\n\n", N);

find(No, N);
exit(EXIT_SUCCESS);
}

33
A quarta solução é um programa quântico desenvolvido em QCL. Como foi dito
anteriormente o mesmo foi baseado no algoritmo de Grover. Conforme descrito na sessão
3.4 a proposta do algoritmo de Grove é aplicar o operador no emaranhado que encapsula
o espaço das soluções até o limite de interações possíveis (m) para aumentar a
probabilidade de efetuar a medida correta no elemento a ser encontrado. Como trata-se
de um algoritmo quântico esse procedimento é realizado até encontrar a solução
propriamente dita. Segue seu fonte.
qufunct query(qureg x,quvoid f,int n) {
int i;
for i=0 to #x-1 { // x -> NOT (x XOR n)
if not bit(n,i) { Not(x[i]); }
}
CNot(f,x); // flip f if x=1111..
for i=0 to #x-1 { // x <- NOT (x XOR n)
if not bit(n,i) { !Not(x[i]); }
}
}

operator diffuse(qureg q) {
H(q); // Hadamard Transform
Not(q); // Invert q
CPhase(pi,q); // Rotate if q=1111..
!Not(q); // undo inversion
!H(q); // undo Hadamard Transform
}

procedure find(int no, int n) {


int i;
int j = 0;
int x;
int l=floor(log(n,2))+1; // no. of qubits
int m=ceil(pi/8*sqrt(2^l));
int r=(2^l)-1;
qureg q[l];
qureg f[1];

print "N = ",n;


print "n = ",l;
print "Range into 0 and ",r;
print "";
print "Searching ",no;

reset;
H(q);

34
{
for i=1 to m {
reset;
H(q);
query(q,f,no);
CPhase(pi,f);
!query(q,f,no);
diffuse(q);
}
j= j+1;
measure q,x;
print j,"a interacao encontrado ",x;
} until (x==no);

print x, " found";


dump q;
}

find(No, N);

Desta forma, para compreender melhor o algoritmo, será realizado um exemplo


instanciado e com uma explicação mais detalhada sobre os motivos que levaram a
determinação dos cálculos descritos neste mesmo algoritmo ora proposto.
Supondo que o número procurado seja 𝑁𝑜 = 5 = 101𝑏 e que o número de
elementos da lista seja 𝑁 = 8 = 23 , 𝑙𝑜𝑔𝑜 𝑛 = 3.

1º Passo.
Colocar o registrador quântico em superposição.
𝑁
1
|𝜓⟩ = ∑ |𝑖⟩
√𝑁 𝑖=0
Logo,

|000⟩ + |001⟩ + |010⟩ + |011⟩ + |100⟩ + |101⟩ + |110⟩ + |111⟩


|𝜓⟩ =
√8

2º Passo.
Aplicar a primeira etapa do operador de Grover.
|𝜓1 ⟩ = 𝑈𝑓 |𝜓⟩

35
𝑁
1
|𝜓1 ⟩ = 𝑈𝑓 ∑ |𝑖⟩
√𝑁 𝑖=0
𝑁
1
|𝜓1 ⟩ = ∑ 𝑈𝑓 |𝑖⟩
√𝑁 𝑖=0

Como
1, 𝑠𝑒 𝑖 = 𝑖0
𝑓(𝑖) = { , logo 𝑈𝑓 = −1𝑓(𝑖)
0, 𝑠𝑒 𝑖 ≠ 𝑖0
Então
𝑁
1
|𝜓1 ⟩ = ∑ −1𝑓(𝑖) |𝑖⟩
√𝑁 𝑖=0
Portanto,
|000⟩ + |001⟩ + |010⟩ + |011⟩ + |100⟩ − |101⟩ + |110⟩ + |111⟩
|𝜓1 ⟩ =
√8

3º Passo.
Aplicar a segunda etapa do operador de Grover.
|𝜓2 ⟩ = 𝑅|𝜓1 ⟩

Como,
𝑅 = 2|𝜓⟩⟨𝜓| − 𝐼

Então,
𝑅|𝜓1 ⟩ = (2|𝜓⟩⟨𝜓| − 𝐼)|𝜓1 ⟩

Com uma manobra algébrica, pode-se escrever |𝜓1 ⟩ da seguinte forma:


2
|𝜓1 ⟩ = |𝜓⟩ − |𝑖0 ⟩
√𝑁

36
Logo,
2
|𝜓2 ⟩ = 𝑅|𝜓1 ⟩ = (2|𝜓⟩⟨𝜓| − 𝐼)|𝜓1 ⟩ = (2|𝜓⟩⟨𝜓| − 𝐼)(|𝜓⟩ − |𝑖0 ⟩)
√𝑁
4 2
|𝜓2 ⟩ = 2⟨𝜓|𝜓⟩|𝜓⟩ − ( ⟨𝜓|𝑖0 ⟩) − |𝜓⟩ + |𝑖0 ⟩
√𝑁 √𝑁
𝑁−4 2
|𝜓2 ⟩ = ( ) |𝜓⟩ + |𝑖0 ⟩
𝑁 √𝑁

Visualizando as probabilidades das possibilidades,


|000⟩ + |001⟩ + |010⟩ + |011⟩ + |100⟩ + |101⟩ + |110⟩ + |111⟩ 2|101⟩
|𝜓2 ⟩ = +
2√8 √8
|000⟩ + |001⟩ + |010⟩ + |011⟩ + |100⟩ + |110⟩ + |111⟩ 5|101⟩
|𝜓2 ⟩ = +
2√8 2√8
Ou seja, se uma medição fosse realizada agora, as chances de se obter a solução
foram aumentadas consideravelmente, pois caiu pela a metade a probabilidade das outras
possibilidades e aumentou-se em 2,5 a probabilidade da solução.
Depois desse passo a passo, fica fácil perceber que com mais uma aplicação do
operador de Grover no emaranhado, as probabilidades das outras possibilidades se
reduzirão a zero e a solução ficará com 100% de chance.
A figura 4.1 denota as operações acima descritas.

Fig 4.1 –Manobra vetorial do operador de Grover

37
O benchmark dos dados coletados foi criado para 𝑁0 igual ao número procurado
e 𝑁 a quantidade de números desordenados. Os dados foram obtidos através de scripts
executados em paralelo numa máquina Virtual Linux 64x com 4GB de RAM. Nos casos
em QCL foi necessário um tempo alongado de processamento (~ 6 horas) por falta de
uma estrutura quântica apropriada para executá-lo.
Todo o processamento foi executado realizando 10 repetições para cada uma das
três listas de tamanho variável definido por 𝑁0 . Além disso em cada uma das listas
também são realizadas buscas de 10 𝑁 distintos. Segue o script que efetua todo esse
processamento.

benchmark.sh
rm -rf *.log
for i in 0 1 2 3 4 5 6 7 8 9
do
./benchmark_40.sh
./benchmark_400.sh
./benchmark_4000.sh
done

benchmark_40.sh
N=40
for i in 2 4 6 7 9 19 22 27 33 37
do
./bench.sh $i $N
done

benchmark_400.sh
N=400
for i in 55 58 70 82 90 111 123 288 342 399
do
./bench.sh $i $N
done

benchmark_4000.sh
N=4000
for i in 567 589 685 698 759 900 1976 2086 3096 3985
do
./bench.sh $i $N
done

Totalizando 100 valores de interações para cada 𝑁0 .

38
Onde,
bench.sh
I=`echo $1 | tr -d '\r'`
N=`echo $2 | tr -d '\r'`
iC=`./classical_search $I $N | grep interacao | wc -l `
C=`echo $iC | tr -d '\r'`
iR=`./random_search $I $N | grep interacao | wc -l`
Para 𝑁 = $iR
R=`echo 50 tem-se
| tr -d a'\r'`
seguinte distribuição de médias para as interações.
iB=`./bin_search $I $N | grep interacao | wc -l`
B=`echo $iB | tr -d '\r'`
iQ=`./quantum_search.sh $I $N | grep interacao | wc -l`
Q=`echo $iQ | tr -d '\r'`

echo $I" "$N" "$C" "$R" "$B" "$iQ >> benchmark_${N}.log

Os gráficos a seguir mostram os valores obtidos no benchmark para as buscas


utilizando valores de tamanho de lista 𝑁 = {40, 400 𝑒 4000}.
No eixo horizontal dos gráficos são apresentados os valores médios das interações
colocados à direita de cada tipo de pesquisa. O valor a ser procurado está na primeira
linha acima dos tipos e varia segundo a distribuição abaixo:
 𝑁0(𝑁=40) = {2, 4, 6, 7, 9, 19, 22, 27, 33 𝑒 37}
 𝑁0(𝑁=400) = {55, 58, 70, 82, 90, 111, 123, 288, 342 𝑒 399}
 𝑁0(𝑁=4000) = {567, 589, 685, 698, 759, 900, 1976, 2086, 3096 𝑒 3985}
Para cada número procurado tem-se a distribuição das médias de interações
mostradas através das barras coloridas segundo o tipo equivalente.

39
Para a quantidade de números desordenados 𝑁 = 40 chegou-se a seguinte
distribuição de médias.
30
25
de Interações

20
Média

15
10
5
0
2 4 6 7 9 19 22 27 33 37
Sequencial 20 26,5 18 15,9 22,7 18 18 22,1 26,3 23,1
Randômica 18 15,4 26 25,3 16 21,3 20 13,8 11,4 19,2
Binária 4 2 3 4 1 0 4 4 5 3
Quântica 9,1 7,4 11 7 5,5 7,9 6,3 6,9 7,4 5,6

Fig 4.2 –Média de Interações para N=40 com 10 repetições para cada 𝑁0

Para a quantidade de números desordenados 𝑁 = 400 a distribuição de médias


comporta-se dessa forma.
300
250
de Interações

200
Média

150
100
50
0
55 58 70 82 90 111 123 288 342 399
Sequencial 192,6 200,5 211,1 216,4 177,9 177,5 199,7 159,5 207,1 172,2
Randômica 181,8 183,9 171,2 242,9 196,2 240,7 203,4 178,9 238,3 222,7
Binária 5 6 6 8 7 4 8 8 5 8
Quântica 62,2 83,9 74,3 45,4 55,8 83,8 47,3 84,5 29,4 55,1

Fig 4.3 –Média de Interações para N=400 com 10 repetições para cada 𝑁0

40
Para a quantidade de números desordenados 𝑁 = 4000, tem-se.
3000
de Interações 2500
2000
Média

1500
1000
500
0
567 589 685 698 759 900 1976 2086 3096 3985
Sequencial 1802,4 2555,9 1731,9 1713 1806,9 1294 2273 2197,1 2135,6 2437
Randômica 2092 2285 1924,6 2000,9 1780,9 2446,5 2116,6 1558,2 2443 1954,7
Binária 11 11 11 11 11 11 11 10 9 11
Quântica 343,3 520,5 423 444,3 460,7 450,8 746,4 483 397 505,5

Fig 4.4 –Média de Interações para N=4000 com 10 repetições para cada 𝑁0

Média da quantidade de interações das 100 buscas realizadas para cada valor de N.
Tabela 4.1 – Média das Interações

Sequencial Sequencial
N Simples Randômico Binário Grover
40 21,19 18,77 3,00 7,41
400 191,45 206,00 6,50 62,17
4000 1994,75 2060,33 10,70 477,45

Desvio padrão da quantidade de interações das 100 buscas realizadas para cada valor.
Tabela 4.2– Desvio Padrão das Interações

Sequencial Sequencial
N Simples Randômico Binário Grover
40 10,64 11,71 1,49 6,21
400 114,80 103,93 1,44 57,79
4000 1181,89 1221,86 0,64 440,30

41
De uma forma geral, as pesquisas binárias levaram vantagem por se tratar de
pesquisas realizadas em dados previamente ordenados, algo que contribui
significativamente para um resultado mais favorável. O algoritmo quântico mostra uma
resposta bastante eficiente quando se utiliza um emaranhado de 8 qubits se comparado
aos equivalentes clássicos. Muito embora 𝑂(√𝑁) < 𝑂(𝑁), com o acréscimo de apenas 1
qubit, o algoritmo quântico perde em eficiência. Por conta disso, se 𝑁 crescer demais, o
problema se torna intratável para o simulador quântico.
Também é interessante notar que o algoritmo randômico clássico, se comparado
à pesquisa sequencial simples, mostra-se mais eficiente somente quando a quantidade de
elementos ultrapassa de 400.
Na figura 4.5 e na tabela 4.3 vemos um comparativo entre as quantidades máximas
de buscas envolvidas. Analisando as pesquisas sob esse ponto de vista, nota-se que a
busca puramente quântica não será pior do que a desordenada e nunca melhor do que a
binária. Vale ressaltar que, no cálculo da complexidade binária, o custo do balanceamento
contínuo da árvore não foi levado em consideração. Caso fosse, esse acréscimo de custo
faria com que a complexidade quântica se aproximasse ainda mais da binária.
Tabela 4.3 – Quantidades máximas de buscas

Quantidades máximas de buscas


N Desordenada Binária Quântica
40 40 3,68887945 6,32455532
400 400 5,99146455 20
4000 4000 8,29404964 63,2455532

Desordenada - O(N)

Binária - O(logN)

Quântica - O(√N)

Fig 4.5 –Comparativo entre as Complexidade

42
Nesse capítulo foi realizado uma comparação entre métodos de buscas tradicionais
e uma busca quântica. Foi observado que em listas desordenadas o algoritmo quântico é
melhor que o desordenado clássico equivalente. Entretanto esse mesmo algoritmo é mais
lento que uma busca binária clássica que está ordenada. Contudo existe um engano na
comparação ingênua e direta desses dois algoritmos. O algoritmo de busca binário
clássica leva uma vantagem significativa por encontrar um conjunto de dados
previamente ordenados e o tempo de busca não leva em consideração o tempo para
ordenar os dados. O algoritmo quântico por sua vez não necessita dessa ordenação.
A complexidade do algoritmo mais rápido de ordenação de dados (quick sort) é
𝑂(𝑁𝑙𝑜𝑔2𝑁 ).
A complexidade da busca binária é 𝑂(𝑙𝑜𝑔2𝑁 ).
A complexidade de Grover é 𝑂(√𝑁).

Tabela 4.4 – Comparativo de Complexidades


𝟐
n 𝒏 𝒍𝒐𝒈𝑵
𝟐 √𝑵 𝒏𝒍𝒐𝒈𝑵𝟐 𝒏𝒍𝒐𝒈𝑵 𝑵
𝟐 + 𝒍𝒐𝒈𝟐 𝒏𝒍𝒐𝒈𝑵 𝑵
𝟐 + 𝒍𝒐𝒈𝟐 -√𝑵
2 4 1,38629 2,00000 2,77259 4,15888 2,15888
3 8 2,07944 2,82843 6,23832 8,31777 5,48934
4 16 2,77259 4,00000 11,09035 13,86294 9,86294
5 32 3,46574 5,65685 17,32868 20,79442 15,13756
6 64 4,15888 8,00000 24,95330 29,11218 21,11218
7 128 4,85203 11,31371 33,96421 38,81624 27,50253
8 256 5,54518 16,00000 44,36142 49,90660 33,90660

Logo o ganho real entre a busca quântica e a mais rápida clássica será dado pelo
custo da solução (ordenação + pesquisa binária – busca quântica), isto é, 𝑂(𝑁𝑙𝑜𝑔2𝑁 ) +
𝑂(𝑙𝑜𝑔2𝑁 ) − 𝑂(√𝑁) e está representado em números na tabela 4.4. Uma informação
interessante desses dados é que na medida que 𝑁 aumenta o ganho também aumenta e
esse fenômeno não ocorre no cenário clássico.
Sob essa ótica, surge o questionamento sobre o comportamento de outros
algoritmos quânticos em problemas de decisão mais sofisticados. O capitulo a seguir trata
de uma implementação quântica para um problema dessa natureza.

43
Capítulo V

Problema das N-Rainhas

5.1 – Definição do Problema

O problema das N-Rainhas, originalmente introduzido em 1850 por Carl Gauss,


pode ser definido como sendo os conjuntos de posições de 𝑁 rainhas em um tabuleiro de
xadrez 𝑁𝑥𝑁, tais que nenhuma rainha pode ser atacada por qualquer outra. Neste sentido,
a Figura 5.1 representa os possíveis movimentos de uma rainha e as Figuras 5.2 e 5.3
mostram duas combinações de posições onde a definição do problema não permite que
ocorra por conta dos ataques em coluna e diagonal.

Fig 5.1 Fig 5.2 Fig 5.3

A Figura 5.4 apresenta uma solução possível para 𝑁 = 4.

Fig 5.4

É fácil notar que, em relação a figura 5.4, existe outra solução simétrica invertendo
as posições a partir da segunda coluna. Ou seja, para 𝑁 = 4 tem-se duas soluções 𝑁 =
{3,1,4,2} 𝑒 {2,4,1,3}, onde cada elemento i do conjunto representa a posição de cima para
baixo da rainha na i-ésima coluna do tabuleiro.

44
Desde sua concepção foram criados inúmeros trabalhos relacionados ao problema.
Existe uma coletânea bastante densa com relação ao mesmo que pode ser pesquisada em
(Kosters, 2013). Nela constam 335 referências sobre o problema, onde o paper ”A Survey
of Known Results and Research Areas for n-Queens" é considerado a síntese teórica mais
importante (Kosters, 2013) do N-Queens. Nele encontramos a explicação pormenorizada
sobre os mais relevantes Teoremas e Conjecturas acerca da questão.
Os Teoremas e conjecturas tentam de alguma maneira amenizar o processo de
cálculo das soluções, validá-la e prever a quantidade de soluções para um determinado
valor de 𝑁. Esse movimento se faz necessário, pois para 𝑁 = 24 obtem-se
227514171973736 soluções. E para 𝑁 = 25 chega-se a espantosas
2207893435808352 soluções (Bell e Stevens, 2009). Sendo que, só é possível chegar
nessas soluções utilizando-se computação em grade com algoritmos em paralelo.
A solução do problema das rainhas é relevante, pois sua aplicação pode ser
encontrada em sistemas de armazenamento de memória em paralelo, testes de VLSI
(Very Large Scale Integration), controle de tráfego de redes de computadores e prevenção
de deadlock em Sistemas Operacionais.

5.2 – Soluções Clássicas

Em síntese, as várias soluções publicadas dependem de uma fórmula específica


para a arrumação das rainhas ou de transposição de soluções menores para soluções com
valores maiores de 𝑁. É interessante observar que soluções empíricas do problema com
tabuleiros menores mostram que o número de soluções aumenta exponencialmente com
o aumento de 𝑁. Por exemplo, para 𝑁 = 4, . . . ,11 (Bell e Stevens, 2009), tem-se
respectivamente as quantidades de soluções {2, 10, 4, 40, 92, 352, 724, 2680} (Bell e
Stevens, 2009). Por conta disso, algumas soluções alternativas veem sendo propostas.
O algoritmo clássico mais famoso capaz de gerar sistematicamente todos os
possíveis conjuntos de soluções para um determinado valor de 𝑁 é o backtracking (
Kosters, 2013). A abordagem recursiva com “backtracking” pode ser resumida da forma
a seguir.
Colocam-se as rainhas, uma de cada vez, por linha e por coluna nessa ordem. Dada
uma linha, escolhe-se uma coluna de tal forma que a mesma não se respeita (para que não

45
haja ataque por coluna) e verifica se ocorre algum ataque em diagonal. Caso não ocorra
ataque, o algoritmo é executado de forma recursiva para a próxima linha. Caso já não
existam posições seguras, volta-se a rainha para a linha anterior e procura-se uma nova
coluna. Se não for possível encontrar uma coluna segura para essa linha e a quantidade
de linhas é menor ou igual a 𝑁 e algoritmo descarta essa combinação de linha e coluna.
Se a rainha estiver segura, o processo continua para a próxima linha com outra coluna
ainda não escolhida (Fernandes e Alho 2004).
Na prática, a abordagem por backtracking produz uma classe muito limitada de
soluções à medida que 𝑁 cresce. Além disso, devido a complexidade 𝑂(𝑁!) dessa
solução, é extremamente custoso para computadores clássicos montar todas as busca que
o problema exige. Desse modo, vários autores veem propondo outras técnicas de busca
eficientes para superar essa limitação. Estes métodos incluem métodos de busca heurística
(Pothumani, 2009), técnicas de minimização de busca e conflitos locais (Pothumani,
2009). Recentemente, os avanços da pesquisa na área de redes neurais têm gerado vários
artigos propondo soluções para o problema. O uso das redes de Hopfield (Pothumani,
2009) tem sido aplicado para o problema de N–Queens, pois trata-se de uma rede neural
simples que é capaz de armazenar certos padrões de modo semelhante ao cérebro, onde o
padrão completo para um determinado problema pode ser recuperado desde que a rede
seja apresentada com dados parciais.
Para valores de 𝑁 > 100 (Sosic, 1993) é interessante que se utilize paralelismo
clássico. O algoritmo sequencial de tempo linear é exemplo simples de solução e mostra-
se bastante eficiente.
Essa técnica pode ser dividida em dois passos: o inicial e o final. Durante o passo
inicial as rainhas são colocadas em sucessivas colunas da esquerda para a direita. Em cada
coluna a posição é escolhida randomicamente. Se ocorrer algum conflito com a coluna
escolhida, a posição da mesma passa para a próxima posição à direita. Esse processo é
repetido até preencher todas as linhas com alguma rainha. No passo final ocorre a
verificação de possíveis conflitos levando em consideração as diagonais de todas as
rainhas colocadas. É absolutamente óbvio que se ocorrer algum conflito, é escolhida outra
posição para a rainha de tal forma que a mesma não sofra nenhum ataque.
Esse algoritmo encontra uma solução num tempo proporcional segundo a fórmula
abaixo.
𝑇(𝑁) = 3,18𝑁 + 72𝐶𝑁

46
Onde 𝐶𝑁 é uma constante dependente de 𝑁, denotanto o número de rainhas com
conflito depois do passo incial. Por exemplo, para 𝑁 = 1000000 tem-se 999950 rainhas
sem conflito no primeiro passo com 50 rainhas, em média, relativas aos conflitos do
segundo passo (Sosic, 1993). Todo esse cálculo sequencial é paralelizado para cada
coluna e o ideal é colocar um processador para cada linha, ou seja, a quantidade de cores
deve ser igual à quantidade de rainhas.

5.3 – Solução Híbrida

Como o algoritmo de backtracking é capaz de encontrar todas as soluções para


um determinado valor de 𝑁, mas à medida que 𝑁 aumenta o mesmo mostra-se intratável
para a computação clássica e com o intuito de aumentar a eficiência na busca de uma
solução com menor complexidade, pode-se avançar na computação da solução tratando
o problema sob a ótica da Computação Quântica.
O processo de construção desse novo algoritmo passou por 3 etapas de
amadurecimento elencadas a seguir em ordem cronológica:
1. Construção de um algoritmo quântico baseado no backtraking utilizando
o algoritmo de Grover.
2. Tentativa de mapear as possíveis séries das soluções com intuito de
transformar a solução em QFT (Quantum Fourier Transformation).
3. Algoritmo baseado em busca quântica.

O algoritmo de busca de Grover foi utilizado para encontrar uma possível posição
numa determinada linha a partir de um algoritmo similar ao de backtraking. Entretanto,
essa solução mostrou ser “ingênua”, pois uma simples busca por posições válidas numa
determinada linha, além de não trazer o caráter quântico desejado à solução, trazia perda
de eficiência no momento da execução. Isso aconteceu devido à necessidade de realizar
pesquisas baseadas no algoritmo de Grove para cada linha do tabuleiro, isto é, realizar
classicamente sucessivas chamadas do algoritmo quântico e assumindo o ônus da
preparação dos disparos dos mesmos. Na verdade, introduzir o caráter quântico desta
forma não explora todo o potencial de uma possível solução quântica para esse problema,
portanto esse caminho foi desconsiderado.

47
A segunda etapa foi concebida devido ao aumento da quantidade de soluções à
medida que 𝑁 cresce. Isso gerou uma suspeita de um possível comportamento funcional
num determinado vetor solução, por exemplo.
Com ajuda do programa confeccionado em “C” utilizando o algoritmo de
backtraking que consta no Apêndice D, para 𝑁 = 4 (4 linhas e 4 colunas) pode-se criar
uma série cujos elementos são pares ordenados <linha, coluna> que representa as
posições das rainhas no tabuleiro par uma determinada solução. Por exemplo a série 1 da
figura 5.5 representa a seguinte solução {<1,2>, <2,4>, <3,1>, <4,3>}.
5

3
Série1
2 Série2
1

0
1 2 3 4

Fig 5.5- Soluções de um tabuleiro 4X4

48
Para 𝑁 = 5 as séries comportam-se da forma descrita na figura 5.6.
6
Série1
5 Série2
Série3
4
Série4

3 Série5
Série6
2
Série7

1 Série8
Série9
0
Série10
1 2 3 4 5

Fig 5.6 – Soluções para um tabuleir 5X5

Para 𝑁 = 8 tem-se a distribuição a seguir.


9

0
1 2 3 4 5 6 7 8

Fig 5.7 – Soluções para um tabuleiro 8X8

Desta forma, visualmente a suspeita de um possível comportamento funcional faz


sentido. Como o conjunto de soluções pode ser dividido por dois e invertido, com uma
manobra matemática, é possível colocar o conjunto de soluções para 𝑁 = 4 sob a forma
de números complexos.

49
As soluções para 𝑁 = 4 são {2, 4, 1, 3} 𝑒 {3, 1, 4, 2}. Mas como uma pode ser
obtida a partir da outra invertendo o tabuleiro em 180º. É possível expressá-las da seguinte
forma.
{−3 − 𝑖, −1 + 3𝑖, 1 − 3𝑖, 3 + 𝑖}
Para se obter o resultado desejado a leitura dos números complexos deve ser
realizada da seguinte forma para cada metade da solução acima.
Para a primeira metade deve-se:
1. Multiplicar por 𝑖.
2. Remover a parte imaginária
3. Multiplicar por −1.
Para a segunda metade deve-se:
1. Multiplicar por 𝑖.
2. Obter o conjugado complexo do resulto e descartar a parte real
3. Multiplicar por 𝑖.
Ou seja, aplicando o método acima respectivamente e sucessivamente tem-se:

{−3 − 𝑖, −1 + 3𝑖, 1 − 3𝑖, 3 + 𝑖}


{1 − 3𝑖, −3 − 𝑖, 3 + 𝑖, −1 + 3𝑖}
{1 − 3, −3 − 1, −𝑖, −3𝑖}
{−2, −4, −𝑖, −3𝑖}
{2, 4, 1,3}
Agora invertendo-se o método para cada metade.
{−3 − 𝑖, −1 + 3𝑖, 1 − 3𝑖, 3 + 𝑖}
{1 − 3𝑖, −3 − 𝑖, 3 + 𝑖, −1 + 3𝑖}
{3,1,3 + 1, −1 + 3}
{3,1,4,2}

Para 𝑁 = 4, esse método de representação é útil, porém para todas as soluções


variando o valor de 𝑁 não é possível encontrar uma função que forme a sequência das
soluções. Por isso a utilização da Transformada de Fourier Quântica também foi
descartada.
A proposta híbrida para resolver o problema pode ser encarada como uma busca
quântica e sequencial dependendo da coluna. A busca por novas possibilidades de

50
posições válidas para as damas é obtida através da aplicação de um operador unitário que
ajusta as possibilidades válidas. Esse operador é obtido da forma descrita a seguir.
Para cada linha ocorrerá uma busca na lista 𝑁 = {0, 1, 2, … , 𝑁 − 1}, onde 𝑛 =
𝑙𝑜𝑔2 (𝑁) é a quantidade de q-bits do registrador mais um q-bit para armazernar o resultado
da função
𝑓: = {0, 1, 2, … , 𝑁 − 1}, → {0,1}.
Definida da seguinte forma:
1, 𝑠𝑒 𝑖 = 𝑖0
𝑓(𝑖) = {
0, 𝑠𝑒 𝑖 ≠ 𝑖0
onde, para todo 𝑓(𝑖) = 1 tem-se a probabilidade confirmada de se encontrar uma
posição inválida e 𝑓(𝑖) = 0 representa o conjunto de possibilidades válidas.
O conjunto de possibilidades inválidas é facilmente calculado a partir dos valores
das damas escolhidas em cada linha levando em consideração as colunas e as diagonais.
Desse modo, um estado qualquer pode ser definido como
𝑁−1
1
|𝜑⟩ = ∑ |𝑖⟩
√𝑁 𝑖=0

E supondo uma posição inválida qualquer diferente de |𝑖0 ⟩.


Logo o estado que representa o conjunto de todas as possibilidades válidas pode
ser representado por.
2
|𝜑1 ⟩ = |𝜑⟩ − |𝑖0 ⟩
√𝑁
Como
𝑁−1
1
|𝜑1 ⟩ = ∑(−1) 𝑓(𝑖) |𝑖⟩
√𝑁 𝑖=0

Logo, 𝑓(𝑖) = 0 prepara o emaranhado com possibilidades de posições válidas


para futuras medições. Com isso as chances de se obter uma posição válida para cada
linha aumentam a eficiência da busca, um processo notadamente inspirado no algoritmo
de Grover.
No anexo B, tem-se o programa que utiliza o algoritmo explicado acima. Para
valores de 𝑁 entre 4 e 128 (2 a 7 qubits) é possível encontrar um caminho válido com
mais facilidade utilizando o simulador, mas para valores maiores de que 7 qubits nem
sempre o simulador consegue obter êxito para encontrar uma solução.

51
O pseudo-código da estratégia adotada pode ser encarado da seguinte forma.

Repita
1.1 Se for a ultima linha busca sequencial
1.2 Senão reset de todos os qubits para |0⟩
2.Superposição do emaranhado f(0)
3.Negação do vetor
4.Complemento normal
5.Medida
5.Valida e guarda o valor
Até encontrar a solução
𝜋√𝑁
ou até no máximo vezes.
8

Entrando no console do QCL com o comando qcl e incluindo o algoritmo


mostrado no apêndice B com o comando <include nqueens.qc.> é possível realizar vários
testes com o novo algoritmo quântico apresentado para resolver o problema das N-
Queens. Para 𝑁 = 4 chega-se facilmente nas soluções <2,4,1,3> e <3,1,4,2>.
Na tabela 5.1 vê-se a saída para valores de 𝑁 iguais ao montante do emaranhado,
e alguns exemplos de soluções encontradas pelo algoritmo. Levando-se em consideração
que a estrutura não é adequada, o equivalente em backtranking não conseguiria ter esse
alcance em tão pouco tempo a menos que estivesse sendo executado numa grade
científica. Uma observação importante é que para 𝑁 = 10 fica muito custoso para um
computador clássico conseguir chegar numa solução, o que para o híbrido não representa
tanta dificuldade assim. Portanto mesmo sendo simulado por um computador clássico, o
algoritmo quântico consegue obter respostas melhores do que um clássico em
determinados casos. Isso demonstra que em alguns casos, investir em soluções quânticas
pode-se ganhar em eficiência mesmo que não se tenha um computador realmente
quântico. Logo, é absolutamente óbvio que, algoritmos quânticos sendo executados em
computadores quânticos essa eficiência aumentará ainda mais.

52
Tabela 5.1 – Output de execução do algoritmo híbrido
qcl>NQueens(4) qcl>NQueens(8) qcl>NQueens(16) qcl>NQueens(32)
: N-Queens N = 4 : N-Queens N = 8 : N-Queens N = 16 com : N-Queens N = 32 com
com 2 qubits com 3 qubits 4 qubits 5 qubits
1 Ciclo 5 Ciclos 4 Ciclos 7 Ciclos
: Alcancei a linha : Alcancei o : Alcancei a linha 13 : Alcancei a linha 27
3 com o primero 1 ultimo com o primero 1 com o primero 14
: Alcancei a linha : Tentando linha 7 : Alcancei o ultimo : Alcancei o ultimo
3 com o primero 2 Coluna 3
: Alcancei o : Mostra a solucao : Tentando linha 15 : Tentando linha 31
ultimo Coluna 8 Coluna 0
: Tentando linha 3 : 1 : Mostra a solucao : Mostra a solucao
Coluna 1
: Mostra a solucao : 5 : 2 : 15
: 3 : 8 : 14 : 4
: 1 : 6 : 5 : 19
: 4 : 3 : 7 : 22
: 2 : 7 : 10 : 25
: 2 : 4 : 17
: 4 : 11 : 10
: 15 : 23
: 3 : 32
: 16 : 8
: 8 : 16
: 12 : 9
: 1 : 2
: 13 : 31
: 6 : 27
: 9 : 24
: 7
: 18
: 13
: 5
: 14
: 6
: 30
: 20
: 12
: 3
: 28
: 11
: 21
: 29
: 26
: 1

53
Na tabela 5.2 tem-se o intervalo médio de execuções para uma bateria de testes
com 100 repetições, onde cada repetição é composta pela busca de um caminho possível
de distribuição das rainhas sendo 𝑁 a quantidade de linhas e colunas do tabuleiro.

Tabela 5.2 Tempo Médio de Execuções

N Média dos Intervalos


8 00:00:00,000
16 00:00:01,440
32 00:00:21,260
64 00:05:01,333
128 00:50:33,000

A título de curiosidade é mostrado uma dessas soluções para cada valor de 𝑁.


Para 𝑁 = 8
< 3, 7, 2, 8, 5, 1, 4, 6>
Para 𝑁 = 16
< 12, 10, 5, 1, 9, 13, 2, 14, 7, 11, 4, 6, 16, 3, 15, 8>
Para 𝑁 = 32
< 15, 11, 8, 16, 24, 13, 28, 2, 22, 20, 12, 10, 4, 32, 23, 9, 19, 6, 18, 31, 21, 5, 27,
30, 14, 29, 7, 25, 3, 17, 26, 1>
Para 𝑁 = 64
<38, 44, 23, 39, 32, 25, 50, 3, 62, 22, 45, 16, 41, 5, 54, 6, 4, 20, 36, 43, 37, 29, 64,
12, 17, 59, 49, 31, 11, 35, 60, 18, 1, 28, 13, 57, 52, 63, 40, 7, 47, 26, 51, 55, 15, 46, 27,
19, 21, 2, 61, 14, 8, 30, 9, 53, 56, 24, 48, 58, 34, 10, 42, 33>
Para 𝑁 = 128
< 81, 6, 17, 122, 50, 56, 73, 29, 121, 65, 117, 83, 114, 4, 93, 107, 28, 92, 11, 63,
70, 25, 123, 78, 26, 74, 15, 13, 37, 42, 103, 71, 40, 67, 14, 54, 68, 119, 100, 98, 27, 35,
20, 53, 62, 69, 104, 45, 49, 9, 97, 79, 128, 34, 80, 124, 110, 118, 55, 21, 95, 57, 126, 106,
84, 96, 87, 36, 105, 22, 46, 61, 3, 112, 91, 111, 59, 18, 5, 33, 64, 30, 127, 108, 58, 85, 24,
52, 115, 47, 41, 86, 31, 12, 23, 94, 125, 1, 10, 16, 43, 89, 39, 109, 60, 76, 51, 77, 38, 88,
113, 8, 48, 120, 7, 90, 101, 32, 2, 82, 75, 19, 72, 44, 116, 102, 66, 99>

54
55
Capítulo VI

Conclusão

6.1 – Conclusão

Quando Gödel mostrou que a matemática não seria capaz de demonstrar todos os
teoremas, ele introduziu o conceito de indecidibilidade especialmente na computação. A
partir desse fato histórico, ficou claro que sempre existirão problemas indeterminados e
determinados. Além disso, dentre os problemas que possuem solução, os mesmos podem
ser subdivididos em tratáveis e intratatáveis.
O poder de abstração da realidade promovido pela Máquina de Turing e unido
com a mecânica quântica deu origem à computação quântica. Essa, por sua vez, produziu
um avanço nas soluções dos problemas determinados e intratáveis do mundo clássico
fazendo com que os NP-Completos (o caixeiro viajante, a busca em grafos, a fatoração
de inteiros etc) ganhassem soluções com maior eficiência. Este fato deve-se ao conceito
dos qubits através do mundo aleatório introduzido pelo vetor de estado numa base
ortonormal de números complexos. Por isso que alguns autores costumam dizer que a
equação FÍSICA ∪ MATEMÁTICA ≡ COMPUTAÇÃO passa a ter sentido quando se
tenta contextualizar os fundamentos da computabilidade.
Os números levantados na prova de conceito do trabalho ilustraram que o
algoritmo quântico de busca terá um ganho quadrático de eficiência se for possível
simulá-lo numa Máquina de Turing Quântica. Entretanto, tais máquinas quânticas ainda
não estão disponíveis, tornando-se necessário simulá-las em computadores clássicos. Sob
esta ótica, a capacidade de lidar com emaranhados em computadores clássicos mostrou-
se ineficiente à medida que se aumenta a quantidade de qubits.
Este trabalho permitiu uma melhor compreensão da relação entre os conceitos de
complexidade, indecidibilidade e mecânica quântica. Desta compreensão, foi possível
entender as Máquinas de Turing clássica e quântica. Em seguida foi realizado um estudo
mais detalhado dos algoritmos de Deutsch, Grover e Shor. Este estudo dos algoritmos
forneceu explicações significativamente mais detalhadas, e assim se espera, didáticas, do

56
que aquelas encontradas na literatura. Logo após, foram feitos experimentos com a
Quantum Computing Language com o objetivo de exercitar uma prática de programação
permeada de conceitos da mecânica quântica. Neste sentido, foi implementada a busca de
um número em uma lista não ordenada, cujos resultados foram comparados com soluções
clássicas.
Com o aprendizado adquirido no algoritmo de Grover e encorajado pelos
resultados satisfatórios obtidos na prova de conceito foi possível a implementação de um
algoritmo híbrido que conseguiu, mesmo que, utilizando o simulador clássico, obter
soluções para o problema das N-Rainhas com número de casas acima de 9. Fato esse que,
somente algoritmos clássicos mais sofisticados tais como: busca heurísticas, técnicas de
minimização de busca e redes neurais conseguem fazê-lo. Ainda assim estes mesmos
algoritmos fazem uso de vários recursos computacionais providos pelo paralelismo com
inúmeros processadores e máquinas, o que não foi preciso na solução mostrada no
trabalho. Desta forma, os resultados sugerem que a computação quântica é uma evolução
em relação aos métodos tradicionais da computabilidade.

57
6.2 – Trabalhos Futuros

Com a construção do algoritmo híbrido para solucionar o problema das N-Rainhas


chegou-se em soluções com tabuleiros de até 128 casas. Na tentativa de encontrar
soluções para tabuleiros maiores, sugere-se a execução do programa em QCL em
máquinas com maior capacidade de processamento, tendo um limite aceitável para
tabuleiros em torno de 1024 casas. Para encontrar todas as soluções propõe-se três
melhorias. A primeira seria encontrar o melhor do ponto de corte entre a parte quântica e
a clássica dentro do algoritmo, pois à medida que se evolui na descoberta das posições,
quanto maior for a precisão do corte, maior será a eficiência ao algoritmo. A segunda
seria descobrir uma maneira de orquestrar diferentes execuções quânticas utilizando
paralelismo e recursividade clássicos, onde cada execução terá como objetivo encontrar
todas as soluções para uma combinação fixa de linha e coluna. Algo semelhante ao
algoritmo de backtraking, mas acrescido da solução quântica proposta no trabalho. E por
último, sugere-se a reconstrução da ideia inicial calcada no algoritmo de Grover passar a
ser analisada sob a ótica de caminhadas quânticas.

58
Bibliografia
Bernstein , Ethan; Vazirani, Umesh. “Quantum complexity theory”, In: Proc. 25th
Annual ACM Symposium on Theory of Computing, ACM, pp.11-20, 1993.

Bernstein , Ethan; Vazirani, Umesh. “Quantum complexity theory”, In: SIAM Journal
on Computing archive, Volume 26 Issue 5, Oct, pp.1411-1473, 1997.

Cafezeiro, Isabel; Heausler, Edward Hermann.“Computabilidade”, VI ERIMG, DCC,


UFLA, 29 a 31 de Agosto de 2007.< http://www.bcc.unifal-
mg.edu.br/~humberto/disciplinas/2010_paa/leitura/complementar_aula01.pdf>,
Acessado em 19 de maio de 2012.

Costa, Newton C. A., “Filosofia da Ciência”, Scientific American Brasil,<


http://www.cs.auckland.ac.nz/~chaitin/costa2.html>, Acessadoem03 de fevereiro de
2012.

Damásio, Antonio. “O Erro de Descartes”, Cia das Letras SP, 2ª ed., 1994.

Del Nero, Henrique Schützer, “O Sítio da Mente”, c.11, São Paulo, Collegium Cognitio,
1997.

Deutsch, D. “Quantum Theory, the Church-Turing Principle and the Universal Quantum
Computer”, Procceedings of the Royal Society A, Mathematical, Physical &
Engineering Sciences, 400, pp.97-117, 1985.

Goldstein, Rebecca. “The Proof and Paradox”, Atlas Books, 2ª ed., 2005.

Green, Brain, “Realidade Oculta”,Companhia das Letras, 1ª ed., SP, 2012.

Griffiths, David J. "Introduction to Quantum Mechanics", Perason Prentice Hall, New


Jersey, 2ª ed., 2004.

Hodges, Andrew. “Turing Um filósofo da Natureza”. UNESP, SP, 1ª ed., 1997.

Kubrusly, Ricardo S., “Uma viagem informal ao Teorema de Gödel”, Instituto de


Matemática, Universidade Federal do Rio de
Janeiro,<http://im.ufrj.br/~risk/diversos/godel.html/>, Acessado em 21 de janeiro de
2012.

59
Mello, Flávio Luis de. “Teoria da Computação”, Notas de aula do curso de Engenharia
Eletrônica e de Computação, Escola Politécnica, Universidade Federal do Rio de
Janeiro, 2014.

McDermott, Drew, “Artificial Intelligence meets Natural Stupidity”, MIT AI Lab, AI


Forum, SIGART Newsletter, n. 57, pp.4-9, Abril, 1976.

Nielsen, Michael A., “Computação Quântica e Informação Quântica”, Bookman, PA, 1ª


ed., 2005.

Nunes, Maria das Graças Volpe; Dosualdo, Daniel Gomes , “Tese de Church”, Notas de
Aula do curso de Teoria da Computação, Instituto de Ciências Matemáticas e de
Computação, Universidade Estadual de São Paulo,
<http://www.icmc.usp.br/~gracan/teoria/SubItem32Teoria.html>, Acessado em 13 de
maio de 2012.

Ömer, Bernard, “A Procedural Formalism for Quantum Computing”. Tese de


Doutorado, Viena, 1998.

Palazzo, Luiz A. M. “Máquina de Turing - Linguagens Sensíveis ao Contexto e


Enumeráveis Recursivamente”, Texto 5 do Paper Linguagens Formais e Autômatos,
Universidade de Pelotas, 2007.

Paulo, Luiz. “Datamining”. Ciência Moderna, RJ, 1ª ed., 2005.

Penrose, Roger. “O Pequeno o Grande e a Mente Humana”, MIT Press, EUA, 1ª ed.,
1996.

Pinker, Steven. “Como a Mente Funciona”. Cia das Letras, SP, 2ª ed., 1998.

Portugal, Renato, “Introdução à Computação Quântica”2ª ed., SP, 2012.

Portugal, Renato, “Algoritmos Quânticos de Busca”, 1ª ed., SP, 2012.

Ribeiro, Henrique de Morais. “Uma Revisão da Teoria Quântica da Consciência de


Penrose e Hameroff”. Revista Eletrônica Informação e Cognição, SP, 2001.

Scientific American Brasil (sciam), Prêmio Nobel de Física 2012,


<http://www2.uol.com.br/sciam/noticias/o_premio_nobel_de_fisica_de_2012.html>,
Acessado em 15 de dezembro de 2012.

60
Toscani, L.V., Veloso, P.A., “Complexidade de Algoritmos: análise, projetos em
métodos”, Sagra-Luzzato, Porto Alegre, Instituto de Informática da UFRGS, 2001.

Pothumani, S. “Solving N Queen Problem Using Various Algorithms – A Survey”,


International Journal of Advanced Research in Computer Science and Software
Engineering, Volume 3 Questão 2, Fevereiro de 2009.

Kosters, Walter. “N-Queens bibliography”, 2013,


<http://www.liacs.nl/~kosters/nqueens/>, acesso em 20 de maio de 2014.

Bell , J. and Stevens, B. “A Survey of Known Results and Research Areas for n-
Queens”, Discrete Mathematics, vol. 309, pp. 1-31, 2009.

Fernandes, Marco e Alho, Miguel. "Solução eficiente para a resolução do Problema das
N-Rainhas", REVISTA DO DETUA, vol. 4, Nº 2, Janeiro de 2004

Sosic, Rok. “A Parallel Search Algorithm for the N-Queens Problem”, School of
Computing and Information Technology, Technical Report CIT-94-3, Agosto de 1993.

61
Apêndice A - QCL
QCL (Quantum Computation Language) é uma linguagem de programação
quântica (QPL) de alto nível que possui as principais características.

 Linguagem de controles clássicos com funções de controle de fluxo e interações


com vários tipos de dados clássicos (int, string, boolean, real, complex)
 2 tipos de operadores quânticos unitários gerais (operator) e portas pseudo-
clássicas reversíveis (qufunc)
 Execução inversa, permitindo execuções on-the-fly de um operador inverso
através do cache de chamadas desses operadores.
 Acesso a vários tipos de dados quânticos (qubit registers) com informações em
tempo de compilação (qureg, quconst, quvoid, quscratch).
 Funções convenientes para manipular registradores quânticos
 q[n] - qubit,
 q[n: m] - substring
 q & p - registro combinado
 Gerenciamento de memória quântica (quheap) possibilitada por variáveis locais
quânticas.
 Integração transparente com gestão de espaço Bennet-style
 Fácil adaptação de conjuntos individuais compostos por operadores elementares

62
Exemplo: Transformada de Fourier Discreta

dft.qcl

operator dft(qureg q)
{ // main operator
const n=#q; // set n to length of input
int i; int j; // declare loop counters
for i=0 to n-1 {
for j=0 to i-1 { // apply conditional phase gates
CPhase(2*pi/2^(i-j+1),q[n-i-1] & q[n-j-1]);
}
Mix(q[n-i-1]); // qubit rotation
}
flip(q); // swap bit order of the output
}

O bloco de código acima mostra a implementação quântica da Transformada de


Fourier Discreta que serve de base para o algoritmo de Shor.
Basicamente, dft.qcl contém dois circuitos: o loop externo realiza a
Transformação de Hadamard do maior para o menor qubit (Mix), enquanto que o laço
interno executa mudanças de fase condicionais (CPhase) entre os qubits.
O operador dft possui um registrador quântico (qureg) q como argumento. O
registrador quântico não é um estado quântico por si, mas sim um ponteiro que indica os
qubits alvos no estado geral da máquina, assim como as linhas de entrada e saída no
modelo de portas lógicas clássicas. Para permitir registrador independente da definição
dos operadores, o número de qubits do registrador pode ser determinado em tempo de
execução pelo tamanho do operador #.
Assim como em linguagens procedurais clássicas, dentro da definição do
operador, sub-operadores podem ser chamados apenas como subprocedimentos. Isto
significa que a sequência real dos operadores (embutido ou definidos pelo usuário) pode
ser totalmente determinada em tempo de execução, incluindo o uso de loops (neste caso,
for-loops), declarações condicionais, etc.
Semelhante a outras as linguagens clássicas, QCL tem uma rigorosa semântica
matemática de funções e operadores, ou seja, duas chamadas subsequentes com os
mesmos parâmetros têm que resultar exatamente na mesma operação. Isto exige que os
operadores devam ser livres de efeitos colaterais, não sendo permitido o uso de variáveis
globais.
QCL permite execuções fora do contexto, por isso é possível fazer a chamada
DFT(q) e !DFT(q), sendo que a segunda chamada terá todos os operadores invertidos e
executados na ordem inversa.

63
O interpretador em QCL simula um computador quântico dentro de um número
arbitrário de qubits e é chamado com a seguinte sintaxe.

𝒒𝒄𝒍 [𝒐𝒑𝒕𝒊𝒐𝒏𝒔][𝑸𝑪𝑳 𝒇𝒊𝒍𝒆𝒔]

Seguem as opções para a linha de comando.

Startup Options:
-h, --help display this message
-V, --version display version information
-i, --interactive force interactive mode
-n, --no-default-include don't read default.qcl on startup
-o, --logfile specify a logfile
-b, --bits=n: set number of qubits (32)

Dynamic Options (can be changed with the set statement):


-s, --seed=<seed-value> set random seed value (system time)
-I, --include-path=<path> QCL include path (.)
-p, --dump-prefix=<file-prefix> set dump-file prefix
-f, --dump-format=b,a,x list base vectors as hex, auto or binary (a)
-d, --debug=<y|n> open debug-shell on error (n)
-a, --auto-dump=<y|n> allways dump quantum state in shell mode (n)
-l, --log==<y|n> log external operator calls (n)
-L, --log-state==<y|n> log state after each transformation (n)
-T, --trace==<y|n> trace mode (very verbose) (n)
-S, --syntax=<y|n> check only the syntax, no interpretation (n)
-E, --echo=<y|n> echo parsed input (n)
-t, --test=<y|n> test programm, ignore quantum operations (n)
-e, --shell-escape=<y|n> honor shell-escapes (y)
-r, --allow-redefines=<y|n> ignore redefines instead of error (n)

A menos que esteja desabilitada com a opção --no-default-include, QCL inicia


com o include do arquivo default.qcl. Se nenhum arquivo for especificado na linha de
comando, QCL inicia em modo interativo via prompt de comandos.

64
Em síntese, qualquer programa quântico deve ser a composição de inicializações,
operadores unitários e medições. Um típico algoritmo quântico probabilístico geralmente
é executado num ciclo de avaliação.
A demonstração abaixo pode ser encarada como um exemplo mínimo de um
algoritmo quântico.

single.qcl

{
reset; // R: |Psi> -> |0>
myoperator(q); // U: |0> -> |Psi'>
measure q,m; // M: |Psi'> -> |m>
} until ok(m); // picked the right m ?

O comando reset “reseta” o estado da máquina de |𝜓⟩ para |0⟩.


O comando measure mede o registrador quântico e atribui o valor medido em bits
para o identificador de variável inteira m. Se nenhum valor é retornado, a variável fica
sem atribuição. O resultado da medição é determinado por um número aleatório dentro
das possibilidades contidas no registrador no momento da medição respeitando as
probabilidades modificadas pelo operador myoperator. Uma vez que as operações de reset
e medida são irreversíveis, as mesmas não devem ocorrer dentro das definições do
operador.

65
Apêndice B – Algoritmo de Deutsch
const QCL (Quantum Computation
coin1=(random()>=0.5) Language) é uma linguagem de programação
const coin2=(random()>=0.5)
quântica (QPL) de alto nível que possui as principais características.
boolean f(boolean x) {
if coin1 { // coin1=true -> g is constant
 return
Linguagem
coin2;
de controles clássicos com funções de controle de fluxo e interações
} else
com{ // coin1=false
vários tipos de dados clássicos -> gboolean,
(int, string, is balanced
real, complex)
return x xor coin2;
2 tipos
} de operadores quânticos unitários gerais (operator) e portas pseudo-
}
Operador F(quconst x,quvoid y) {
if f(false) xor f(true) { CNot(y,x); }
if f(false) { Not(y); }
}

operator U(qureg x,qureg y) {


H(x);
F(x,y);
H(x & y);
}

procedure deutsch() {
qureg x[1];
qureg y[1];
int m;
{
reset; Incializa a máquina
U(x,y); Executa o operador
measure y,m; É realizada a medida do Segundo registrador
} until m==1; Repete-se o loop até encontrar 𝑓(𝑥) = 1
measure x,m; Lê-se o valor de 𝑥
print "f(0) xor f(1) =",m; Imprime o resultado
}

66
Apêndice C – Busca Quântica N-Queens
nqueens.qcl

include "grover.qcl";

boolean ataca(int x1, int y1, int x2, int y2)


{
//mesma linha ou mesma coluna
if ((x1==x2) or (y1==y2)) {return true;}

//teste das diagonais


if (x1>x2) {
if ((x1-x2 == y1-y2) or (x1-x2 == y2-y1)) {return true;}
}

if (x2>x1) {
if ((x2-x1 == y2-y1) or (x2-x1 == y1-y2)) {return true;}
}

return false;
}

boolean funciona(int k, int vector queen)


{
int i;
for i=0 to k-1 {
if (ataca(i, queen[i], k, queen[k])) {
return false;
}
}
return true;
}

operator exclude(qureg q, int n) {


qureg f[0];

H(q);
query(q,f,n);
}

int ValidandoPosicao(int vector queens, int colexcluida, int linha)


{
if (funciona(linha, queens)) {
return queens[linha];
}
else
{
return -1;
}
}

67
procedure NQueens(int N) {
int n=floor(log(N,2));
int L;
int i;
int j;
int k;
int x;
int ciclo;
int vector queens[N];
int vector index[N];

qureg q[n];

print "N-Queens N =",N,"com",n,"qubits";

//Zera todo o vetor de solucao


for i=0 to N-1 {
queens[i] = -1;
}
//Zera todo o vetor reverso solucao
for i=0 to N-1 {
index[i] = -1;
}

reset;
k = -1;
L = 0;
ciclo = 0;
while (queens[N-1] == -1) {
//Primeiro elemento do vetor, qualquer posicao funciona
if (L == 0) {
ciclo = ciclo+1;
k = k+1;
if (k == N) {
k = 0;
}
queens[L] = k;
//print "Forcei o primeiro",k;
}
else {
if (L == N-1) {
print "Alcancei o ultimo";
i = -1;
{
i = i+1;
} until (i < N) and (index[i] == -1);

print "Tentando linha",L,"Coluna",i;


queens[L] = i;
if not (funciona(L, queens)) {
queens[L] = -1;
print "Nao achei";
}
}
//Se for o ultimo elemento, nao há mais necessidade de procurar por
outras opções
else {
j = 0;
//Varro o vetor
while j < L and (queens[L] == -1) {
//print "Tentando encontrar uma posicao";
//Excluo a coluna
exclude(q, queens[j]);
measure q,x;
queens[L] = x;
//print "Tentando validar a coluna ",queens[L],"na
linha",L,"excluindo a coluna",queens[j];

68
queens[L] = ValidandoPosicao(queens, queens[j], L);
if (queens[L] == -1) {
//Excluo a diagonal à esquerda
if (queens[j] - (L-j) > -1) {
exclude(q, queens[j] - (L-j));
measure q,x;
queens[L] = x;
//print "Tentando validar a coluna
",queens[L],"na linha",L,"excluindo a coluna",(queens[j]-(L-j));
queens[L] = ValidandoPosicao(queens,
(queens[j]-(L-j)), L);
if (queens[L] == -1) {
//Excluo a diagonal à direita
if (queens[j] + (L-j) < N) {
exclude(q, queens[j] +
(L-j));
measure q,x;
queens[L] = x;
//print "Tentando validar a
coluna ",queens[L],"na linha",L,"excluindo a coluna",(queens[j]+(L-j));
queens[L] =
ValidandoPosicao(queens, (queens[j]+(L-j)), L);
}
}
}
}

j = j+1;
}
}
}

// print "Linha",L,"Coluna",queens[L];
if (queens[L] == -1) {
print "Alcancei a linha",(L+1),"com o primero",(k+1);
for i=0 to N-1 {
queens[i] = -1;
}

for i=0 to N-1 {


index[i] = -1;
}

L = 0;
reset;
}
else {
index[queens[L]] = L;
//print "Achei Linha",i,"Coluna",queens[i];
L = L+1;
}
}

//Mostra a solucao
if (queens[N-1] != -1) {
print "Mostra a solucao";
for i=0 to N-1 {
if (queens[i] != -1) {
print queens[i]+1;
}
}
//exit;
}
}

69
Apêndice D – Backtraking N-Queens
nqueens.cpp

#define EXIT_SUCCESS 1;

#include <iostream>
#include <cstdlib>
#include <stdio.h>

using namespace std;

class Rainhas{
public:
int contador;
bool imprimeTabuleiros;
int rainha[25];
int N;
//Construtor Padrao
Rainhas(){
contador = 0;
imprimeTabuleiros = false;
}

void SetN(int n){


if ( n < 4 || n > 25 ) {
cout<<"********************************************************"<<endl;
cout<<"**** Entre com N entre 4 e 25 ****"<<endl;
cout<<"********************************************************"<<endl;
exit(1);
}

N = n;
rainha[0] = 0;
for (int i = 1; i<N;i++){
rainha[i] = N;
}
}

void start(){
busca (rainha,0);
cout << "Numero total de solucoes: " << contador << endl;
}
}

70
private:

void busca (int rainha[], int k)


{
if (rainha[0]>(N-1)) return;//percorreu todas as posicoes possiveis ja
achou solucao

if (rainha[k]>(N-1))
{

k--; //retorna para a ultima rainha


rainha[k]++;//passa para a proxima posicao da rainha
//nova busca
busca(rainha, k);
}

//depois de passar por todos os campos do tabuleiro...


if ((k==(N-1))&&(funciona(rainha,k)))//se esta na ultima posicao e
funciona, achou solucao
{

contador++; //contador de solucoes


impressao(rainha); //impressao da solucao
rainha[k]++;
//nova busca
busca(rainha,k);
}

if (funciona(rainha,k)){ //funciona nesta posicao, passa para a proxima


k++; //proxima rainha
rainha[k]=0; //comeca a partir da posicao 0
}
else{//senao, passa para a proxima posicao
rainha[k]++;
}
//nova busca
busca(rainha,k);
}

bool funciona (int rainha[], int k)


{
for (int i=0; i<k; i++){
if (ataca(i, rainha[i], k, rainha[k])) return false;
}
return true;
}

void impressao(int rainha[])


{
for (int i=0; i<N; i++) cout << (1 + rainha[i]) << " ";
cout<<endl;

//opcao de impressao dos tabuleiros


if (imprimeTabuleiros){
//cout<<"Deseja imprimir matriz desta solucao (s/n)?"<<endl;
//cin>>x;
// }
//if (x =='s'){
for (int i=0; i<N; i++){
for (int j=0; j<N; j++){
if (j == rainha[i]){
cout<<" O";
}
else{
cout<<" x";
}
}
cout<<endl;

}
cout << endl;
}
}

71
bool ataca(int x1, int y1, int x2, int y2)
{
if ((x1==x2)||(y1==y2)) return true;//mesma linha ou mesma coluna

//teste das diagonais


if (x1>x2){
if ((x1-x2 == y1-y2)||(x1-x2 == y2-y1)) return true;
}
if (x2>x1){
if ((x2-x1 == y2-y1)||(x2-x1 == y1-y2)) return true;
}

return false;
}
};

int main(int argc, char *argv[])


{
int a;
//cria objeto rainha
if (argc == 2) {
a = atoi(argv[1]);
Rainhas r;
r.SetN(a);
r.start();
return EXIT_SUCCESS;
}
}

72

Você também pode gostar