ver/abrir - Repositório do Departamento de Ciência da Computação

Transcrição

ver/abrir - Repositório do Departamento de Ciência da Computação
Universidade de Brası́lia
Instituto de Ciências Exatas
Departamento de Ciência da Computação
Verificação Formal da Correção do Algoritmo
AKS
Ricardo Peixoto
Monografia apresentada como requisito parcial
para conclusão do Curso de Computação – Licenciatura
Orientador
Prof. Dr. Flávio Leonardo Cavalcanti de Moura
Brası́lia
2008
Universidade de Brası́lia – UnB
Instituto de Ciências Exatas
Departamento de Ciência da Computação
Curso de Computação – Licenciatura
Coordenadora: Prof a.̄ Dr a.̄ Priscila América Solı́s Mendez Barreto
Banca examinadora composta por:
Prof. Dr. Flávio Leonardo Cavalcanti de Moura (Orientador) – CIC/UnB
Prof. Dr. Guilherme Albuquerque Pinto – CIC/UnB
Prof. Dr. Mauricio Ayala Rincón – MAT/UnB
CIP – Catalogação Internacional na Publicação
Peixoto, Ricardo.
Verificação Formal da Correção do Algoritmo AKS / Ricardo Peixoto.
Brası́lia : UnB, 2008.
94 p. : il. ; 29,5 cm.
Monografia (Graduação) – Universidade de Brası́lia, Brası́lia, 2008.
CDU 004
Endereço: Universidade de Brası́lia
Campus Universitário Darcy Ribeiro – Asa Norte
CEP 70910–900
Brası́lia – DF – Brasil
Universidade de Brası́lia
Instituto de Ciências Exatas
Departamento de Ciência da Computação
Verificação Formal da Correção do Algoritmo
AKS
Ricardo Peixoto
Monografia apresentada como requisito parcial
para conclusão do Curso de Computação – Licenciatura
Prof. Dr. Flávio Leonardo Cavalcanti de Moura (Orientador)
CIC/UnB
Prof. Dr. Guilherme Albuquerque Pinto Prof. Dr. Mauricio Ayala Rincón
CIC/UnB
MAT/UnB
Prof a.̄ Dr a.̄ Priscila América Solı́s Mendez Barreto
Coordenadora do Curso de Computação – Licenciatura
Brası́lia, 15 de fevereiro de 2008
Dedicatória
Dedico este trabalho a meu filho, Gustavo Henrique, a minha esposa, Litiane,
a meus pais, Cleomar e Peixoto, e a meu amigo, Dioney, que nunca me deixaram
parar.
Agradecimentos
Agradeço a meu orientador, Prof. Flávio, que acreditou e tornou possı́vel este
trabalho.
Resumo
Este trabalho apresenta uma formalização, utilizando o assistente de prova
Coq, do algoritmo AKS, o qual foi apresentado por Agrawal, Kayal e Saxena em
seu artigo PRIMES is in P, divulgado em 2002 e publicado em 2004. O AKS
é o primeiro algoritmo capaz de determinar, sem erro e em tempo polinomial,
a primalidade de um número. O assistente de prova Coq é baseado em uma
lógica de ordem superior muito expressiva, conhecida como cálculo de construções
indutivas.
Palavras-chave: Algoritmo AKS, Coq, teste de primalidade, tempo polinomial,
assistente de prova, cálculo de construções indutivas.
Abstract
This work presents a formalization of the AKS algorithm in the Coq proof
assistant. The AKS algorithm was presented in the Agrawal, Kayal and Saxena’s
PRIMES is in P paper, in 2002. This is the first algorithm that can decide in
polynomial time, without error, whether a given number is prime or not. The
Coq proof assistant is based on a higher-order logic called calculus of inductive
constructions.
Keywords: AKS, Coq, prime numbers, primality test, polynomial time, proof
assistants, calculus of inductive constructions.
Sumário
Lista de Figuras
9
Capı́tulo 1 Introdução
10
Capı́tulo 2 Testes de Primalidade e o Algoritmo AKS
2.1 Noções de Complexidade . . . . . . . . . . . . . . . .
2.1.1 O Custo de um Algoritmo . . . . . . . . . . .
2.1.2 Notação Assintótica . . . . . . . . . . . . . .
2.1.3 Classes de Complexidade . . . . . . . . . . . .
2.2 Testes de Primalidade . . . . . . . . . . . . . . . . .
2.2.1 O Crivo de Eratóstenes . . . . . . . . . . . . .
2.2.2 Distribuição dos Números Primos . . . . . . .
2.2.3 Eficiência dos Testes de Primalidade . . . . .
2.3 O Algoritmo AKS . . . . . . . . . . . . . . . . . . . .
2.3.1 O Teorema Fundamental . . . . . . . . . . . .
2.3.2 O Pseudocódigo e a Correção . . . . . . . . .
2.3.3 A Complexidade Polinomial . . . . . . . . . .
Capı́tulo 3 Assistentes de Prova
3.1 Verificação Formal e Assistentes de Prova .
3.2 O Assistente de Prova Coq . . . . . . . . .
3.2.1 Tipos . . . . . . . . . . . . . . . .
3.2.2 Táticas . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
12
12
13
15
18
21
23
27
29
29
43
45
.
.
.
.
51
51
54
55
56
Capı́tulo 4 Metodologia
68
Capı́tulo 5 Resultados
71
Capı́tulo 6 Alguns trabalhos
83
Apêndice A Princı́pio da Indução Matemática
85
Apêndice B Teorema de Lagrange
88
Referências
91
Lista de Figuras
2.1
2.2
Comparação do crescimento de duas funções. . . . . . . . . . . . .
Conjectura atual para a relação entre as classes de complexidade.
14
17
4.1
Dependências que serão seguidas na formalização do AKS. . . . .
69
5.1
5.2
Dependências finais na formalização do algoritmo AKS. . . . . . .
Especificação final do AKS e a prova de sua correção em Coq. . .
73
74
Capı́tulo 1
Introdução
Além de despertarem uma antiga curiosidade matemática, atualmente os números primos são usados na criptografia de dados. Em geral, chaves criptográficas
são geradas a partir da multiplicação de grandes números primos. Quanto maior
o número chave, mais demorada será a sua decomposição em primos, ou seja,
a sua “quebra”. No entanto, computacionalmente, é mais difı́cil decompor um
número em seus fatores primos do que testar se grandes números candidatos a
geradores de chaves são primos ou não ([Cormen et al., 2001]. Daı́ a importância da eficiência dos testes de primalidade (algoritmos que testam se um número
é primo ou composto), desde uma antiga fascinação até uma necessidade prática atual ([Coutinho, 2004]). Até 2002, não era conhecido nenhum algoritmo
capaz de determinar, sem erros, a primalidade de um número em tempo polinomial. Na Teoria da Complexidade, o problema era comumente considerado
classe co-NP, apesar de ser conhecida uma prova de que também é classe NP
([Agrawal et al., 2004]).
Em 2002, Agrawal, Kayal e Saxena disponibilizaram na Internet o esboço de
um artigo, chamado PRIMES is in P, o qual foi aprimorado com contribuições,
entre elas a de Lenstra Jr. ([Bernstein, 2003]), e definitivamente publicado em
2004, em que apresentavam um algoritmo capaz de determinar, inequivocamente,
a primalidade de um número em tempo polinomialmente proporcional ao seu tamanho, que passou a ser conhecido como algoritmo AKS ([Agrawal et al., 2004]).
Apesar da grande importância teórica do algoritmo AKS, já que o problema
da primalidade passou a ser também classe P, na prática, ainda é mais eficiente
o uso de algoritmos probabilı́sticos de tempo polinomial para se testar a primalidade de números grandes ([Coutinho, 2004]). Isso se deve ao fato de que esses
algoritmos probabilı́sticos, mesmo necessitando de um custo adicional para reduzir sua probabilidade de erro, possuem uma curva de tempo (polinomial) bem
menos acentuada que a do algoritmo AKS1 .
Ainda assim, o algoritmo AKS pode ter aberto portas para novos testes
eficientes de primalidade, que serão possı́veis a partir de sua boa compreensão. Nestes últimos anos, foram desenvolvidos vários trabalhos a respeito do
AKS, como, por exemplo, [Bernstein, 2003], [Crandall and Papadopoulos, 2003],
1
Veremos mais à frente que o custo do AKS realmente pode ser representado por um polinômio em função da entrada, mas de grau elevado.
10
[Schoof, 2003], [Bernstein, 2004], [Tou and Alexander, 2005], entre outros, mas
não encontramos nenhum trabalho de verificação formal utilizando, por exemplo,
assistentes de prova. Isso pode se explicar, em parte, pela dificuldade e nı́vel de
detalhe que a formalização do algoritmo exige. Além disso, em seu estágio atual,
o algoritmo AKS ainda não é a melhor opção implementacional. Mesmo assim,
um trabalho de formalização do algoritmo AKS, como o proposto aqui, é relevante
para uma possı́vel utilização, mesmo que de partes do algoritmo, em softwares
certificados, especialmente os relacionados à criptografia de dados, considerando o
fato de que alguns assistentes de prova nos permitem extrair código executável do
conteúdo computacional das provas que satisfaz a especificação dada, e também
permitiria uma melhor compreensão do algoritmo, fornecendo uma prova formal
de propriedades fundamentais envolvidas em sua correção e sua complexidade.
Uma formalização completa do algoritmo AKS é um trabalho complexo, além
do escopo de uma monografia de conclusão de curso de graduação. Desta forma,
nosso objetivo é verificar formalmente apenas a correção do algoritmo, utilizando o
assistente de prova Coq ([The Coq Development Team, 2008b]), que está baseado
em uma lógica de ordem superior muito expressiva, conhecida como cálculo de
construções indutivas ([Bertot and Castéran, 2004]).
Esperamos contribuir, assim, para pesquisas futuras envolvendo testes de primalidade ou mesmo outros problemas relacionados. É importante ressaltarmos
que a formalização que aqui apresentaremos se limitará à correção do algoritmo
AKS. Mais especificamente, provaremos que, para qualquer entrada n > 1, o algoritmo pára com a resposta correta. A prova formal de que o algoritmo tem
complexidade polinomial é deixada como trabalho futuro.
Adicionalmente, esperamos que o presente trabalho seja útil como um texto
introdutório ao assistente de prova Coq para estudantes de Computação, Matemática e áreas afins, já que pouco se tem documentado sobre o assunto em
lı́ngua portuguesa. Desta forma, tentaremos explicar algumas táticas, estruturas
e caracterı́sticas do Coq. Também esperamos apresentar, de forma detalhada e
ao nı́vel de graduação, o algoritmo AKS e os teoremas relacionados. Estudantes que tenham cursado disciplinas de lógica, álgebra e algoritmos/programação
terão facilidade para entender o texto. A complexidade do algoritmo também
será demonstrada, apesar de não estarmos interessados, neste momento, em sua
prova formal. Assim, estudantes com conhecimentos em Teoria da Computação
terão facilidade para compreender o algoritmo AKS, bem como sua importância
e principal caracterı́stica: a complexidade polinomial.
11
Capı́tulo 2
Testes de Primalidade e o
Algoritmo AKS
2.1
Noções de Complexidade
Nesta seção, faremos uma breve revisão sobre complexidade temporal de algoritmos e notação assintótica. Ressaltamos que estaremos apenas tratando da
complexidade temporal de algoritmos. A complexidade espacial será deixada de
lado. Além disso, adotaremos o chamado modelo genérico RAM (random access
machine ou máquina de acesso aleatório) de computação, que pressupõe uma máquina semelhante aos computadores binários, com um único processador e capaz
de executar apenas uma instrução por vez. Assim, os algoritmos considerados
aqui podem ser vistos como programas de computadores ([Cormen et al., 2001]).
Aqueles que já conhecem o assunto podem ir diretamente para a Seção 2.2.
No entanto, àqueles estudantes que ainda não tiveram contato com a Teoria
da Complexidade, sugerimos a leitura de [Knuth, 1968], [Bach and Shallit, 1996],
[Cormen et al., 2001] e [Hopcroft et al., 2001].
2.1.1
O Custo de um Algoritmo
Do ponto de vista exclusivamente temporal, o algoritmo mais eficiente para
a resolução de um problema é aquele que leva menos tempo para dar uma resposta a entradas de um mesmo tamanho n. O tamanho da entrada depende,
especificamente, do tipo de problema e é importante que seja fixado para comparações de eficiência ([Cormen et al., 2001]). Por exemplo, em algoritmos de
ordenação de números inteiros, a entrada é constituı́da de uma seqüência de números. É de se supor que seqüências maiores gastem mais recursos computacionais
para serem ordenadas, independentemente dos algoritmos utilizados. É necessário, portanto, fixar o tamanho da entrada para compararmos o tempo gasto por
algoritmos distintos. E mais: esse tempo precisa ser o menos dependente da máquina o quanto possı́vel. Desta forma, não é conveniente medi-lo em unidades de
tempo habituais, como segundos, pois computadores distintos podem ter drásti-
12
cas diferenças de desempenho1 . Uma medida adequada para a comparação da
eficiência temporal de algoritmos é a quantidade de operações mı́nimas ou passos
([Bach and Shallit, 1996]) necessários para se gerar a saı́da, o chamado tempo de
execução ou custo de um algoritmo.
Em geral, temos custos diferentes para entradas distintas, ainda que tenham
um mesmo tamanho n. Mas muitas vezes podemos identificar aquelas que necessitam executar um maior número de passos para que seja apresentada a resposta.
O pior caso para um tamanho n refere-se às entradas de tamanho n que necessitam da maior seqüência de passos para se obter a resposta, o chamado tempo
de execução do pior caso ou custo máximo de um algoritmo para entradas de
tamanho n. De forma análoga, o tempo de execução do melhor caso ou o custo
mı́nimo de um algoritmo se refere às entradas de tamanho n que necessitam da
menor seqüência de passos para se obter a resposta. Voltando ao exemplo da
ordenação, é de se supor que um algoritmo necessite de mais passos computacionais para gerar a saı́da quando recebe uma entrada totalmente desordenada
do que quando recebe uma que esteja parcialmente ordenada, desde que ambas
tenham o mesmo tamanho n. Este é o caso dos algoritmos mais simples, como a
ordenação por inserção ([Cormen et al., 2001]). O pior caso para esse algoritmo
é quando a entrada está em ordem inversa e o melhor caso é quando a entrada já
está ordenada.
Assim, o tempo de execução do pior caso se revela como parâmetro para
ser usado em comparações entre os custos de algoritmos que resolvem o mesmo
problema2 . Se TA (n) e TB (n) são funções que representam, respectivamente, o
custo máximo dos algoritmos A e B, em que n é o tamanho da entrada, podemos
comparar essas funções, quando n cresce indefinidamente, para sabermos qual
algoritmo é, assintoticamente, mais eficiente (menos custoso).
2.1.2
Notação Assintótica
A notação O (“o” grande ou “o” maiúsculo), introduzida por Bachmann em
1892 ([Knuth, 1968]), tornou-se muito útil para indicar o tempo de execução do
pior caso de algoritmos. Formalmente, temos a seguinte
Definição 2.1.1. ([Cormen et al., 2001]) Seja g(n) uma função com domı́nio N.
Então,
f (n) : existem constantes positivas c e n0 tais que
O(g(n)) =
0 ≤ f (n) ≤ cg(n) para todo n ≥ n0 .
Ou seja, uma função f (n) pertence ao conjunto O(g(n)) se existir uma constante positiva c tal que o produto cg(n) limite superiormente f (n) a partir de
algum n. Assim, a função f (n) = 3n3 ∈ O(n4 ), pois temos que n4 ≥ 3n3 para
todo n ≥ 3. Neste caso especı́fico, temos c = 1 e n0 = 3. De fato, qualquer polinômio não negativo tem o crescimento limitado superiormente por um polinômio
1
Afinal, estamos preocupados com a eficiência de algoritmos e não com a eficiência de máquinas.
2
Mas muitas vezes, quando o pior caso raramente ocorre, a melhor medida para o custo de
um algoritmo é o caso médio ([Cormen et al., 2001]).
13
de grau maior ou igual ao seu. Por exemplo, 2n2 ∈ O(n3 ) e 2n2 ∈ O(n2 ), pois
podemos escolher constantes c e d tais que cn3 ≥ 2n2 , para todo n a partir de
algum n0 , e dn2 ≥ 2n2 , para todo n a partir de algum n1 . A Figura 2.1 mostra
uma comparação entre duas funções, f (n) e g(n). Note que a partir do ponto n0 ,
g(n) é sempre maior que f (n). Neste caso, f (n) ∈ O(g(n)).
Figura 2.1: Comparação do crescimento de duas funções.
Para um algoritmo que tenha como tempo de execução do seu pior caso a
função T (n) = αn2 + βn + γ, com α, β e γ constantes e n significando o tamanho
da entrada, podemos indicar seu tempo de execução simplesmente como O(n2 ),
pois T (n) ∈ O(n2 ) ([Bach and Shallit, 1996]). Ou seja, retiramos as constantes e
os termos de mais baixa ordem, restando apenas o termo mais elevado, o qual é
suficiente para indicar o crescimento da função. De fato,
αn2 + βn + γ
= α.
n→∞
n2
lim
Como α é constante, αn2 + βn + γ e n2 têm a mesma ordem de crescimento. Por
isso, essa medida de eficiência é conhecida como eficiência assintótica, isto é, a
eficiência no limite do tamanho da entrada, quando este cresce indefinidamente
([Cormen et al., 2001]).
Apesar de ser uma relação de pertinência, iremos escrever, a partir de agora,
f (n) = O(g(n)) para indicar que f (n) ∈ O(g(n)). Esta é a notação mais utilizada na literatura especializada e significa que f (n) é, no máximo, proporcional a g(n) ([Bach and Shallit, 1996]), o que satisfaz a definição (2.1.1) e tornase útil nas relações algébricas envolvendo aproximações ([Cormen et al., 2001] e
[Knuth, 1968]). Por exemplo, imagine a seguinte soma, a qual está provada por
14
indução no Apêndice A:
n3 n2 n
1 + 2 + 3 + ... + n =
+
+ .
3
2
6
2
2
2
2
(2.1)
Para n suficientemente grande, podemos escrever:
12 + 22 + 32 + ... + n2 =
n3 n2
+
+ O(n)
3
2
12 + 22 + 32 + ... + n2 =
n3
+ O(n2 )
3
12 + 22 + 32 + ... + n2 = O(n3 ).
(2.2)
(2.3)
Em (2.2), temos uma aproximação assintótica para a soma (2.1) quando n
torna-se grande o suficiente. Estamos dizendo que o lado direito de (2.1) é equi3
2
valente a n3 + n2 , com erro limitado a (ou de ordem máxima) n. O mesmo vale
para as demais equações, que são aproximações menos acuradas que a apresentada
em (2.2). Por fim, em (2.3), indicamos que a soma tem ordem de crescimento
limitada a n3 . Assim, a notação O é usada para indicar funções anônimas, cujos
valores exatos são desconhecidos, mas que possuem limites superiores conhecidos.
Outro ponto importante é que a convenção de usar o sinal “=” na notação
só vale para um lado. Ou seja, podemos escrever que 3n2 = O(n2 ), mas nunca
que O(n2 ) = 3n2 . Devemos lembrar que O(f (n)) representa todo um conjunto de
funções que tem ordem de crescimento limitado a f (n) e que “=” representa uma
relação de pertinência. Portanto, O(n2 ) ∈ 3n2 não faria sentido.
De forma análoga à notação O, podemos definir a notação Ω da seguinte forma:
Definição 2.1.2. ([Cormen et al., 2001]) Seja g(n) uma função com domı́nio N.
Então,
f (n) : existem constantes positivas d e n0 tais que
Ω(g(n)) =
0 ≤ dg(n) ≤ f (n) para todo n ≥ n0 .
Ou seja, uma função f (n) pertence ao conjunto Ω(g(n)) se existir uma constante positiva d tal que o produto dg(n) limite inferiormente f (n) a partir de
algum n. Assim, a função f (n) = 3n3 − 3 ∈ Ω(n2 ), pois temos que n2 ≤ 3n3 − 3
para todo n ≥ 2. Neste caso especı́fico, temos d = 1 e n0 = 2. De fato, qualquer
polinômio não negativo tem crescimento limitado inferiormente por um polinômio de grau menor ou igual ao dele. Como fizemos com a notação O, quando
f (n) ∈ Ω(g(n)), iremos escrever f (n) = Ω(g(n)). Seguindo a analogia, a notação
Ω é comumente usada para indicar o custo mı́nimo de um algoritmo.
2.1.3
Classes de Complexidade
Estamos preocupados, neste trabalho, com os problemas de decisão ou decidı́veis, que são os problemas para os quais temos respostas de simples verificação,
15
geralmente sim ou não ([Cormen et al., 2001]). Ou seja, quando se tem que decidir se uma dada entrada satisfaz ou não um problema. A primalidade é um
problema de decisão, pois queremos determinar se um número é ou não é primo.
Algoritmos não-determinı́sticos são aqueles que respondem um problema de
decisão mas que, em alguns pontos, podem escolher qual passo seguir, dentre um
número finito de possibilidades, podendo, inclusive, fornecer uma saı́da não válida
para o problema ([Bach and Shallit, 1996]). A escolha do caminho pode ser feita,
por exemplo, heuristicamente (procurando-se o melhor caminho para a resposta).
Como exemplo, imagine que temos uma lista de conferência de estoque de um
depósito qualquer. A lista pode ser percorrida de várias formas. Normalmente,
começaremos seguindo a ordem da lista mas, invariavelmente, acharemos objetos
próximos uns dos outros no estoque que acabarão por serem conferidos antes do
previsto. Neste caso, independentemente do tempo variável de execução da tarefa,
saberemos, ao final, se todo o estoque confere ou não. Um outro exemplo envolve
a primalidade ([Bach and Shallit, 1996]). Imagine um algoritmo que recebe, como
entrada, dois inteiros n e d, maiores que 1 e com d < n. Se d divide n, o algoritmo
fornece a saı́da “n não é primo”. Se d não divide n, o algoritmo fornece a saı́da
“não sei”. Neste caso, trata-se de um algoritmo de verificação de primalidade, em
que fornecemos um certificado: um possı́vel fator d de n ([Cormen et al., 2001]).
Se o certificado for verdadeiro, então n não é primo. Se for falso, nada podemos
concluir, pois n pode ter outros fatores. Assim, um algoritmo não-determinı́stico
nem sempre fornece uma resposta válida e, além disso, a cada execução, o caminho
percorrido e, portanto, a quantidade de passos podem ser diferentes.
Quando a escolha do caminho é totalmente aleatória, ou pelo menos envolve
algum tipo de randomização, temos os chamados algoritmos probabilı́sticos ou
randomizados. Se o algoritmo citado acima escolher, aleatoriamente, novos candidatos a fatores de n para cada vez que a divisão falhar, estará aumentando a
probabilidade de n ser primo. No entanto, por maior que seja a probabilidade,
no caso de uma divisão se verificar, n será composto. Algoritmos probabilı́sticos
que possuem taxa de erro bem controlada são muito usados na prática devido a
sua alta eficiência ([Bach and Shallit, 1996]).
Já os algoritmos determinı́sticos podem ser vistos como um caso especial de
algoritmos não-determinı́sticos que só possuem uma única seqüência de passos
que sempre leva à resposta correta ([Hopcroft et al., 2001]). Assim, para cada
entrada, teremos um tempo de execução fixo para esses algoritmos, além de uma
saı́da correta.
Com relação ao tempo de execução, algoritmos com custo limitado a O(nk ),
em que n representa o tamanho da entrada e k é constante, são chamados algoritmos de tempo de execução polinomial ([Cormen et al., 2001]). Na Teoria da
Complexidade, os problemas são classificados quanto à dificuldade de serem resolvidos por algoritmos. Considerando a complexidade exclusivamente temporal,
há, entre outras, três importantes classes: P, NP e co-NP.
Na classe P estão os problemas de decisão que podem ser resolvidos por algoritmos determinı́sticos em tempo polinomial. A classe P representa, assim, os problemas de melhor solução computacional ou mais fáceis ([Cormen et al., 2001]).
Há casos, entretanto, em que o tempo de execução, apesar de polinomial, é excessivamente grande, preferindo-se a utilização de algoritmos probabilı́sticos de
16
tempo polinomial mais eficientes, com margem de erro controlável.
Na classe NP estão os problemas de decisão que podem ser resolvidos por algoritmos não-determinı́sticos em tempo polinomial (“NP” vem de non-deterministic
polynomial ). Equivalentemente, podemos definir a classe NP como o conjunto
dos problemas de decisão cujas soluções que os satisfazem podem ser verificadas
em tempo polinomial ([Cormen et al., 2001]). Algoritmos de verificação são algoritmos que recebem dois valores: a entrada para o problema e um certificado.
Com a utilização do certificado, eles podem tomar corretamente pelo menos uma
das decisões.
A classe co-NP é complementar à classe NP: compreende os problemas de
decisão cujas soluções que não os satisfazem podem ser verificadas em tempo
polinomial ([Cormen et al., 2001]). Como vimos anteriormente, podemos verificar
se um determinado número n não é primo com a utilização de um certificado d,
ao realizarmos a divisão de n por d. Essa divisão pode ser realizada em tempo
polinomial, nos fornecendo um não correto quando d for fator de n (o certificado
for verdadeiro). Assim, o problema da primalidade pertence à classe co-NP.
Com essas definições, verificamos que qualquer problema pertencente à classe
P, também pertence às classes NP e co-NP, pois, se um problema decidı́vel tem
solução determinı́stica em tempo polinomial, tanto as entradas que satisfazem o
problema, como as que não o satisfazem, podem ser verificadas em tempo polinomial. Ou seja, P ⊆ NP e P ⊆ co-NP. E o fato de um problema estar na classe
NP não quer dizer que o mesmo não esteja na classe P, pois, talvez, apenas não
tenhamos descoberto um algoritmo determinı́stico em tempo polinomial que o resolva. Foi o que aconteceu com o problema da primalidade, que antes do algoritmo
AKS, apresentado em 2002, era considerado apenas pertencente a NP ∩ co-NP
([Agrawal et al., 2004]). Alguns teóricos, inclusive, sugerem que todos os problemas NP e co-NP estejam em P, mas a maioria discorda, sendo, então, P 6= NP e
P 6= co-NP a conjectura atual ([Hopcroft et al., 2001] e [Cormen et al., 2001]).
Figura 2.2: Conjectura atual para a relação entre as classes de complexidade.
Essa conjectura se sustenta nos problemas NP-completo, que são problemas
que se encontram na classe NP e compartilham caracterı́sticas comuns. Se alguém
for capaz de construir um algoritmo que resolva apenas um desses problemas
deterministicamente em tempo polinomial, todos os problemas NP passarão a ser
considerados também P. Mas acredita-se, ainda que não provado, que isso seja
impossı́vel ([Bach and Shallit, 1996]). Na Figura 2.2, vemos a relação mais aceita
atualmente entre essas classes. Para ressaltar, estamos considerando algoritmos
17
construı́dos para computadores binários (computadores realizados)3 .
2.2
Testes de Primalidade
Os números primos são estudados desde a antigüidade ([Ribenboim, 1995]).
Euclides, em seu livro Os Elementos, define o número primo como “aquele que é
medido somente pela unidade” [Coutinho, 2004]. De fato, os gregos antigos não
chamavam a unidade de número e, como não denominavam matematicamente o
zero, seus números naturais começavam no 2. Assim, pela definição de Euclides, entende-se por primo o número (a partir de 2) que é composto apenas por
ele mesmo e pela unidade. Na mesma obra, é demonstrada, com a utilização
do Teorema Fundamental da Aritmética, a infinidade do conjunto dos números
primos.
Naquela época, a dificuldade de se encontrar números primos já intrigava a
curiosidade matemática, pois sua distribuição se mostra incerta entre os naturais.
Eratóstenes, então, construiu um método para encontrar primos, considerado o
primeiro algoritmo para tal propósito, e que hoje é conhecido como Crivo ou
Peneira de Eratóstenes. O método não consiste em nenhuma fórmula explı́cita,
mas implicitamente utiliza multiplicações (em forma de contagem), que os gregos
sabiam ser mais fáceis de realizar do que divisões ([Ribenboim, 1995]).
Do Renascimento Cientı́fico ao Século XIX, os estudos sobre a primalidade
se intensificaram, mas ainda com importância puramente teórica. Fermat, Euler
e Gauss foram alguns expoentes desse tempo. Surgem muitas conjecturas, das
quais várias são derrubadas. Uma das mais famosas é o chamado número de
Fermat, que tem a seguinte forma:
n
Fn = 22 + 1, para n ≥ 0.
Fermat constatou que Fn é primo para 0 ≤ n ≤ 4 e conjecturou que Fn seria
sempre primo para qualquer n ≥ 0. Somente décadas depois, após a morte de
Fermat, Euler demonstrou que F5 = 232 + 1 = 4294967297 é composto pelos
primos 641 e 6700417, derrubando a conjectura. O problema era que, naquele
tempo, fazer cálculos com números dessa ordem era algo extremamente penoso.
Fermat também enunciou um importante teorema que envolve a primalidade, o
Pequeno Teorema de Fermat, base de muitos testes de primalidade probabilı́sticos
atuais ([Coutinho, 2004]) e do próprio algoritmo AKS ([Agrawal et al., 2004]).
Antes de enunciarmos e provarmos esse teorema, vejamos a seguinte
Proposição 2.2.1. Sejam a, b e p inteiros, com p primo. Se p divide o produto
ab, então p divide a ou p divide b (ou ambos).
Prova. Se p é primo, não possui fatores menores que p. Se p divide ab, então o
produto ab possui, ao menos, um fator p que deve estar em a ou b. Não há como
dividir esse fator em fatores menores separados em a ou b, pois ele é primo.
3
Algoritmos feitos para computadores idealizados, como os quânticos, podem ter uma outra
classificação para sua complexidade.
18
Veja que se p não é primo, então a proposição não vale. Por exemplo, 6|(4 · 3)
mas 6 - 4 e 6 - 3. Agora, vejamos o
Teorema 2.2.2 (Pequeno Teorema de Fermat).
Se m é primo, então, para qualquer a inteiro tal que mdc(a, m) = 1, temos:
am−1 = 1 (mod m).
Prova. Considere o intervalo [1, m − 1]. Sabemos que m não divide nenhum
número desse intervalo, já que são todos menores que m. Para quaisquer j e k
distintos pertencentes a esse intervalo, |j − k| também pertence ao intervalo, logo
m - (j − k). Então, como a é relativamente primo a m, temos que nenhum dos
números do conjunto {a, 2a, 3a, ..., (m−1)a} divide m. Se a divisão fosse possı́vel,
como m é primo, pela Proposição 2.2.1, ou m teria que dividir a, ou teria que
dividir um dos números no intervalo [1, m − 1].
Assim, os termos a, 2a, 3a, ..., (m − 1)a, quando reduzidos módulo m, são nãonulos e distintos entre si. Caso contrário, se ja = ka(mod m), com j e k distintos
e pertencentes ao intervalo [1, m − 1], temos, pela definição de congruência, que
m | (ja − ka) = a(j − k). Como m é primo, isso significa que ou m divide a ou
m divide (j − k), ambos absurdos.
Temos, então, que a, 2a,..., (m − 1)a são congruentes a 1, 2,..., (m − 1) módulo
m, em alguma ordem, numa relação biunı́voca4 . Podemos escrever que
a · 2a · 3a · . . . · (m − 1)a = 1 · 2 · 3 · . . . · (m − 1) (mod m).
Simplificando ambos os lados por 1 · 2 · 3 · . . . · (m − 1), temos o resultado
desejado.
Portanto, assumindo que mdc(a, m) = 1, temos que se a congruência não
se verificar, certamente m é composto. Já a recı́proca do Teorema 2.2.2 não é
verdadeira: podemos ter um certo m que é composto e que satisfaz a congruência
para algum a relativamente primo a m. Por exemplo, 53 = 1 (mod 4). Ou seja,
no caso do teste não falhar, teremos apenas uma probabilidade de que o número
m é primo, podendo ser aumentada ao se repetir o teste com um novo número a,
mas nunca nos dando a certeza. Neste caso, m é chamado pseudoprimo de base
a ([Ribenboim, 2004]).
Há uma forma mais comum desse teorema que será usada em nossa prova do
AKS:
Corolário 2.2.3.
Se m é primo, então, para qualquer a inteiro , temos:
am = a(mod m).
4
Estas são exatamente as classes residuais módulo m distintas, excluindo-se a classe 0̄
([Hefez, 2002])
19
Prova. ([Campello and Leal, 2007]). Se m - a, como m é primo, então a e m
são coprimos e podemos usar o Pequeno Teorema de Fermat:
am−1 = 1(mod m).
Então, pela definição de congruência,
m | (am−1 − 1).
Logo,
m | a(am−1 − 1),
o que é o mesmo que
m | (am − a).
Então, novamente pela definição de congruência,
am = a(mod m).
Por outro lado, se m | a, então
m | a(am−1 − 1),
o que é o mesmo que
m | (am − a).
Logo, pela definição de congruência,
am = a(mod m).
O Pequeno Teorema de Fermat nos fornece um teste de primalidade de tempo
polinomial, mas não-determinı́stico5 . Ele apenas responde com certeza para a nãoprimalidade de um número. Até 2002, na Teoria da Complexidade, o problema
era, pois, comumente considerado classe co-NP, apesar de Pratt ter mostrado, em
1974, que o problema estava na classe NP também ([Agrawal et al., 2004]).
As coisas começaram a mudar, no que diz respeito a pesquisas e necessidades
práticas, com o advento das ferramentas computacionais. Já no final do Século
XIX, Lucas descobre um método eficiente para testar a primalidade de números
de Mersenne (números que têm a forma 2p − 1, com p primo). Seu método foi
refinado por Lehmer, um dos pioneiros no uso dos computadores em teoria dos
números ([Coutinho, 2004]), em meados do século passado, e é usado até hoje,
sob o nome de teste de Lucas-Lehmer.
A partir da década de 70, o estudo da primalidade deixa de ter importância
puramente teórica devido ao aparecimento dos algoritmos de criptografia de dados
e, com o auxı́lio de computadores mais velozes, teve importantes avanços. Algoritmos de criptografia, como o RSA, baseiam-se na utilização de grandes números
primos que, multiplicados, geram um composto o qual, por sua vez, participará
5
Se mantivermos o mesmo a e repetirmos o teste, a resposta será a mesma, inconclusa. Mas
se realizarmos o teste novamente, variando o valor de a, teremos um teste probabilı́stico.
20
na formação das chaves criptográficas. Para que alguém quebre o código, será
necessária a fatoração desse número. Mas aı́ está a segurança da criptografia
RSA: computacionalmente é mais difı́cil decompor um número em seus fatores
primos do que testar a primalidade de dois grandes números para serem usados
na geração das chaves ([Cormen et al., 2001]). E quanto mais eficientes os testes
de primalidade, mais fácil será encontrar primos gigantes para formarem a chave,
tornando ainda maior essa diferença.
Basicamente, até 2002, tı́nhamos dois grupos de testes de primalidade: algoritmos determinı́sticos, mas exponenciais, e algoritmos polinomiais, mas nãodeterminı́sticos, sobretudo probabilı́sticos ([Ribenboim, 2004]). Estes últimos
ainda são muito usados na prática devido a sua alta eficiência ([Coutinho, 2004]),
entre eles o teste de Miller-Rabin ([Cormen et al., 2001]), que avalia pseudoprimos
de Fermat, e os testes com curvas elı́pticas ([Ribenboim, 2004]).
Antes de enunciarmos o algoritmo AKS, os teoremas envolvidos e a demonstração de sua correção, vamos enunciar o Crivo de Eratóstenes, que inclusive será
um dos métodos utilizados na análise da complexidade de duas de suas etapas.
Também vamos incluir um pequeno teorema sobre a distribuição dos números
primos, além de verificar como se determina o custo de execução de um teste
de primalidade em função do tamanho da entrada, já que o parâmetro usado
para tamanho da entrada de um problema varia, conforme o tipo de problema
([Cormen et al., 2001]).
2.2.1
O Crivo de Eratóstenes
Como o próprio nome diz, o Crivo de Eratóstenes separa os primos dos compostos, dentro de um intervalo dado, como se fosse uma peneira.
Algoritmo 1 Crivo de Eratóstenes
Primeiro, determine um número final para o intervalo, iniciado em 2, em que
se deseja achar todos os números primos.
Comece com o primeiro número do intervalo, no caso 2. Então, risque todos os
demais números do intervalo que sejam múltiplos de 2 (simplesmente, risque-os
contando de 2 em 2).
Agora, vá ao próximo número do intervalo que não esteja riscado, neste caso,
3. Risque os demais de 3 em 3 (múltiplos de 3).
Continue o procedimento de procurar no intervalo o próximo número não riscado (primo) e de riscar todos os seus múltiplos até que todos os números
compostos do intervalo estejam riscados. Ou seja, para cada p (o próximo
número ainda não riscado da lista), risque os demais de p em p.
Podemos simplificar o teste, deixando-o mais econômico, ao verificarmos uma
propriedade importante: ao passarmos pelo número inteiro igual ou imediatamente superior ao valor da raiz quadrada do limite do intervalo, todos os compostos já estarão riscados. Isso é uma conseqüência do seguinte
21
Teorema 2.2.4 (Teorema da Fatoração Única ou Teorema Fundamental
da Aritmética). Dado um inteiro positivo n ≥ 2, podemos sempre escrevê-lo na
forma:
n = p1 e1 · p2 e2 · ... · pk ek ,
em que 1 < p1 < p2 < ... < pk são números primos e e1 , e2 , ..., ek são inteiros
positivos.
Ou seja, qualquer número inteiro maior que 1 pode ser decomposto em números primos, e essa decomposição é única, a menos da ordem em que aparecem os
fatores.
Prova. Primeira parte: qualquer inteiro maior que 1 pode ser decomposto em
primos.
Vamos usar o Princı́pio da Indução Matemática (ver Apêndice A). Seja S o
conjunto de todos os inteiros positivos que são produtos de primos. 2 ∈ S (base
de indução), já que ele próprio é número primo e admite decomposição trivial.
Suponha que todo inteiro r, com 2 ≤ r < a para algum inteiro a > 2, seja
elemento de S (passo indutivo). Se a é primo, então também pertence a S, pois
admite decomposição trivial. Se a é composto, então a = bc, sendo que 2 ≤ b < a
e 2 ≤ c < a. Portanto, pela hipótese indutiva, temos que b e c são elementos de
S, ou seja, podem ser escritos como produtos de primos. Assim, a também é um
produto de primos.
Segunda parte: unicidade da decomposição.
Novamente usaremos o Princı́pio da Indução Matemática. Sejam n = p1 p2 ...ps
e n = q1 q2 ...qt duas decomposições de n, em que p1 ≤ p2 ≤ ... ≤ pn e q1 ≤ q2 ≤
... ≤ qt são primos (a possibilidade dos fatores serem iguais elimina a representação
em potências, como apresentada no inı́cio do teorema). Se s = 1, temos que
n = p1 = q1 q2 ...qt . Mas, como p1 é primo, então, necessariamente, t = 1, p1 = q1
e as duas decomposições são idênticas. Suponha agora que s > 1 e que a segunda
parte do teorema seja verdadeira para s − 1. De n = p1 p2 ...ps = q1 q2 ...qt , temos
que p1 é um divisor do produto q1 q2 ...qt e, logo, é um divisor de um dos fatores
qi , com 1 ≤ i ≤ t. Como qi é primo, então p1 é igual a qi . Precisamos, agora,
checar se os demais fatores também são idênticos. Seja n0 = n/p1 = n/qi . As
decomposições para n0 seriam p2 p3 ...ps = q1 q2 ...qi−1 qi+1 ...qt . Da hipótese indutiva
(s−1 fatores), temos que estas duas últimas decomposições são idênticas, a menos
da ordem dos fatores. Logo, a segunda parte do teorema também é verdadeira
para s, sendo p1 p2 ...ps e q1 q2 ...qt decomposições idênticas para n.
Assim, seja f o menor fator primo de um composto n. Então, n = f a, em que
a ≥ f também é fator de n, podendo ser primo ou composto por primos maiores
ou iguais a f . Mas, se √
a ≥ f , então, substituindo na igualdade anterior, temos
2
que n ≥ f , donde f ≤ n é o menor fator primo de um composto n.
Voltando ao crivo, suponha que estejamos prestes a riscar os compostos de p
em p para o primo p. Caso passemos por um composto que também seja múltiplo
de algum primo q < p, o mesmo já estará riscado (isso foi feito quando passamos
22
por q e riscamos os múltiplos de q em q). Então, basta começarmos a procurar
por múltiplos de p que não sejam múltiplos de √
algum primo menor do que p. Ou
2
seja, a partir de p . Mas, se p for maior que n, p2 será maior que n e estará
fora do intervalo estipulado para o crivo. Portanto, para cada primo p, basta
√
riscarmos os compostos de p em p a partir de p2 , e quando p for maior que n,
todos os compostos já estarão riscados.
Ainda podemos simplificar um pouco mais o teste. Começamos nosso intervalo
a partir de 3 e listamos apenas os números ı́mpares, já que 2 é o único número
par primo e todos os demais pares são múltiplos de 2. Desta forma, para cada
primo p, riscamos os múltiplos de 2p em 2p.
O Crivo de Eratóstenes é fácil de ser implementado e útil para encontrar
números primos em intervalos pequenos. Porém, para números muito grandes,
torna-se inutilizável devido a sua complexidade exponencial, como veremos na
Subseção 2.2.3.
2.2.2
Distribuição dos Números Primos
A distribuição dos números primos é um assunto importante, do qual depende
o bom entendimento do algoritmo AKS. Como dito anteriormente, desde a antigüidade os matemáticos sabem que os números primos são infinitos. Um teorema
que prova essa propriedade é devido a Euclides ([Monteiro, 1978] e [Hefez, 2002]),
e é conseqüência do Teorema da Fatoração Única:
Teorema 2.2.5. Existem infinitos números primos.
Prova. Suponha que p1 , p2 ,...,ps sejam todos os números primos existentes.
Sejam os inteiros a = p1 p2 ...ps e a + 1 = p1 p2 ...ps + 1. Como é maior que 1,
então, pelo Teorema da Fatoração Única, a + 1 pode ser fatorado exclusivamente
por números primos. Seja p um dos fatores primos de a + 1. O número p deve,
necessariamente, coincidir com um dos primos p1 , p2 ,...,ps , pois estes representam,
por hipótese, todos os primos existentes. Logo, p divide o produto p1 p2 ...ps = a.
Como p é fator de a+1, e divide a, segue que p tem que dividir 1, o que é absurdo.
Mas esse teorema só mostra que os números primos são infinitos. Não diz nada
sobre como são distribuı́dos. Há um importante teorema sobre a distribuição dos
números primos, o qual mostra que a quantidade de primos entre 2 e um inteiro
positivo n, freqüentemente denotada por π(n), é
n
.
ln(n)
Esse teorema foi conjecturado por vários matemáticos ([Ribenboim, 1995]),
entre eles, Gauss. Por volta de 1850, Tschebycheff mostrou que
n
n
0, 922
≤ π(n) ≤ 1, 105
(2.4)
ln(n)
ln(n)
π(n) ∼
π(n)
e, portanto, se limn→∞ n/ln(n)
existir, então este converge para 1. Mas essa
convergência só foi provada em 1896, nos trabalhos independentes de Hadamard e Vallé Poussin, o que ficou conhecido como Teorema dos Números Primos
23
([Coutinho, 2004]). A demonstração desse teorema é bastante difı́cil, por isso não
iremos prová-lo aqui. Para maiores detalhes, veja [Ribenboim, 1995]. No entanto,
[Coutinho, 2004] nos apresenta um interessante teorema que envolve a distribuição dos números primos e que pode ser visto como uma “prova fraca” de uma das
desigualdades de (2.4), sendo importante para se entender o custo de uma das
etapas do algoritmo AKS ([Coutinho, 2004]):
Teorema 2.2.6. Sejam n um inteiro positivo e P(2n) o conjunto de todos os
primos positivos menores ou iguais a 2n. Então,
Y
2n
=
pk p ,
n
p∈P(2n)
onde6
kp =
∞
X
(b
m=1
2n
n
c − 2b m c).
m
p
p
Prova. Primeiro, note que a soma kp é finita, pois, quando pm > 2n, temos que
b
2n
n
c = b m c = 0.
m
p
p
A quantidade de múltiplos positivos de q menores ou iguais a n pode ser
denotada por bn/qc. Por exemplo, a quantidade de múltiplos positivos de 4
menores ou iguais a 20 é igual a quantidade de múltiplos positivos de 4 menores
ou iguais a 22, ou seja, b20/4c = b22/4c = 5, que são exatamente os múltiplos
4, 8, 12, 16 e 20.
Mas os múltiplos positivos de q menores que n também são menores que n!. E
mais: esses múltiplos participam da formação de n!, com pelo menos uma potência
de q. Em outras palavras, q bn/qc |n!. Mas um múltiplo de q pode também ser uma
potência de q, de modo que queiramos representar todas as potências de q que
estão na fatoração de n!. Isso é especialmente útil se q for primo. Usaremos a
notação
∞
X
(bn/pk c) =
µ(n, p) =
k=1
bn/pc + bn/p2 c + bn/p3 c · · · bn/pm c,
para representar a quantidade total de potências, a qual chamaremos multiplicidade, de um primo p ≤ n na fatoração de n!. Perceba que múltiplos de potências
maiores também são múltiplos de potências menores, por isso são contados mais
de uma vez, justamente na quantidade de potências de p que possuem. E essa
soma é finita, pois quando pk > n, passarão a ser somados apenas 0’s.
Desenvolvendo o binomial, temos
2n
2n!
=
.
n
n!n!
6
Os sinais bc serão usados para representar o inteiro igual ou imediatamente inferior, e os
sinais de serão usados para representar o inteiro igual ou imediatamente superior.
24
Podemos, então, usar a notação de multiplicidade para representar as potências de p que há nesse binomial. Note que as potências de p que formam n!
também são potências de p formadoras de 2n!. E em 2n! há, pelo menos, o dobro
de potências de p que há em n!. Mas esses fatores em comum serão cancelados
na fração, pois são termos comuns entre o numerador
e o denominador. Então,
podemos dizer que a multiplicidade de p em 2n
é
n
∞
∞
X
X
k
µ(2n, p) − 2µ(n, p) =
(b2n/p c) − 2
(bn/pk c).
k=1
k=1
E isso é exatamente kp do enunciado do teorema. Assim, para cada um dos p
primos que existem em P(2n), existe um respectivo kp , que é a sua multiplicidade
em 2n
. Multiplicando
cada um deles elevado ao seu respectivo kp , obtemos
n
2n
exatamente n .
Desse teorema seguem dois corolários, sendo que o segundo será usado no
cálculo do custo do algoritmo AKS:
Corolário 2.2.7. Cada termo kp do Teorema 2.2.6 é menor ou igual a blog2 (2n)c.
Prova. Seja ps a maior potência de p que divide 2n. Neste caso, kp não pode ser
maior que s, pois representa, justamente, a quantidade de fatores primos p em 2n.
Por outro lado, ps ≤ 2n. Aplicando log nos dois lados, temos slog2 p ≤ log2 (2n).
Mas p é primo positivo, logo p ≥ 2 e log2 p ≥ 1. Então,
kp ≤ s ≤ blog2 (2n)/log2 pc ≤ blog2 (2n)c.
Corolário 2.2.8. Se n é um inteiro positivo, então 2n ≤ (2n2 )# , onde m# ,
denominado primorial de m, representa o produto de todos os primos positivos
menores ou iguais ao inteiro positivo m.
Prova. Seja r um inteiro positivo. O produto,
Y
pr = ((2n)# )r ,
(2.5)
p∈P(2n)
pois significa, justamente, multiplicar todos os primos positivos menores ou iguais
a 2n elevados a r.
Como
Y
2n
=
pk p
n
p∈P(2n)
e kp ≤ blog2 (2n)c, se fizermos r = blog2 (2n)c em (2.5), teremos que
2n
divide ((2n)# )r ,
n
25
2n
pois o produto de potências de primos
que
representa está incluı́do no produto
n
Q
r
de potências maiores de primos de p∈P(2n) p . Portanto,
2n
≤ ((2n)# )r .
n
Mas,
2n
2m(2m − 1)(2m − 2) · · · (m + 2)(m + 1)
=
=
n
m!
2m (2m − 1) 2(m − 1) (2m − 3) 2(m − 2)
(m + 2) (m + 1)
=
···
≥ 2n ,
m m−1 m−2 m−3 m−4
2
1
do que segue:
2n ≤ ((2n)# )r .
Se essa relação vale para o inteiro positivo n, então vale para n2 , ou seja,
2
2n ≤ ((2n2 )# )r .
Aplicando log base 2 em ambos os lados, temos:
2
log2 (2n ) ≤ log2 (((2n2 )# )r )
n2 ≤ rlog2 ((2n2 )# ).
Como escolhemos r = blog2 (2n2 )c, então
n2 ≤ (log2 (2n2 ))log2 ((2n2 )# )
≤ (log2 2 + log2 n2 )log2 ((2n2 )# )
≤ (1 + 2log2 n)log2 ((2n2 )# ).
Assim,
n≤
p
p
(1 + 2log2 n)log2 ((2n2 )# ) ≤ (1 + 2log2 n) · log2 ((2n2 )# ).
(2.6)
Mas,
n≥
p
(1 + 2log2 n)
para n ≥ 2. Disto, e de (2.6), segue que
n ≤ log2 ((2n2 )# )
2n ≤ (2n2 )# .
26
2.2.3
Eficiência dos Testes de Primalidade
Já dissemos que até antes do algoritmo AKS ser apresentado, os algoritmos
que testavam a primalidade de um número eram divididos, com relação ao tempo
de resposta para o tamanho da entrada, entre os determinı́sticos exponenciais e
os não-determinı́sticos polinomiais. Mas como é feita essa medida de eficiência7 ?
Antes de mais nada, temos que saber qual o parâmetro usado para medir o tamanho da entrada, como referido no inı́cio da seção. De uma forma geral, esses
testes basicamente realizam operações numéricas com o dado de entrada afim de
se verificar sua primalidade. Computadores binários realizam cálculos binários,
os quais terão um custo de tempo maior quanto mais bits (dı́gitos binários) estiverem envolvidos nas operações. Ou seja, quanto mais bits possuir a entrada,
maior será o custo do algoritmo. Desta forma, o tamanho da entrada usado para
o cálculo da eficiência dos testes de primalidade é a quantidade de bits da entrada.
Mas a quantidade de bits depende diretamente da entrada. Na verdade, para
uma entrada n, a quantidade b de bits é ≈ log2 n. Devido às propriedades da
notação assintótica, assumimos b = log2 n (o número b de bits usados para a
representação binária de n) como sendo o tamanho da entrada n.
Isso pode causar um certo desconforto quando analisamos a eficiência de um
teste de primalidade. Por exemplo, um teste de primalidade de custo polinomial
será aquele que tenha custo O((log2 n)k ) para algum k inteiro. Por outro lado, um
algoritmo que seja O(n), ou pior, será dito exponencial. Não podemos perder de
vista que n não representa o tamanho da entrada, papel desempenhado por b (a
quantidade de bits da entrada). Ou seja, um custo O((log2 n)k ) equivale a O(bk ),
em que b é a quantidade de bits da entrada (tamanho da entrada) e k é um inteiro,
portanto polinomial. Por outro lado, um custo O(n) equivale a O(2log2 n ) = O(2b ),
ou seja, exponencial.
Podemos, então, realizar os cálculos dos custos das operações elementares de
inteiros (soma, subtração, multiplicação e divisão) realizadas por computadores
binários, que são as operações básicas para operações mais complexas e o ponto
de partida para o custo dos testes de primalidade. Deixaremos de fora outras
operações mais simples e fatores que não dependem tanto do tamanho da entrada (operações de comparação, arquitetura da máquina, latência de memória,
tempo de transferência de dados etc) e que, portanto, não influenciam na notação
assintótica.
No caso da soma entre dois inteiros, os bits dos dois números são somados um
a um, da direita para a esquerda, levando em consideração, ainda, o bit de reserva
quando a soma dos bits anteriores for maior que 1 (o conhecido ”vai um” da soma).
Considere que o maior dos números, n, tenha b bits de comprimento. A outra
parcela também poderá ser representada por b bits de comprimento, preenchida
com 0’s à esquerda, caso seja necessário. Assim, haverá pelo menos b somas. No
entanto, não consideramos o cálculo com os bits de reserva. Poderemos ter, no
máximo, um bit de reserva para cada bit a partir do segundo. Ou seja, podemos
ter mais b − 1 somas, totalizando um máximo possı́vel (pior caso) de 2b − 1 somas
(um polinômio na variável b, de grau 1). Assim, pela notação assintótica, temos
7
Importante salientarmos que estamos nos referindo, exclusivamente, à eficiência temporal.
A complexidade espacial não será considerada neste trabalho.
27
que o custo da soma entre dois inteiros é O(b), em que b representa o número de
bits do maior número da entrada. Como b = log2 n, temos que o custo da soma
entre dois números inteiros menores ou iguais a n é O(log2 n).
Na multiplicação, consideramos novamente b o número de bits do maior fator.
Primeiro, multiplicamos o maior fator por cada bit do menor fator. Em binário,
isso é relativamente fácil. Para cada bit 1 do menor fator, geramos um produto
elementar idêntico ao maior fator, mas deslocado à esquerda proporcionalmente
à ordem do bit no menor fator. Para cada bit 0, teremos um produto nulo. Desta
forma, teremos, no máximo, b produtos gerados (no caso em que os fatores têm
o mesmo número de bits e todos são 1). Ou seja, um custo máximo de b operações. Ao final, somamos todos os produtos elementares e obtemos a multiplicação.
Cada soma terá um custo máximo de 2(b + 1) − 1 = 2b + 1 operações, que é o
custo máximo encontrado anteriormente para a soma, considerando uma parcela
com b + 1 bits (devido ao deslocamento à esquerda de cada parcela subseqüente).
Como são b produtos elementares, teremos b − 1 somas. Desta forma, considerando ainda o custo inicial para a geração dos produtos elementares, o custo total
da multiplicação será (b − 1)(2b + 1) + b = 2b2 − 1 operações, o que é O(b2 ).
Assim, o custo da multiplicação entre dois números inteiros menores ou iguais a
n é O((log2 n)2 ). Esse resultado era esperado, se pensarmos que a multiplicação
de inteiros não passa de uma repetição de somas que têm custo O(b), e a quantidade dessas somas (produtos elementares) depende diretamente da quantidade
de dı́gitos dos fatores.
Para a subtração a − b, em que a e b são números inteiros, lembramos que
se trata da soma de a com o oposto de b. Logo, há apenas o trabalho inicial de
trocar o bit de sinal de b para depois realizar a soma normalmente. Assim, o
custo da operação de subtração entre dois números inteiros menores ou iguais a
n é O(log2 n).
Como na multiplicação temos o encadeamento de somas, na divisão entre
inteiros temos o encadeamento de subtrações. Para dois números inteiros a e b,
a divisão a/b representa a quantidade de vezes que b pode ser subtraı́do de a.
Caso a seja maior ou igual a b, subtraı́mos b de a sucessivamente até atingirmos
um valor menor que b. Não vamos demonstrar aqui, mas o custo para a divisão8
e, conseqüentemente, para o cálculo de restos é O((log2 n)2 ), para dois números
menores ou iguais a n.
2.2.3.1
Custo do Crivo de Eratóstenes
A única operação efetuada no Crivo de Eratóstenes é a soma, que é executada
na contagem de 2p em 2p (considerando o crivo mais econômico, em que constam
apenas ı́mpares). Para cada primo p, adicionamos 2p sucessivamente, obtendo
3p, 5p, 7p etc, que são os múltiplos ı́mpares de p, até n. Como as parcelas estão
limitadas a n, teremos um custo limite de O(log2 n) para cada soma. Precisamos
agora saber quantas somas serão feitas. Mais precisamente, quantos compostos
ı́mpares de p existem até n.
8
Para verificação, [Coutinho, 2004] sugere a leitura de von zur Gathen e J. Gerhard, 1999,
Modern Computer Algebra, Cambridge University Press.
28
Seja k > 0 um inteiro. Se kt ≤ n para algum inteiro positivo t, então os
múltiplos de k menores ou iguais a n são:
kt, em que t = 1, 2, 3, ..., bn/kc.
Assim, existem bn/kc múltiplos de k de 1 a n. Como estamos interessados nos
múltiplos ı́mpares, temos, aproximadamente, metade desse valor, ou seja, bn/kc/2
múltiplos ı́mpares de k menores que n.
Somando 2p de cada vez, indo de p até n, realizaremos, no máximo, bn/kc/2
somas, ao custo de O(log
√ 2 n) cada uma. Mas precisaremos fazer isso para cada
primo menor ou igual a n. Suponha que P (k) seja o conjunto de todos os primos
menores ou iguais a k. Desta forma, realizaremos, no máximo
X
X
X
√
bn/kc/2 ≤
n/2k ≤
n/2 ≤ (n n)/2 = (n3/2 )/2
√
p∈P ( n)
√
p∈P ( n)
√
p∈P ( n)
somas, a um custo de O(log2 n) cada uma. Portanto, descartando a constante 1/2,
a qual é insignificante para a notação
O, encontramos um custo total máximo de
√
3/2
O(n log2 n). E como log2 n ≤ n para n ≥ 4, temos um custo total limitado a
O(n2 ). Esta é uma estimativa alta para o custo do Crivo de Eratóstenes, calculada
de forma simples após muitas concessões. Existem estimativas melhores, mas
ainda sim exponenciais ([Coutinho, 2004]). O importante é que essa estimativa
será usada para analisar duas etapas do algoritmo AKS, a qual faz uso do crivo.
2.3
O Algoritmo AKS
Em 2002, os indianos Manindra Agrawal, Neeraj Kayal e Nitin Saxena disponibilizaram na Internet um artigo contendo um algoritmo capaz de realizar um
teste de primalidade determinı́stico em tempo polinomial, resolvendo um antigo
problema matemático ([Ribenboim, 2004]). Após contribuição de Lenstra Jr., eles
publicaram a versão definitiva de seu artigo em 2004 ([Agrawal et al., 2004]).
2.3.1
O Teorema Fundamental
A idéia do algoritmo vem de um lema usado em [Agrawal and Biswas, 2003]
como a base para um teste probabilı́stico de tempo polinomial:
Lema 2.3.1. Sejam a e n inteiros, com n ≥ 2 e mdc(a, n) = 1. Então, n é primo
se, e somente se,
(X + a)n = X n + a (mod n).
Antes de provarmos o lema, vejamos o que ele significa. A notação (mod n)
após a identidade, usada em [Agrawal et al., 2004], significa que os dois polinômios são comparados após a redução dos seus coeficientes módulo n. Ou seja,
(X + a)n = X n + a no anel de polinômios Zn [X].
Por exemplo, o polinômio P (X) = 7X + 3 será reduzido a 2X + 3 no anel
Z5 [X], pois 7 = 2 mod 5 e 3 = 3 mod 5. Assim, (7X + 3) = (2X + 3) mod 5.
29
Portanto, o Lema 2.3.1 nos traz uma identidade de classes de polinômios
módulo n. Vamos à
Prova. Do lado esquerdo, utilizando o teorema binomial, temos:
n
n
n
(X + a) = X + a +
n−1 X
n
k=1
k
X n−k ak .
(2.7)
Consideraremos, primeiro, o caso de n ser primo. Como
n
n!
=
,
k
k!(n − k)!
temos que
n
· k!(n − k)! = n!.
k
(2.8)
Mas n |n! e n é primo, então, de (2.8) e da Proposição 2.2.1, n deve dividir o
produto nk · k!(n − k)!. Só que, como n é primo, para todo 0 < k < n, n - k! e
n - (n − k)!. De fato, n primo não pode dividir nenhum fatorial de inteiro menor
que n, pois esses fatoriais
são compostos apenas de fatores primos menores que
n
n. Portanto, n | k para todo 0 < k < n, tornando esses coeficientes, e todo o
somatório em (2.7), nulos módulo n.
Assim,
(X + a)n = X n + a (mod n)
n−1 X
n
n
n
X +a +
X n−k an = X n + a (mod n)
k
k=1
X n + an = X n + a (mod n).
Do Pequeno Teorema de Fermat (Corolário 2.2.3), an = a (mod n), quando n
é primo, o que completa a primeira parte da prova.
Por outro lado, se n for composto, existe ao menos um fator primo q de n.
Para k ∈ N, suponha q k | n, e q k+1 - n, ou seja, k é a maior potência de q que
divide n. Logo,
∃ c ∈ Z tal que
Neste caso9 , q k -
n
q
n
= c e q - c.
qk
(2.9)
, pois, do contrário, terı́amos:
n
n (n − 1)(n − 2)...(n − q + 1)
n n−1
=
q |
=
.
q
q
(q − 1)!
q q−1
k
Mas q k | nq
temos que
9
n−1
q−1
significa que o resultado da divisão deve ser inteiro e, de (2.9),
Prova modificada de [Tou and Alexander, 2005].
30
n n−1
q q−1
qk
c n−1
.
=
q q−1
Como q - c, e q é primo, então, para que o resultado anterior seja inteiro,
n−1
(n − 1)(n − 2)...(n − q + 1)
q|
=
,
q−1
(q − 1)!
o que significa, novamente por q ser primo (Proposição 2.2.1), que
q | (n − 1) ou q | (n − 2) ou q | (n − 3) ou ... ou q | (n − q + 1).
E, neste caso,
n = 1 (mod q) ou n = 2 (mod q) ou ... ou n = q − 1 (mod q).
Um absurdo, pois assumimos que q | n.
Além disso, q k é relativamente primo a aq , pois mdc(a, n) = 1. Como q é fator
de n, segue que mdc(a, q) = 1. Então, potências de q não possuem qualquer fator
em comum com a ou
potências de a e, assim, mdc(aq , q k ) = 1.
com
n
q
Portanto, n - q · a , o que significa que nem todos os coeficientes do somatório em (2.7) são nulos módulo n e a identidade não se verifica no anel Zn [X],
completando nossa prova, já que dois polinômios numa mesma variável só são
iguais se possuı́rem os mesmos coeficientes para cada monômio de mesmo grau.
Esse lema representa um teste determinı́stico interessante de primalidade, não
fosse ele exponencial! Para comprovar isso, basta mostrarmos que um dos passos
da computação da identidade seja exponencial em função do tamanho da entrada.
Bom, mas a identidade está sobre Zn [X], de forma que não estamos preocupados
como esse anel foi implementado. Então, comecemos já computando (X + a)n em
Zn [X] para n > 0. O primeiro cuidado que precisamos ter é em como expandir
esse termo. Uma forma de fazer isso é utilizando recursão:
(X + a)n = (X + a)(X + a)n−1 .
Neste caso, precisaremos realizar n − 1 produtos. Como a multiplicação tem
custo O((log2 n)2 ), conforme mostrado na Subseção 2.2.3, só o cálculo desses
produtos, sem contar os demais cálculos de redução módulo n, terá um custo
O((n − 1)(log2 n)2 ) = O(n(log2 n)2 ), que é uma função exponencial do tamanho da
entrada em bits. Assim, podemos dizer que esse método tem custo, pelo menos10 ,
Ω(n(log2 n)2 ).
Também
podemos tentar usar a expansão binomial (X + a)n = X n + an +
Pn−1
n
n−k k
a , como fizemos na prova do lema. Neste caso, terı́amos n − 1
k=1 k X
parcelas no somatório. Para avaliar cada coeficiente módulo n, será necessária
uma divisão, que tem custo O((log2 n)2 ), conforme mostrado na Subseção 2.2.3.
10
Como a notação Ω, ao contrário da notação O, é usada para limitar inferiormente o crescimento de funções, podemos ter certeza que não há nada melhor.
31
Mas, no pior caso, quando n é primo, teremos que avaliar os n − 1 coeficientes. Portanto, o custo total para o pior caso é O(n(log2 n)2 ), que já sabemos ser
exponencial.
Uma forma de diminuir o número de operações é calcular quadrados. A partir de X + a, calculamos (X + a)2 , fazendo (X + a)(X + a). E, em seguida,
fazemos as simplificações módulo n necessárias. Depois, calculamos (X + a)4 =
(X + a)2 (X + a)2 , e assim prosseguimos até (X + a)n . Sem considerar as reduções
módulo n intermediárias, teremos realizado, no máximo, apenas log2 n produtos
de polinômios. O problema é que cada produto agora tem custo O(g 2 (log2 n)2 ),
em que g é o grau do polinômio que está sendo multiplicado, já que a multiplicação de polinômios de um mesmo grau g requer, no pior caso, em torno de g 2
multiplicações11 . No primeiro passo, ou seja, no primeiro produto, o grau é 1.
Depois, 2, 22 , . . . , 2log2 n/2 . Como não há limite para o grau dos polinômios em
Zn [X], o grau dos fatores envolvidos em cada quadrado cresce√exponencialmente.
Assim, só o custo do cálculo para o último quadrado será O( n(log2 n)2 ), e, portanto, ainda exponencial em relação ao tamanho da entrada que, como sabemos,
é medido em número de bits. Como não verificamos os
√ demais custos, podemos
dizer que esse método tem custo igual ou pior que O( n(log2 n)2 ).
Seja como for, não é possı́vel calcular a identidade do Lema 2.3.1 em tempo
polinomial. Segundo [Agrawal et al., 2004], o custo desse cálculo é Ω(n).
Então, uma forma de melhorar a eficiência do teste, reduzindo os coeficientes
envolvidos nos cálculos da identidade do Lema 2.3.1, seria avaliar ambos os lados
módulo um polinômio na forma X r − 1 para algum r > 0 ∈ Z:
(X + a)n = X n + a (mod X r − 1, n).
(2.10)
Ou seja, obtemos os restos das divisões (polinomiais) de (X +a)n e X n +a pelo
polinômio X r − 1, os quais possuirão graus menores que r, e depois os avaliamos
módulo n ([Ribenboim, 2004]). Como o polinômio X r − 1 gera um ideal no anel
Zn [X], temos que aquela notação indica que a identidade agora será avaliada no
anel quociente Zn [X]/(X r − 1) ([Agrawal et al., 2004]). Ou seja,
(X + a)n = X n + a em Zn [X]/(X r − 1).
Agora, é possı́vel computar essa nova identidade em tempo polinomial. Para
mostrar isso, ao contrário do que fizemos com a identidade anterior, não basta
mostrar o custo de um único passo. Na verdade, teremos que mostrar que todos
os passos dessa computação terão custo limitado por um polinômio em função do
tamanho da entrada. Novamente, não é importante saber como o anel quociente
será implementado. Apenas queremos saber quanto custa verificar a identidade
naquele anel. Assim, começaremos com a expansão de quadrados, que sabemos
que gera, no máximo, log2 n produtos. Precisamos descobrir, então, quanto custa
o cálculo de um quadrado em Zn [X]/(X r − 1).
11
No pior caso, temos g +1 coeficientes não nulos em cada polinômio. Fazendo a multiplicação
distributivamente, teremos (g + 1)2 multiplicações.
32
Seja p um polinômio reduzido módulo (X r − 1) em Zn [X]. O cálculo de p2
tem custo limitado por O(r2 (log2 n)2 ) em Zn [X], já que o grau g de p é limitado a r. É possı́vel que p2 tenha grau maior que r em Zn [X]. Então, para
acharmos p2 em Zn [X]/(X r − 1), ainda teremos que reduzir p2 módulo X r − 1.
Mas isso quer dizer que teremos que reduzir cada termo X j do polinômio p2 ,
dividindo j por r, quando j ≥ r. Se o grau de p é limitado a r − 1, então
podemos conceder que não teremos j > 2r e não teremos mais que r termos
de grau maior ou igual a r. O custo máximo da divisão de um inteiro limitado a 2r é O((log2 2r)2 ) = O((log2 2 + log2 r)2 ) = O(1 + (log2 r)2 ) = O((log2 r)2 ).
Portanto, o custo total das divisões será O(r(log2 r)2 ). Mas ainda precisamos
somar esses novos termos reduzidos de X, de expoentes menores que r, com
os antigos de mesmo grau, para que o polinômio tenha sua forma usual. Teremos que fazer, no máximo, r somas de coeficientes, limitados a n2 , pois antes
da multiplicação os coeficientes eram limitados a n. Isso terá um custo total de O(rlog2 n2 ) = O(2rlog2 n) = O(rlog2 n). O próximo passo será reduzir
cada um dos, no máximo, r coeficientes módulo n, para que o polinômio p2
fique ajustado no anel Zn [X]/(X r − 1) de forma definitiva. Teremos que fazer, no máximo, r divisões por n de coeficientes limitados a 2n2 , pois antes das
somas dos coeficientes de mesmo grau, os mesmos eram limitados a n2 . Isso
terá um custo de O(r(log2 2n2 )2 ) = O(r(log2 2 + log2 n2 )2 ) = O(r(2log2 n)2 ) =
O(4r(log2 n)2 ) = O(r(log2 n)2 ). Assim, terminamos todos os passos para o cálculo do quadrado de um polinômio p em Zn [X]/(X r − 1). O custo total é
O(r2 (log2 n)2 ) + O(r(log2 r)2 ) + O(rlog2 n) + O(r(log2 n)2 ) = O(r2 (log2 n)2 ). Veja
que a parte mais custosa é a primeira, ou seja, o cálculo de p2 em Zn [X]. Isso
quer dizer que o cálculo de p2 em Zn [X]/(X r − 1) é tão custoso quanto o cálculo
de p2 em Zn [X].
Agora que sabemos quanto custa um quadrado de polinômio no quociente
Zn [X]/(X r −1), podemos calcular o custo total da expansão, que terá, no máximo,
log2 n produtos. Multiplicando, o custo da expansão é O(r2 (log2 n)3 ). Porém, para
concluirmos a verificação da identidade, ainda precisamos reduzir o polinômio do
lado direito, no caso em que este tenha grau maior ou igual a r (quando n ≥ r).
Mas isso requer apenas uma divisão de n por r, com custo O((log2 n)2 ). Por fim,
realizamos a comparação, que pode ser feita em tempo polinomial. Então, o custo
total da verificação da identidade é O(r2 (log2 n)3 ) + O((log2 n)2 ) = O(r2 (log2 n)3 ).
Por enquanto, não estamos preocupados com a escolha desse r. Apenas dizemos
que pode ser qualquer inteiro positivo. Se não houver nenhuma relação entre r e
n, por exemplo, se r for apenas uma constante escolhida, o custo é (O(log2 n)3 ),
devido às propriedades da notação assintótica. Veja que a principal diferença,
com relação à identidade anterior, é que o grau dos quadrados não mais cresce
indefinidamente. Pelo contrário, é limitado a r. Isso quer dizer que, mesmo
que r seja bem maior que n, haverá um limite em que a função passa a crescer
polinomialmente.
Assim, este é um teste polinomial interessante de primalidade, não fosse ele
não-determinı́stico! Senão, vejamos. O anel Zn [X]/(X r −1) é o conjunto de todas
as classes residuais do anel Zn [X] reagrupadas módulo o ideal gerado por X r − 1.
Disto, segue que qualquer igualdade em Zn [X] se mantém em Zn [X]/(X r − 1)
([Coutinho, 2004]). Por isso, a identidade (2.10) se verifica para todos os valores
33
de a e r quando p é primo, seguindo diretamente do Lema 2.3.1.
Por outro lado, existem algumas classes distintas em Zn [X] que passam a
compartilhar o mesmo conjunto de classes após aplicada a relação de equivalência
módulo o ideal (X r − 1). Isso quer dizer que, infelizmente, alguns n compostos
também satisfazem (2.10) para alguns valores de r e a, tornando essa identidade
um teste não-determinı́stico. No entanto, Agrawal, Kayal e Saxena conseguiram
mostrar que é possı́vel escolher um r, de modo que, se a identidade for satisfeita
para um certa quantidade de a’s, n é, ao menos, potência de um primo. Antes de
provarmos o teorema principal que mostra essa propriedade, vamos enunciar três
lemas que estão em [Coutinho, 2004] e serão usados em sua prova.
Lema 2.3.2. Sejam a e k inteiros não negativos e p > 1 um primo. Então,
k
k
(X + a)p = X p + a em Zp [X].
Prova. Mais uma vez, usaremos o Princı́pio da Indução Matemática (ver Apêndice A). Se k = 0, a verificação é imediata. Assim, suponha que
k
k
(X + a)p = X p + a em Zp [X]
para algum k ≥ 0. Se a identidade se verificar para k + 1, então a prova estará
completa. Temos que
(X + a)p
k+1
k
k
= ((X + a)p )p = (X p + a)p ,
(2.11)
sendo que a última igualdade é obtida assumindo-se a hipótese de indução.
Do Lema 2.3.1, segue que
(X + a)p = (X p + a).
Como X é uma variável, a identidade deve valer para qualquer valor de X. Então,
k
trocando X por X p , temos:
k
k+1
(X p + a)p = (X p
+ a).
Logo, disto, e de (2.11),
(X + a)p
k+1
k+1
= (X p
+ a).
Lema 2.3.3. Sejam n, r e a inteiros positivos e p > 1 um primo. Se
(X + a)n = X n + a em Zp [X]/(X r − 1),
então
i
i
(X + a)n = X n + a em Zp [X]/(X r − 1) para qualquer i ≥ 0 ∈ Z.
34
Prova. Novamente, vamos usar a Indução Matemática (ver Apêndice A). Se
i = 0, então o resultado é imediato. Suponha que
i
i
(X + a)n = X n + a em Zp [X]/(X r − 1) para algum i ≥ 0.
Pela hipótese inicial do lema,
(X + a)n = X n + a em Zp [X]/(X r − 1).
Então,
(X + a)n = X n + a + q(X)(X r − 1) para algum q(x) ∈ Zp [X].
i
Substituindo X por X n , temos:
i
i+1
(X n + a)n = X n
i
i
+ a + q(X n )(X n r − 1).
i
Mas, como (X r )n = 1 mod(X r − 1), segue que12
i
i
X n r − 1 = (X r )n − 1 = 1 − 1 = 0 em Zp [X]/(X r − 1).
Destas duas últimas igualdades, temos que
i
(X n + a)n = X n
i+1
+ a em Zp [X]/(X r − 1).
(2.12)
Mas, pela hipótese indutiva,
i+1
(X + a)n
i
i
= ((X + a)n )n = (X n + a)n em Zp [X]/(X r − 1).
(2.13)
Assim, de (2.12) e (2.13), temos:
i+1
(X + a)n
= Xn
i+1
+ a em Zp [X]/(X r − 1),
o que finaliza a prova.
Lema 2.3.4. Sejam n, r e a inteiros positivos e p > 1 um primo. Se
(X + a)n = X n + a em Zp [X]/(X r − 1),
então
i j
i j
(X + a)n p = X n p + a em Zp [X]/(X r − 1) para quaisquer i, j ≥ 0 ∈ Z.
Prova. Da hipótese inicial e do Lema 2.3.3, temos que
i
i
(X + a)n = X n + a em Zp [X]/(X r − 1).
Logo,
i j
i
j
i
j
(X + a)n p = ((X + a)n )p = (X n + a)p em Zp [X]/(X r − 1).
12
Qualquer potência de X múltipla de r deixará resto 1 após a divisão por X r − 1.
35
(2.14)
Como toda igualdade em Zp [X] dá lugar a uma igualdade em Zp [X]/(X r − 1),
i
podemos aplicar o Lema 2.3.2 a (2.14), substituindo o X daquele Lema por X n ,
já que X é variável e pode assumir qualquer valor. Assim, obtemos
i j
i
j
i j
(X + a)n p = (X n + a)p = X n p + a em Zp [X]/(X r − 1),
o que completa a prova.
Agora vamos enunciar o teorema principal envolvido no algoritmo. Seguiremos
os passos que estão em [Coutinho, 2004] e, às vezes, em [Bernstein, 2003]. Tratase de uma versão levemente alterada, com algumas diferenças na notação e em que
tentamos esclarecer melhor alguns pontos, atualizada com a modificação proposta
por Lenstra Jr. ([Coutinho, 2004] e [Agrawal et al., 2004]), a qual melhorou a
eficiência do algoritmo em relação à primeira versão, de 2002.
Teorema 2.3.5. Sejam n ≥ 2 ∈ Z, r um primo positivo, S o conjunto {1, 2, ..., r}
e U (r) o grupo abeliano dos elementos inversı́veis de Zr . Se
i) r - n,
ii) n é relativamente primo a cada um dos elementos de S,
√
2db (r−1/d)c
iii) 2r−2
para qualquer inteiro d que divide (r − 1)/v, onde v
≥
n
r
é a ordem de n em U (r) e
iv) a identidade (X + a)n = X n + a, em Zn [X]/(X r − 1), se verificar para todo
a ∈ S,
então n é potência de primo.
Antes de prosseguirmos com a prova, vamos analisar as informações do enunciado do teorema. Primeiro, como r é primo e não divide n, segue que r e n são
coprimos. Assim, n é inversı́vel em Zr e, portanto, está em U (r). Seja p um primo
positivo que divida n. Então, mdc(r, p) = mdc(r, n) = 1, ou seja, p e r também
são coprimos, e p é inversı́vel em Zr . Seja hn, pi o subgrupo de U (r) gerado
por n e p. Assim, podemos construir o grupo quociente U (r)/hn, pi. Para um
maior esclarecimento, veja o Exemplo B.0.4 do Apêndice B, que também possui
uma apresentação do Teorema de Lagrange, fundamental para o entendimento da
prova que se segue.
Prova. A ordem de U (r) (sua quantidade de elementos), que passamos a denotar
por |U (r)|, é igual a r − 1, pois r é primo e, portanto, relativamente primo a todos
os inteiros positivos menores que r. Então, esses r − 1 inteiros são inversı́veis em
Zr , sendo exatamente os elementos de U (r). Seja d a ordem de U (r)/hn, pi, ou
seja, d = |U (r)/hn, pi|. Como13
|U (r)/hn, pi| =
13
Veja Apêndice B.
36
|U (r)|
,
|hn, pi|
então
d=
|U (r)|
(r − 1)
=
=⇒ |hn, pi| = (r − 1)/d.
|hn, pi|
|hn, pi|
(2.15)
Pelo Teorema de Lagrange (Apêndice B), a ordem v de n em U (r), ou seja, o
menor inteiro v tal que nv = 1, divide a ordem de hn, pi, que, de (2.15), é igual a
(r − 1)/d. Mas se v divide (r − 1)/d, existe algum c inteiro tal que cv = (r − 1)/d.
Assim, cvd = (r − 1). Como a ordem v também divide |U (r)| = (r − 1), podemos
escrever cd = (r − 1)/v. Logo, d divide (r − 1)/v, conforme a hipótese iii do
teorema.
Suponha m1 , m2 , ..., md as d classes do grupo quociente U (r)/hn, pi. Seja h
um fator irredutı́vel de X r − 1 em Zp [X]. Como Zp é um corpo finito, o anel
quociente K = Zp [X]/(h) é um corpo finito. Seja G o subgrupo de K ∗ gerado
pelos elementos X mi + a, onde 1 ≤ i ≤ d e a ∈ S.
Suponha a seguinte cota inferior para a ordem do subgrupo G:
√
|G| ≥ n2b (r−1)/dc .
(2.16)
A prova dessa cota será feita posteriormente, nos moldes de [Coutinho, 2004], e é
devida a um resultado provado por Lenstra Jr. p
([Agrawal et al., 2004]).
Considere os produtos ni pj , para 0 ≤ i, j ≤ b (r − 1)/dc. Como p ≤ n, então
ni pj ≤ ni nj ≤ n2ij . Podemos, assim, reescrever essa desigualdade como:
√
(2.17)
1 ≤ ni pj ≤ n2b (r−1)/dc .
Cada classe ni pj de Zr pertence ao subgrupo hn, pi de U (r), pois esse subgrupo
é formado justamente pela multiplicação de todas as potências14 de n e p em U (r).
Mas, de (2.15), o subgrupo hn, pi tem ordem (r−1)/d e, portanto, não há mais
que (r −1)/d elementos
ni pj em Zr . Como i e j variam
p a partir
pdistintos na forma
2
de 0, há um total de (b (r − 1)/dc+1) pares (i, j), com 0 ≤ i, j ≤ b (r − 1)/dc.
Como isso é maior que (r − 1)/d, as classes ni pj não podem ser todas distintas
em Zr . Desta forma, existem pares
(k1 , l1 ) 6= (k2 , l2 ) tais que nk1 pl1 = nk2 pl2 (mod r).
(2.18)
Sejam u = nk1 pl1 e v = nk2 pl2 . Suponha que v ≥ u. Como u = v (mod r),
então v = u + qr para algum q inteiro positivo. Assim,
X v = X u+qr = X u (X r )q .
Como (X r )q deixa resto 1 após a divisão por (X r − 1), segue que
X v = X u+qr = X u (X r )q = X u · 1 = X u em Zp [X]/(X r − 1).
Mas, como vimos antes,
14
Lembrando que as potências das classes se repetem a partir do expoente igual a sua ordem
em U (r).
37
X v = X u em Zp [X]/(X r − 1)
equivale a
∃ q(X) tal que X v = X u + q(X)(X r − 1) em Zp [X].
(2.19)
Como h(X) | (X r − 1), então
∃ s(X) tal que (X r − 1) = s(X)h(X).
Substituindo isso em (2.19), temos:
∃ q(X) tal que X v = X u + q(X)s(X)h(X) em Zp [X],
o que significa que a congruência se mantém no corpo K, ou seja,
X v = X u em K = Zp [X]/(h).
(2.20)
Porém, da hipótese iv do teorema,
(X + a)n = X n + a em Zn [X]/(X r − 1)
para cada a ∈ S, o que equivale a
∃ t(X) tal que (X + a)n = X n + a + s(X)n (mod X r − 1)
para cada a ∈ S. Da mesma forma que fizemos anteriormente, se p divide n,
então s(X) é múltiplo de p também. Assim,
(X + a)n = X n + a em Zp [X]/(X r − 1)
para cada a ∈ S. Então, pelo Lema 2.3.4,
i j
i j
(X + a)n p = X n p + a em Zp [X]/(X r − 1) para quaisquer i, j ≥ 0 ∈ Z
e para cada a ∈ S. Como qualquer igualdade em Zp [X]/(X r − 1) se mantém em
Zp [X]/(h), segue que
i j
i j
(X + a)n p = X n p + a em Zp [X]/(h) para quaisquer i, j ≥ 0 ∈ Z
e para cada a ∈ S.
Mas esta última igualdade vale para quaisquer que sejam os expoentes i, j
inteiros. Então, podemos escrever:
k1 pl1
= Xn
k2 pl2
= Xn
(X + a)u = (X + a)n
k1 pl1
+ a = X u + a em Zp [X]/(h)
(2.21)
k2 pl2
+ a = X v + a em Zp [X]/(h)
(2.22)
para cada a ∈ S e
(X + a)v = (X + a)n
para cada a ∈ S.
38
De (2.20), (2.21) e (2.22), temos que
(X + a)u = (X + a)v em Zp [X]/(h)
para cada a ∈ S.
Como X é uma variável, podemos substituı́-la por qualquer valor mantendo
a identidade. Vamos substituir X por X mi , em que mi é, como já apresentado
anteriormente, uma das d classes do grupo quociente U (r)/hn, pi (i pode variar
de 1 a d):
(X mi + a)u = (X mi + a)v em Zp [X]/(h)
para cada a ∈ S.
Assim, os produtórios de cada membro da identidade, quando se varia a em
S, devem permanecer iguais. Ou seja,
Y
Y
(X mi + a)u =
(X mi + a)v em K = Zp [X]/(h).
a∈S
a∈S
Como esses produtos geram o subgrupo G de K ∗ , conforme apresentado anteriormente, temos que g v =g u em K para todo g ∈ G. Isso implica que todos os
elementos de G, (e também 0) são soluções da equação polinomial Y v − Y u em
K. Mas (2.17) implica em:
√
(r−1)/dc
√
√
=⇒ 0 ≤ u − 1 ≤ n2b (r−1)/dc =⇒ 0 ≤ u ≤ n2b (r−1)/dc + 1
(r−1)/dc
√
√
=⇒ 0 ≤ v − 1 ≤ n2b (r−1)/dc =⇒ 0 ≤ v ≤ n2b (r−1)/dc + 1.
1 ≤ u ≤ n2b
e
√
1 ≤ v ≤ n2b
Como estamos
√ supondo que v ≥ u, então a diferença (v − u) é no mı́nimo 0 e
2b (r−1)/dc
no máximo n
+ 1, neste último caso, quando u é mı́nimo e v é máximo.
Então, podemos escrever que
√
(v − u) + 1 ≤ n2b (r−1)/dc ,
e, combinando isso com a cota inferior para |G|, em (2.16), temos
|G| ≥ (v − u) + 1.
Mas isso significa que o polinômio Y v −Y u admite mais que (v −u)+1 soluções
em K, considerando a solução nula. Mas esse polinômio não pode admitir mais
que (v − u) + 1 soluções em um corpo se v > u. Para ilustrar esse fato, imagine
v = 2 e u = 1. Neste caso, (v − u) + 1 = 2. Admitir mais soluções que isso seria
admitir pelo menos 3 soluções. Mas o polinômio Y v − Y u = Y 2 − Y admite, no
máximo, 2 soluções num corpo, já que seu grau é 2. Logo, se v ≥ u e v ≯ u, só
resta a opção v = u. Assim,
nk1 pl1 = u = v = nk2 pl2 .
39
Agora vamos analisar esses expoentes. Se k1 = k2, então pl1 = pl2 , do que
segue que l1 = l2 . Mas isso significa que (k1 , l1 ) = (k2 , l2 ), o que é absurdo, pois
admitimos que esses pares eram distintos. Logo, k1 6= k2 . Só que isso significa
que
nk1 −k2 = pl1 −l2 .
Temos que uma potência de n é potência de p. Mas, como as potências de
p só possuem p como fator primo, segue que a potência de n também só pode
possuir p como fator primo. Disto, ou n é p ou é potência de p, como querı́amos
mostrar.
Note que a demonstração do Teorema 2.3.5 depende da prova de que |G| ≥
√
(r−1)/dc
n
]. Como já citamos anteriormente, essa prova se deve a um resultado
obtido por Lenstra Jr. ([Agrawal et al., 2004]). Vamos agora provar essa cota,
seguindo, novamente, os passos de [Coutinho, 2004].
√
Lema 2.3.6. A ordem do subgrupo G é maior ou igual a n2b (r−1)/dc .
2b
Prova. Sejam f1 (X), f2 (X), f3 (X), ..., fj (X) funções fk : S → N, ou seja, funções
que recebem os elementos de S como entrada e nos dão o resultado como um
número natural. Vamos associar cada função fk à d-upla
Dk = (pk (X m1 ), pk (X m2 ), ..., pk (X md )), no corpo K = Zp [X]/(h),
onde pk é o polinômio
pk (X) =
Y
(X + a)fk (a) .
a∈S
Portanto, os elementos de cada Dk são elementos de G, conforme a construção
daquele subgrupo usada na prova do Teorema 2.3.5. Mas esses elementos podem
se repetir em cada d-upla e, além disso, sua ordem importa. Assim, temos permutações, com repetição permitida, de d elementos de G e, da Análise Combinatória,
isso significa que temos
|G|d
(2.23)
d-uplas distintas.
Podemos mostrar, ainda, que determinadas funções distintas sempre serão
associadas a d-uplas distintas. Sejam f1 e f2 . Se as respectivas d-uplas associadas,
D1 e D2 , forem iguais, então
p1 (X mi ) = p2 (X mi ) em K para todo 1 ≤ i ≤ d.
b c
(2.24)
Sejam b e c dois naturais. Desenvolvendo p1 (X mi )n p em Zp [X]/(X r − 1),
temos:
Y
b c
b c
p1 (X mi )n p =
((X mi + a)f1 (a) )n p =
a∈S
40
=
Y
b pc
(X mi + a)f1 (a)n
=
a∈S
Y
((X mi + a)n
b pc
)f1 (a) .
(2.25)
a∈S
Mas, da hipótese iv do Teorema 2.3.5, (X + a)n = X n + a em Zn [X]/(X r − 1)
para todo a ∈ S. Como p divide n, temos também que
(X + a)n = X n + a em Zp [X]/(X r − 1)
para todo a ∈ S. Então, podemos aplicar o Lema 2.3.4 em (2.25), obtendo
b pc
p1 (X mi )n
=
Y
b pc
(X mi n
b pc
+ a)f1 (a) = p1 (X mi n
) em Zp [X]/(X r − 1).
a∈S
E, como h divide X r − 1, essa igualdade também vale em K = Zp [X]/(h). Disto,
e de (2.24), segue que
b pc
p1 (X mi n
) = p2 (X mi n
b pc
) em K, ∀ 1 ≤ i ≤ d.
(2.26)
Façamos g(X) = p1 (X) − p2 (X). Como U (r) possui r − 1 classes distintas dos
elementos inversı́veis módulo r e o quociente U (r)/hn, pi possui d classes distintas,
representadas por m1 , ..., md e geradas pelos elementos de U (r) módulo potências
de n e p, então, para qualquer inteiro 1 ≤ e ≤ r − 1, existem b, c ∈ N tais que
e = mi nb pc (mod r). Mas isso significa que e = mi nb pc + tr para algum t inteiro.
Assim,
b c
b c
g(X e ) = g(X mi n p (X r )t ) = g(X mi n p ) em Zp /(X r − 1),
já que X r = 1 em Zp /(X r − 1). Como h(X) divide o polinômio X r − 1, então
b pc
g(X e ) = g(X mi n
b pc
(X r )t ) = g(X mi n
) em Zp [X]/(h).
Disto, e de (2.26), segue que
g(X e ) = 0 em Zp [X]/(h) = K.
(2.27)
Seja o conjunto R = {X e | 1 ≤ e ≤ r − 1} ⊆ K. Como X r = 1 em K, por ser
primo, r é o primeiro expoente w > 0 de X tal que X w = 1 em K. Portanto, r
é a ordem de X em K ∗ e todos os elementos de R são distintos. Mas, de (2.27),
cada elemento de R é raiz de g(X) em K. Então, por admitir r − 1 raı́zes, g(X)
é um polinômio de, no mı́nimo, grau igual a r − 1 no corpo Zp (X). Assim, se
X
X
f1 (a) < r − 1 e
f2 (a) < r − 1,
(2.28)
a∈S
a∈S
então p1 (X) = p2 (X) em Zp [X]. Mas, da hipótese ii do Teorema 2.3.5, temos
que n é relativamente primo a cada um dos elementos de S. Isso quer dizer que
mdc(n, a − a0 ) = 1 para quaisquer a e a0 distintos no intervalo [1, r]. Disto, segue
que (X + a) − (X + a0 ) = a − a0 6= 0 em Zp [X].
Logo, os polinômios X + a e X + a0 são distintos e irredutı́veis em Zp [X],
para distintos a e a0 de S. Portanto, como a fatoração de polinômios é única,
segue que p1 (X) = p2 (X), considerando a hipótese (2.28), somente se as funções
41
f1 e f2 forem iguais. Assim, o conjunto das d-uplas distintas Dk , de elementos
de G, tem pelo menos tantos elementos quantas são as funções fk para as quais
P
a∈S fk (a) < r − 1. Mas, como essas funções têm o conjunto dos naturais como
contradomı́nio, precisamos saber quantas são as somas de naturais que resultam
num valor menor que r − 1. Somente os naturais até r − 2 podem ser incluı́dos
nestas somas, pois fica claro que uma soma do natural r − 1 com qualquer outro
não é menor que r − 1. Também, a ordem em que esses naturais são somados
não importa. Desta forma, temos uma combinação de r elementos (a ordem
de S, que é o domı́nio das funções) de um total de r − 1 (os naturais de 0 a
r − 2), com repetição permitida. Da Análise Combinatória, isso significa que
temos exatamente
(r − 1) + r − 1
2r − 2
=
r
r
possibilidades. Então, disto, e de (2.23), temos:
2r − 2
d
|G| ≥
.
r
E, da hipótese iii do Teorema 2.3.5, segue que
√
2r − 2
d
|G| ≥
≥ n2db (r−1)/dc
r
e, portanto,
√
|G| ≥ n2b
(r−1)/dc
.
Na subseção seguinte, apresentaremos uma versão modificada do AKS a partir
da versão que está em [Coutinho, 2004], que, por sua vez, é uma variação do que
está em [Bernstein, 2003], a qual formalizaremos no assistente de prova Coq.
42
2.3.2
O Pseudocódigo e a Correção
Algoritmo 2 AKS
Entrada: n > 1 inteiro
{*Número a ser testado*}
Saı́da: primo ou composto
ETAPA 1:
se n é potência de algum k > 1 ∈ Z, com expoente maior que 1, então
imprima composto e pare.
ETAPA 2:
2
calcule N = 2n(n − 1)(n2 − 1)(n3 − 1)...(n4dlog2 ne − 1) e determine o menor
primo r que não divide N .
ETAPA 3:
se n é divisı́vel por algum primo q < r, então
se q = n então
imprima primo e pare
senão
imprima composto e pare.
ETAPA 4:
se (X + a)n = X n + a no anel Zn [X]/(X r − 1) para todo a ∈ S = {1, 2, ..., r},
então
imprima primo e pare
senão
imprima composto e pare.
Veja que se trata de um algoritmo que possui quatro etapas bem distintas.
A Etapa 4 faz a verificação da identidade (2.10) mas, antes, o algoritmo realiza
algumas operações para atender às propriedades do Teorema 2.3.5. Demonstraremos, agora, a correção do algoritmo, provando que realmente se trata de um
algoritmo determinı́stico.
Teorema 2.3.7. A saı́da do algoritmo AKS é primo se, e somente se, n for
primo.
Prova. =⇒ Se n for primo, o algoritmo deve parar com a resposta primo na
Etapa 3 ou na Etapa 4. Por ser primo, n não pode ser potência de nenhum
inteiro maior que 1. Assim, o algoritmo passará pela Etapa 1. Na Etapa 2, será
determinado o menor r primo que não divide o N calculado. Se o r encontrado
for maior que n, então haverá um primo q menor que r que divide n: o próprio
n, e o algoritmo parará na Etapa 3 com a resposta primo. Caso r não seja maior
que n, então a Etapa 4 será executada. Como n é primo, pelo Lema 2.3.1, temos
que (X + a)n = X n + a em Zn [X]. Como toda igualdade em Zn [X] se mantém em
Zn [X]/(X r − 1), a identidade da Etapa 4 se verificará para quaisquer que sejam
a e r, e o algoritmo retornará primo na saı́da.
⇐= Se n for composto, o algoritmo deve parar com a resposta composto na
Etapa 1, ou na Etapa 2 ou na Etapa 4. Se n for potência, de expoente maior
que 1, de algum inteiro, então o algoritmo parará na Etapa 1 com a resposta
composto. Se o algoritmo chegar à Etapa 2, então n não é potência de inteiro,
43
com expoente maior que 1. Não há parada na Etapa 2, então o algoritmo chegará
à Etapa 3, após ter calculado N e encontrado r. Se houver algum q menor que r
que divida n, então o algoritmo parará na Etapa 3 com a resposta composto. Por
outro lado, se não houver, perceba que, na Etapa 2, N é múltiplo de n e de 2.
Portanto, o primo r encontrado também não divide n e nem 2. Logo, 2 < r 6= n.
Disto, segue que, se o algoritmo passar pela Etapa 3, 2 < r < n.
Assim, considerando n composto, se o algoritmo chegar à Etapa 4, teremos
um r primo que não divide n, conforme hipótese i do Teorema 2.3.5, e teremos que
n não possui divisores menores ou iguais a r, o que implica mdc(n, a) = 1 para
todo a ∈ S = {1, 2, ..., r}, conforme a hipótese ii do Teorema 2.3.5. Se tivermos
como comprovar que a hipótese iii também estará satisfeita, poderemos aplicar
o Teorema 2.3.5 à congruência da Etapa 4. Mas r não divide N . Então, r não
divide nenhum dos fatores (nk − 1), com 1 ≤ k ≤ 4dlog2 ne2 daquele produto.
Disto, segue que
nk 6= 1 (mod r) para todo 1 ≤ k ≤ 4dlog2 ne2 .
Assim, a ordem v de n em U (r), ou seja, o primeiro expoente positivo k que
torna nk = 1 (mod r), tem que ser maior que 4dlog2 ne2 . Lembrando que, pelo
Teorema de Lagrange (ver Apêndice B), a ordem v de n em U (r) divide a ordem
de U (r) = r − 1, suponha um inteiro positivo d que divida (r − 1)/v. Portanto,
d≤
(r − 1)
(r − 1)
(r − 1)
<
≤
,
2
v
4dlog2 ne
4(log2 n)2
(2.29)
já que dlog2 ne representa o menor dos inteiros maiores ou iguais a log2 n.
Por outro lado,
r
r
r
p
(r − 1)
(r − 1)
(r − 1)
c ≤ 2d
= 2 d2 ·
= 2 d(r − 1).
2db
(2.30)
d
d
d
Mas, de (2.29), temos:
s
p
(r − 1)
(r − 1)
(r − 1)(r − 1)
2 d(r − 1) ≤ 2
=2·
=
.
2
4(log2 n)
2(log2 n)
log2 n
Disto, e de (2.30), segue que
r
2db
(r − 1)
(r − 1)
c≤
.
d
log2 n
Então,
√
n
Como
2db
(r−1)/dc
≤ n(r−1)/log2 n =
√
log2 n
n(r−1) = 2r−1 .
n
n!
n(n − 1)(n − 2)...(n − k + 1)
=
=
k
k!(n − k)!
k(k − 1)(k − 2)1
44
(2.31)
para quaisquer n ≥ k ≥ 0 inteiros, temos:
2r − 2
(2r − 2) (2r − 3) (2r − 4)
r−1
r
(r + 1)
=
···
=
r
r
r−1 r−2
r − (r − 3) r − (r − 2) 1
=
r−1
Y
2r − j
j=2
r−j
.
(2.32)
Mas
2r − j
j
=2+
.
r−j
r−j
E como
2+
j
j
> 2, quando 0 < j < r, e 2 +
≥ 3, quando 0 < dr/2e ≤ j,
r−j
r−j
para r ≥ 4, dos r − 2 fatores do produto (2.32), teremos pelo menos br/2c ≥ 2
maiores ou iguais a 3. Os restantes serão maiores ou iguais a 2. Mas o produto de
dois fatores maiores ou iguais a 3 é maior que o produto de três fatores maiores
ou iguais a 2, pois 32 ≥ 23 . Isso quer dizer que, para r ≥ 4, o produto (2.32), de
r − 2 fatores, é maior ou igual ao produto de r − 1 fatores iguais a 2, pois temos,
ao menos, 2 fatores maiores que 2 e o restante igual ou maior que 2. Ou seja,
Y
r−1
2r − 2
2r − j
=
≥ 2r−1
r
r−j
j=2
4
para r ≥ 4. E se r = 3, então 2r−2
= 3 = 4 = 2r−1 . Assim, e de (2.31),
r
Y
r−1
2r − 2
2r − j
≥ 2r−1 ≥
=
r
−
j
r
j=2
√
log2 n
n(r−1) = 2r−1
para r ≥ 3. E, já que necessariamente r > 2 se o algoritmo atinge a Etapa 4,
conforme dito anteriormente, temos que a hipótese iii do Teorema 2.3.5 também
se verifica.
Como todas as hipóteses do Teorema 2.3.5 estão confirmadas na execução da
Etapa 4, podemos aplicá-lo com segurança na identidade daquela etapa. Como n é
composto, a igualdade só poderá se verificar para todo a ∈ S se n for uma potência
de primo. Mas essa condição foi “filtrada” pela Etapa 1. Logo, a identidade da
Etapa 4 não se verificará para algum a ∈ S e o algoritmo parará com a saı́da
composto.
2.3.3
A Complexidade Polinomial
Nesta subseção iremos mostrar que o AKS realmente é um algoritmo de resposta em tempo polinomial. Mas, para que isso seja verdade, cada uma de suas
etapas deve ter custo polinomial. Então, analisaremos etapa por etapa.
45
Custo da Etapa 1
Nessa etapa, será verificado se existe um inteiro a > 1 e um expoente k >
1 tais que ak = n. Existem alguns algoritmos capazes de realizar essa tarefa
em tempo polinomial. Mostraremos uma técnica simples, que consiste em uma
recursão de k após a inversão do expoente em n e após a mudança de base para
2 ([Santos et al., 2002]). Se
ak = n,
então
n1/k = a =⇒ (2log2 n )1/k = a =⇒ 2(log2 n)/k = a.
Assim, podemos fazer uma recursão em k, calculando potências de 2. Se,
em uma dessas recursões, acharmos um inteiro a, então n é potência de a. A
representação de n em logaritmo base 2 é imediata em computação binária e
não representa qualquer custo adicional. Com essa nova representação, podemos
rapidamente verificar que k está limitado a log2 n. De fato, o expoente k é máximo
quando a é mı́nimo ([Coutinho, 2004]), ou seja, 2. Assim, o maior k só pode ser
log2 n. Então, faremos, no máximo, log2 n divisões de números limitados a log2 n.
Portanto, cada divisão terá custo de O((log2 (log2 n))2 ) e o custo total das divisões
será O(log2 n(log2 (log2 n))2 ). Como log2 n < n, podemos fazer uma concessão pela
notação O para simplificar um pouco esse custo, já que nosso objetivo é apenas
mostrar que se trata de um algoritmo polinomial. Então, consideremos que esses
passos nos dão um custo O((log2 n(log2 n)2 ) = O((log2 n)3 ).
Mas, para cada divisão realizada, temos que calcular uma potência de 2. Potências de 2 são multiplicações sucessivas por 2 que, em binário, têm custo constante, pois tratam-se de apenas deslocar um bit para a esquerda. Então, uma
multiplicação por 2 é desprezı́vel para a notação O. No entanto, o expoente das
potências depende do valor n da entrada e, quanto maior, maior será a quantidade
de multiplicações. Precisamos saber, então, quantas multiplicações por 2 iremos
fazer. No pior caso, quando n é potência de 2 e k = log2 n, faremos, justamente,
log2 n multiplicações por 2. Então, podemos concluir nossa estimativa do custo da
primeira etapa como O(log2 n(log2 n)3 ) = O((log2 n)4 ) que, como sabemos, trata-se
de um custo polinomial.
Custo da Etapa 2
Nessa etapa, primeiro vamos analisar o custo do cálculo de N . Para cal2
cular a maior das potências, n4dlog2 ne , são feitas multiplicações entre números
2
limitados a n4dlog2 ne . Uma multiplicação entre números dessa ordem tem custo
2
O((log2 (n4(log2 n) ))2 ) = O((4(log2 n)2 log2 n)2 ) = O((log2 n)6 ). Como são, no máximo, 4dlog2 ne2 multiplicações, então o custo total para o cálculo da maior das
potências é O(4(log2 n)2 (log2 n)6 ) = O((log2 n)8 ). Após o cálculo da potência, há,
ainda, a subtração de uma unidade. O custo da subtração para números até
2
2
n4dlog2 ne é O(log2 (n4(log2 n) )) = O((log2 n)3 ). Assim, o custo total para o cálculo
do maior termo de N é O((log2 n)8 ) + O((log2 n)3 ) = O((log2 n)8 ). Mas ainda
temos que calcular os outros termos, com potências de expoentes menores que
4dlog2 ne2 . Considerando que são, no máximo, 4dlog2 ne2 , o custo total para calcular todos os termos é limitado a O(4(log2 n)2 (log2 n)8 ) = O((log2 n)10 ). Veja que
46
fomos bem generosos com a notação O, pois os expoentes das outras potências
são menores que 4dlog2 ne2 , começando em 1. Além, disso, poderı́amos ter usado
a multiplicação de quadrados, como fizemos na análise do custo da identidade
2
(2.10), para calcular n4dlog2 ne , o que diminuiria o custo de N .
Ainda resta multiplicar todos os termos, inclusive 2n, para o cálculo final
de N . São da ordem de 4dlog2 ne2 multiplicações a um custo de, no máximo,
2
O((log2 (n4(log2 n) ))2 ) = O((4(log2 n)2 log2 n)2 ) = O((log2 n)6 ) cada uma, considerando o valor máximo envolvido nas operações. O custo total das multiplicações
será, então,
O(4(log2 n)2 (log2 n)6 ) = O((log2 n)8 ).
Assim, o custo final para a computação de N será O((log2 n)10 ) + O((log2 n)8 ) =
O((log2 n)10 ), que é polinomial. Mas, nessa etapa, ainda temos que computar o
menor primo r que não divide N .
Temos que
2
2
2n(n − 1)(n2 − 1)...(n4dlog2 ne − 1) ≤ 2n · n · n2 · · · n4dlog2 ne .
Então, como dlog2 ne2 ≤ (log2 n + 1)2 ,
2
N ≤ 2n · n · n2 · · · n4(log2 n+1) = 2n · nj ,
onde
4(log2 n+1)2
j=
X
nk .
k=1
Mas, como o somatório dos c primeiros naturais é c(c + 1)/2, temos:
4(log2 n + 1)2 (4(log2 n + 1)2 + 1)
j=
= 8(log2 n + 1)4 + 2(log2 n + 1)2 .
2
Então,
4
2
N ≤ 2n · n8(log2 n+1) +2(log2 n+1)
4
2
≤ 2n8(log2 n+1) +2(log2 n+1) +1 .
Aplicando logaritmo base 2 em ambos os lados, temos:
log2 N ≤ 1 + (8(log2 n + 1)4 + 2(log2 n + 1)2 + 1)log2 n.
Somando 1 apenas ao lado direito:
log2 N < 2 + (8(log2 n + 1)4 + 2(log2 n + 1)2 + 1)log2 n.
Então, existe um inteiro positivo m tal que
log2 N < m ≤ 2 + (8(log2 n + 1)4 + 2(log2 n + 1)2 + 1)log2 n.
E disto, segue que
4 +2(log n+1)2 +1
2
N < 2m ≤ 4n8(log2 n+1)
47
.
Mas o Corolário 2.2.8 nos garante que o produto de todos os primos positivos
menores ou iguais a 2m2 é maior ou igual a 2m . Então, como N < 2m não pode
ser múltiplo de todos os primos menores ou iguais a 2m2 , r será, no máximo, igual
a 2m2 . Assim, se
m ≤ 2 + (8(log2 n + 1)4 + 2(log2 n + 1)2 + 1)log2 n,
então
r ≤ 2m2 ≤ 2(2 + (8(log2 n + 1)4 + 2(log2 n + 1)2 + 1)log2 n)2 .
Podemos, agora, usar a notação O para estimar r, ou seja, seu crescimento
à medida que n cresce, e simplificar nossos cálculos adiante. Vemos que o maior
grau do polinômio em log2 n da desigualdade acima é 10, pois temos que realizar
a distributiva da multiplicação antes de elevarmos ao quadrado. Então, nossa
estimativa para r é O((log2 n)10 ).
Uma forma simples para encontrar r é testar a divisão de N por cada primo,
a partir de 2, até encontrarmos o primeiro que não divide N . Para isso, podemos
usar o Crivo de Eratóstenes para descobrirmos todos os primos de 2 a r. Na
Subseção 2.2.3, fizemos uma estimativa para o custo do crivo em O(n2 ) e afirmamos realmente se tratar de um teste de primalidade exponencial. Mas como
poderemos usá-lo em uma das etapas do algoritmo AKS se queremos justamente
provar que este se trata de um algoritmo em tempo polinomial? A resposta para
isso é que, apesar de o crivo ter custo exponencial em relação à entrada, não o
aplicaremos diretamente à nossa entrada n e, sim, a r, que tem uma estimativa
polinomial. Assim, teremos um custo de O(r2 ) = O((log2 n)10 )2 ) = O((log2 n)20 ).
Por sua vez, cada divisão de N pelos primos até r terá custo
4
2
O((log2 N )2 ) = O((log2 (2n8(log2 n+1) +2(log2 n+1) +1 ))2 )
= O((8(log2 n + 1)4 + 2(log2 n + 1)2 + 1)log2 2n)2 ) = O((log2 n)10 ).
Como são, no máximo, r divisões15 , então o custo total de todas as divisões será
O((log2 n)10 )(log2 n)10 ) = O((log2 n)20 ).
Desta forma, verificamos que os últimos dois passos superam a computação
do cálculo de N e, assim, são os determinantes para o custo dessa etapa, o qual é
O((log2 n)20 ), portanto, polinomial. É possı́vel usarmos um método mais eficiente
que o Crivo de Eratóstenes para encontrarmos r, desde que não seja probabilı́stico. Caso seja, não teremos a certeza de que o r encontrado realmente é primo,
tornando o AKS probabilı́stico. Além disso, poderı́amos ter usado, para r, a estimativa de Tschebycheff, que está citada na Subseção 2.2.2. Essas duas medidas
poderiam ter diminuı́do, e muito, nossa estimativa de custo da Etapa 2.
Custo da Etapa 3
Um método simples é usar, nessa etapa, o Crivo de Eratóstenes de forma
semelhante ao que fizemos na etapa anterior: achar todos os primos q menores que
r e, à medida que encontramos esses primos, iremos testar se dividem n. Assim,
15
Aqui, fizemos uma concessão muito grande, já que nem todos os inteiros até r são primos.
48
novamente o custo do crivo aplicado a r será O(((log2 n)10 )2 ) = O((log2 n)20 ), que
é polinomial.
Mas também teremos que realizar uma divisão de r para cada q encontrado.
O custo de uma divisão de r por um inteiro menor é
O((log2 r)2 ) = O((log2 (log2 n)10 )2 ) = O((10log2 log2 n)2 ).
Concedendo r divisões, o custo total será O(((log2 n)10 )(10log2 log2 n)2 ), que é
menor que o custo de apenas aplicar o crivo. Então, o custo total dessa etapa está
limitado pelo custo de se aplicar o Crivo de Eratóstenes a r, que é O((log2 n)20 ).
Custo da Etapa 4
Na Etapa 4 iremos verificar a identidade
(X + a)n = X n + a no anel Zn [X]/(X r − 1)
para todo a ∈ S = {1, 2, ..., r}. Já vimos, no inı́cio desta seção, que o custo para
computar a identidade é O(r2 (log2 n)3 ). Naquela ocasião, r ainda não estava em
função de n. Podia ser visto como apenas uma constante, sem influência na notação assintótica. Mas a identidade representava um teste não-determinı́stico de
primalidade, diferente de agora. Com um custo adicional para computar um r
apropriado, e mais alguns ajustes, a identidade passou a ser um teste determinı́stico. Assim, precisamos substituir r por seu custo, calculado quando analisamos
a Etapa 2. Além disso, a identidade será verificada uma vez para cada a ∈ S, ou
seja, r vezes. O custo para essa etapa será, então,
O(r(r2 (log2 n)3 ) = O(r3 (log2 n)3 ) = O((((log2 n)10 )3 )(log2 n)3 ) =
= O(((log2 n)30 )(log2 n)3 ) = O((log2 n)33 ),
que, apesar de alto, representa um custo polinomial.
De todas as etapas, a mais custosa é a Etapa 4. Só por ela, podemos estimar o custo total do algoritmo AKS em O((log2 n)33 ), ou seja, realmente se trata
de um algoritmo em tempo polinomial. Seja b ≈ (log2 n) o número de bits da
entrada. Como vimos na Seção 2.2.3, b é o melhor parâmetro para ser usado
como tamanho da entrada em testes de primalidade. Desta forma, o custo que
estimamos para o algoritmo AKS é O(b33 ), ou seja, de custo limitado superiormente por um polinômio de grau 33 em relação a quantidade de bits da entrada.
Há, todavia, estimativas bem melhores que a nossa para o custo do algoritmo,
já que fizemos várias concessões, entre elas a utilização de métodos não muito
eficazes em algumas etapas. Em [Agrawal et al., 2004], a estimativa é melhor que
((O(log2 n)(15/2)+1 ).
É evidente que, se comparado aos testes determinı́sticos anteriores a ele, o
algoritmo AKS é mais eficiente16 , já que nenhum responde em tempo polinomial.
Mas, se comparado aos testes probabilı́sticos em tempo polinomial mais usados,
o algoritmo AKS possui um custo mais alto devido, principalmente, à Etapa 4.
16
Estamos nos referindo, como fizemos a todo momento neste trabalho, à eficiência temporal,
ou seja, o tempo de resposta em relação ao tamanho da entrada.
49
Por isso, os testes probabilı́sticos de primalidade continuam a ser os mais usados
atualmente ([Coutinho, 2004]). Ainda que se tenha uma resposta absolutamente
correta em tempo polinomial, é preferı́vel uma resposta ainda mais rápida, com
possibilidade de erro desprezı́vel, sem falar em outros critérios, como dificuldades
de implementação e eficiência espacial17 .
De qualquer forma, o AKS resolveu um problema teórico antigo importante,
ao mostrar que a primalidade está na classe P. Isso pode ter aberto um caminho para se entender melhor a primalidade e, também, para se resolverem outros
problemas computacionais semelhantes. Para isso, um correto entendimento do
algoritmo AKS é fundamental, começando pelos passos algébricos mı́nimos envolvidos. Uma forma de ajudar a trilhar esse caminho seria uma formalização do
algoritmo utilizando um assistente de prova. Veremos o que é um assistente de
prova, sobretudo o Coq, no capı́tulo seguinte.
17
A eficiência espacial se refere ao crescimento do uso da memória em relação ao tamanho da
entrada e pode ser crucial para a eficiência total de um algoritmo ([Cormen et al., 2001]).
50
Capı́tulo 3
Assistentes de Prova
3.1
Verificação Formal e Assistentes de Prova
Há algumas décadas, havia a idéia de que os computadores poderiam substituir os matemáticos, podendo provar teoremas sozinhos. Até os dias atuais,
no entanto, os computadores mostraram-se limitados nessa tarefa e tal idéia foi
abandonada ([Friedman, 2006]). De fato, os resultados de indecidibilidade deixam
claro que um tal nı́vel de automatização é impossı́vel. No entanto, computadores
vêm sendo utilizados com bastante sucesso na construção de provas, por meio de
um processo semi-automático em que o usuário direciona os caminhos a serem
seguidos pelo computador.
A verificação formal de software e hardware torna-se importante devido à utilização destes em um número cada vez maior de atividades humanas, que vão
desde matrı́culas em disciplinas da universidade até transações bancárias e gerenciamento de tráfego aéreo. Desta forma, a verificação formal das teorias matemáticas, utilizadas diretamente ou indiretamente por tais sistemas, é fundamental,
de forma que a verificação matemática e a verificação de programas cada vez mais
se tornam relacionadas e próximas.
Apesar de haver um consenso de que nem tudo na Matemática pode ser formalizado, em [Friedman, 2006] temos alguns motivos importantes para se continuar
a formalização e verificação de teorias matemáticas, além do que indicamos acima:
• Benefı́cios para o estudo da Teoria de Prova, da lógica matemática. Há
a idéia de que a estrutura das provas pode trazer mais benefı́cios do que
a prova em si. Por exemplo, teoremas distintos podem ter uma mesma
estrutura de prova que guarda algo em comum entre eles. Isso pode vir
a ser utilizado para classificar as provas de acordo com sua formalização.
No caso especı́fico do algoritmo AKS, uma formalização pode revelar passos
algébricos e classes de teoremas que podem estar relacionados com teoremas
usados nos demais testes de primalidade ou em outros campos. Isso pode
ajudar no desenvolvimento de novos teoremas e testes de primalidade mais
eficientes.
• A certeza: como uma reivindicação filosófica dos fundamentos da Matemática, temos que saber se algo está ou não está realmente provado, encerrando
51
disputas acerca disso, ainda que em uma situação mais rara. Um exemplo
pode estar no chamado Último Teorema de Fermat. Por volta de 1637,
Fermat anunciou que possuı́a uma prova “maravilhosa” de que a equação
X n + Y n = Z n não possui soluções inteiras não nulas para quando n > 2,
mas que a margem do livro era “muito estreita para escrevê-la”. Esse teorema ficou sem demonstração por séculos, após tentativas fracassadas de
vários matemáticos. Até que em 1994, Wiles anunciou uma demonstração.
Mas a prova continha um erro. Foram necessários mais alguns meses para
Wiles corrigir a prova e ela ser aceita ([López-Ortiz, 1997]). Essa prova
pode ainda conter um erro? Apesar do consenso de que Fermat não possuı́a
a prova, podemos ter certeza disso? Uma formalização da prova do Último
Teorema de Fermat pode ajudar a responder essas perguntas. É proposto
como um dos 100 teoremas importantes da matemática para serem formalizados em [Wiedijk, 2008].
• Alguns matemáticos não dão importância à formalização matemática por
saberem que nem toda a Matemática pode ser formalizada. Mas cada vez
mais coisas são formalizadas, de forma que não há um limite claro para o
que pode e o que não pode ser formalizado. Se esse processo continuar,
poderemos sensibilizá-los, atraindo ainda mais contribuidores.
• Contribuir para a verificação formal de software e sistemas computacionais,
diminuindo a probabilidade de erros. Por exemplo, em 2002, uma pesquisa
do NIST (National Institute of Standards and Technology) estimou que erros
de software acarretam prejuı́zos em torno de 59 bilhões de dólares por ano à
economia dos Estados Unidos [Research Triangle Institute, 2002]. A situação é semelhante ao considerarmos a indústria de hardware, que freqüentemente faz recalls de seus produtos. O caso mais famoso desses recalls ocorreu
em 1994, quando processadores da Intel apresentaram erros de divisão de
ponto flutuante. Erros como esses podem ser diminuı́dos se o processo de
verificação de software e hardware for melhorado. No caso especı́fico do
AKS, uma formalização pode ajudar a aprimorar a verificação de softwares
geradores de chaves criptográficas, portanto, diretamente ligados à segurança de dados. Além disso, pode permitir a extração de código já validado
para ser usado por esses programas.
As ferramentas utilizadas em verificação formal são conhecidas como assistentes de prova. Como exemplos de assistentes de prova, podemos citar o Coq
([The Coq Development Team, 2008b]), o PVS ([SRI International, 2008]), o Isabelle/HOL ([Paulson and Nipkow, 2008]), entre outros. Os assistentes podem ser
divididos em duas partes fundamentais ([Barendregt and Geuvers, 2001]):
1. Um verificador de prova (proof checker), o qual segue noções, definições,
axiomas e provas formalizados numa dada linguagem lógica. As definições
são verificadas pela sua boa formação e as provas, por sua correção. Tudo
isso seguindo a semântica e a sintaxe da lógica escolhida.
2. Um sistema de desenvolvimento de provas, o qual disponibiliza às pessoas
um meio interativo e mais fácil de realizar sua prova.
52
Atualmente, os assistentes de prova estão bem desenvolvidos devido à quantidade de pessoas que já trabalharam com eles desde a década 1960. Muitos
teoremas matemáticos foram e continuam sendo verificados formalmente com a
utilização desses softwares. O processo de verificação formal pode ser dividido em
3 partes:
1. Especificação da teoria: nesse momento, o usuário precisa descrever os termos utilizados na teoria, como os tipos de dados.
2. Especificação das propriedades a serem provadas (teoremas, lemas, etc).
3. Construção das provas dessas propriedades. Nessa etapa, o usuário conduz
o assistente na construção da prova.
Alguns assistentes de prova podem, inclusive, gerar, como saı́da, o objeto de
prova, um arquivo que contém a prova em código de baixo nı́vel. Neste caso, o
objeto de prova pode ser checado por outro verificador de prova independente
([Friedman, 2006]). O conceito de objeto de prova está relacionado com o conhecido isomorfismo de Curry-Howard, que relaciona programas de computador com
provas matemáticas. Essa correspondência pode ser facilmente compreendida no
contexto do λ-cálculo simplesmente tipado ([Barendregt, 1992]), que nos permite
utilizar programação na construção de provas e lógica na construção de programas. Por exemplo, suponha que nossa tarefa seja construir uma prova para a
seguinte afirmação:
(P → Q) → (Q → R) → P → R,
(3.1)
onde P , Q e R são objetos proposicionais quaisquer. Uma prova de (3.1) consiste
em construir um λ-termo t que tenha (3.1) como tipo, isto é, t : (P → Q) →
(Q → R) → P → R. Pode-se verificar que o λ-termo
λx:P →Qy:Q→Rz:P .y(xz)
(3.2)
é solução de (3.1). Esse termo expressa como construir uma prova de R a partir
de provas arbitrárias de P → Q, Q → R e P , respectivamente a saber, x, y e z.
Uma questão que surge naturalmente neste contexto é: mas como podemos
confiar em um assistente de prova se, ele mesmo, também é um programa que
pode conter erros? Uma forma de responder a essa pergunta está em um critério, conhecido como Critério de de Brujin: “um assistente de provas satisfaz
o Critério de de Brujin se ele gera (de alguma forma) um objeto de prova que
pode ser checado por um algoritmo fácil ” ([Barendregt and Geuvers, 2001]). Ser
checado por um algoritmo “fácil” quer dizer que pode ser checado por um verificador de prova realmente pequeno e que possua um código facilmente verificável
(manualmente). Para o exemplo acima, isso consistiria em fornecer o objeto de
prova (3.2) e a afirmação (3.1) para o verificador de prova constatar que, de fato,
t : (P → Q) → (Q → R) → P → R.
Apesar de nunca podermos ter a certeza absoluta, já que sempre haverá a
(mesmo que remota) possibilidade de algum tipo de falha eletrônica que permita
a aceitação de uma prova falsa, seguindo esse critério, conseguimos a mais alta
53
confiabilidade que uma prova pode ter ([Barendregt and Geuvers, 2001]), pois
todo o processo passa a depender de uma pequena prova verificada por humanos.
Mas, e no caso dos assistentes de prova que não nos fornecem o objeto de prova?
Neste caso, há duas classes, os que podem ter traduzido o script de prova que será
usado por seu próprio verificador interno para um objeto de prova não padrão e os
que não possuem, ainda, qualquer possibilidade de produzir objetos de prova. As
pessoas que utilizam provas feitas nesta última classe precisam confiar no sistema
utilizado.
Em geral, os assistentes de prova têm os seguintes princı́pios ([Friedman, 2006]):
• O usuário determina o refinamento de objetivos e hipóteses, de acordo com a
estrutura de dedução natural. Isso está de acordo com a organização lógica
geral das provas matemáticas atuais.
• O usuário cita definições e teoremas já existentes nas bibliotecas do assistente. Uma apropriada construção de bibliotecas é importante devido ao
suporte ao reuso. Qualquer nova prova construı́da poderá ser introduzida
nas bibliotecas para futuro reuso.
• Também é crucial que o assistente de provas seja capaz de fazer inferências
relativamente triviais por si só. Caso contrário, a experiência mostra que o
processo demandaria muito tempo.
Formalmente, temos a seguinte notação ([Barendregt and Geuvers, 2001]) para
uma prova:
Γ `L p : A
que significa que a partir de um conjunto Γ de hipóteses podemos obter A, por
meio de uma lógica L. O termo p representa a prova formal, que, em geral, é
construı́da aos poucos com a ajuda de táticas que produzem um script da prova,
como veremos na seção a seguir.
3.2
O Assistente de Prova Coq
Como citamos na introdução, o Coq é um assistente de prova baseado em
uma lógica de ordem superior, conhecida como cálculo de construções indutivas, que é uma variação do λ-cálculo tipado ([Bertot and Castéran, 2004]). Portanto, utiliza uma teoria de tipos muito expressiva, onde, pelo isomorfismo de
Curry-Howard, os tipos são vistos como proposições e os termos como provas
[Barendregt and Geuvers, 2001].
A linguagem de especificação usada é chamada Gallina e é com ela que se
definem expressões matemáticas e se realizam as provas. Já a linguagem de comando é chamada Vernacular. É com ela que indicamos o que iremos fazer,
seja iniciar uma definição, iniciar um lema, começar a prova de um lema etc
([The Coq Development Team, 2008c]).
O Coq possui um conjunto relativamente extenso de provas nas bibliotecas nativas ([The Coq Development Team, 2008f]), além de contribuições de pesquisadores e professores de várias instituições ([The Coq Development Team, 2008g]),
54
tudo sob licença GNU LGPL. Isso permite uma interação entre o desenvolvimento
de provas e é importante para o reuso ([Friedman, 2006]). A versão que utilizaremos é a 8.1pl3, disponı́vel em [The Coq Development Team, 2008a]. Assim,
sempre estaremos nos referindo a essa versão em particular.
3.2.1
Tipos
Todas as expressões em Coq possuem um tipo. Se combinarmos expressões,
utilizando ou não certos conectivos, para gerar novas expressões bem formadas,
estas também terão algum tipo. Por exemplo, declaremos a variável b do tipo
nat:
Variable b:nat.
Podemos agora utilizar a constante 2 do tipo nat para escrever a expressão 2b,
que também será do tipo nat. Por outro lado, se utilizarmos a constante true,
do tipo bool, ao escrever a expressão true b, haverá um erro, pois não é uma
expressão bem formada ([Bertot and Castéran, 2004]).
Inicialmente, ao carregarmos o Coq, temos alguns tipos básicos como nat e
bool. O tipo de um tipo é chamado de sort. O Coq possui 3 sorts pré-definidos:
• Prop, para as expressões proposicionais (expressões que podem ser avaliadas
como falsas ou verdadeiras, de acordo com sua carga semântica).
• Set, para descrever tipos de dados e especificações. Os termos cujo tipo é
uma especificação são chamados de programas.
• Type, que são utilizados na construção dos universos que estão relacionados
à consistência do sistema de tipos. Os identificadores Prop e Set possuem
tipo Type, que por sua vez possui tipo Type.
A partir desses três sorts (que também são tipos, já que termos e tipos pertencem à mesma classe no cálculo de construções indutivas), podemos construir todos
os demais tipos que precisarmos. Mas seria muito trabalhoso começarmos apenas
com esses tipos para provarmos expressões matemáticas. Justamente tendo em
vista o suporte ao reuso, existem os tipos nat, bool, e mais tantos outros que podem ser carregados ao chamarmos as bibliotecas apropriadas (Z, para os inteiros,
Q, para os racionais etc).
Há um certo paralelismo entre a teoria de tipos e a teoria de conjuntos
([Barendregt and Geuvers, 2001]). Por exemplo, podemos considerar que elementos de um mesmo tipo pertencem a um mesmo conjunto, o conjunto de todos os
elementos com aquele tipo. Vejamos como é construı́do o tipo nat, que representa
o conjunto dos números naturais. Podemos fazer isso no Coq com o comando:
Print nat.
A resposta, então, será:
Inductive nat : Set := O : nat | S : nat -> nat
For S: Argument scope is [nat scope]
55
Isso quer dizer que o tipo nat é do tipo Set (como esperávamos), construı́do
de forma indutiva, com o uso de dois construtores, O, do tipo nat e S, do tipo
nat->nat.
O construtor O é do tipo nat. O construtor S requer uma entrada nat para
dar uma saı́da nat (nat->nat), ou seja, é uma função unária. Assim, S O será
do tipo nat. Então, também podemos escrever S (S O), S (S (S O)) etc. Se
O é o zero e S é a função sucessor, podemos representar todos os naturais dessa
forma. O é o natural 0, S O é o natural 1 (sucessor de 0), S (S O) é 2 etc. E a
correspondência de notação é feita no escopo nat scope. Os comandos Check 5
e Check S (S (S (S (S O)))) geram a mesma saı́da:
5
:
nat.
Por outro lado, se usarmos o comando Unset Printing Notations, estaremos descarregando as notações do escopo e, agora, ao usarmos o comando Check
5, obteremos a saı́da:
S (S (S (S (S O))))
: nat.
3.2.2
Táticas
No Coq, as provas e as especificações permanecem no mesmo arquivo. Após
enunciada a especificação, abre-se o campo de provas e podemos começar a aplicar
as táticas. Isso é feito de forma interativa. Cada tática é um comando que,
inclusive, pode ser desfeito. Existe uma ferramenta gráfica, chamada CoqIDE
([The Coq Development Team, 2008a]), que auxilia no processo de construção das
provas. Também é possı́vel utilizar outras ferramentas de interação, como o Proof
General e o Pcoq ([The Coq Development Team, 2008e]).
Podemos enunciar um lema da seguinte forma:
Lemma dois mais dois:
2+2=4.
Isso significa que queremos provar 2+2=4. O identificador dois mais dois é
utilizado para referências futuras.
Ao iniciarmos a prova, aparecerá o seguinte:
1 subgoal
-----------------------------(1/1)
2 + 2 = 4
Abaixo da linha pontilhada temos 1 objetivo a ser provado. Acima da linha são
colocadas as hipóteses (inexistentes no nosso exemplo). Devido ao isomorfismo de
Curry-Howard, uma prova em Coq nada mais é do que um termo t da linguagem
do cálculo de construções indutivas que possui como tipo o que se está afirmando,
56
no caso 2+2=4. De uma forma geral, a construção de um tal termo não é nada
trivial. O Coq possui, então, várias táticas que nos permitem construir pouco a
pouco esse termo, ou seja, uma prova de 2+2=4. Vamos começar com a tática
simpl.
Agora, o campo de prova será
1 subgoal
-----------------------------(1/1)
4 = 4
A tática simpl é uma tática de redução que procura simplificar o termo de
prova. Neste caso, ela realizou a soma 2+2. Agora, a prova é trivial, pois 4=4.
Podemos usar a tática trivial, que é capaz de identificar igualdades triviais.
trivial.
E obtemos no campo de provas Proof completed, o que mostra que chegamos
ao fim da prova. Ainda precisamos pedir para o Coq guardar nossa prova, com
o nome que escolhemos no inı́cio do comando Lemma, para podermos utilizá-lo
futuramente. Só assim sairemos do campo de provas. O comando é
Qed.
O Coq nos dá a saı́da:
simpl in |- *.
trivial.
dois mais dois is defined
E isso significa que dois mais dois foi definido utilizando a tática simpl seguida
de trivial. O termo que possui tipo 2+2=4 pode ser visto com o comando Print:
Print dois mais dois.
refl equal 4
: 4 = 4
Na verdade, esta é uma prova muito simples e trivial. Poderı́amos ter simplesmente usado a tática auto, que realiza uma busca completa por definições e
provas nas bibliotecas carregadas para tentar provar o objetivo, já que, quando
inicialmente carregado, o Coq importa parte da biblioteca Arith, que possui,
além do tipo nat e do escopo nat scope, provas envolvendo o tipo nat.
Vejamos uma outra prova, lembrando que, por ser baseado em uma lógica de
ordem superior, podemos usar quantificadores em predicados:
Proposition tauto ou:
forall A:Prop, A-> (A ∨ ∼A).
57
Internamente, os comandos Proposition, Lemma, Theorem, entre outros, representam a mesma coisa. A diferenciação pode ser útil para uma melhor organização das provas ([The Coq Development Team, 2008c]). Estamos dizendo que,
para toda proposição A, A implica em A ou na negação de A. Teremos o seguinte
no campo de prova:
1 subgoal
-----------------------------(1/1)
forall A : Prop, A -> A ∨ ∼A
Comecemos com a tática intro, que introduz hipóteses no campo de hipóteses
a partir do termo de prova.
intro A.
Então, teremos:
1 subgoal
A : Prop
-----------------------------(1/1)
A -> A ∨ ∼A
Ou seja, introduzimos a hipótese A no nosso campo de hipóteses a partir do
que estava no campo de provas. O que temos agora é, seja A qualquer proposição.
Então, A implica em A ou na negação de A. É justamente essa a idéia do quantificador universal. O que fizemos foi aplicar a regra de introdução do quantificador
universal. Ainda podemos fazer a introdução da implicação, já que a implicação
pressupõe, como hipótese, o antecedente1 . Assim, façamos
intro H.
Como saı́da, teremos:
1 subgoal
A : Prop
H : A
-----------------------------(1/1)
A ∨ ∼A
Isso significa: seja A uma proposição; se tivermos uma prova de A (neste caso H),
então podemos provar A ou a negação de A. Poderı́amos ter feito as duas táticas
intro de uma única vez com a tática intros A H. Ou simplesmente, intros.
Neste caso, o Coq faz todas as introduções de hipóteses possı́veis nomeando-as
seguindo um algoritmo interno.
1
Se tivermos o antecedente, teremos o conseqüente.
58
Assim, reduzimos todo o nosso termo de prova inicial a apenas uma disjunção.
Para provar a disjunção, basta provar o seu lado esquerdo ou o direito. Mas nós
temos o lado esquerdo como hipótese. Ou seja, temos uma prova de A, a saber, H.
Podemos usar a tática left, que diz que provaremos o lado esquerdo da disjunção.
left.
Agora, temos:
1 subgoal
A : Prop
H : A
-----------------------------(1/1)
A
Bom, mas como temos A como a hipótese H, basta dizermos isso ao Coq com
a tática exact H, ou pedirmos para ele procurar no banco de hipóteses com a
tática
assumption.
E isso completa a prova.
Essa prova também é bem simples. Se trata de uma tautologia. O Coq
pode resolver tautologias com a tática tauto. Bastaria essa única tática para
provarmos nossa proposição. Novamente, a tática auto também resolveria por
ser mais abrangente que tauto. De fato, tauto tenta resolver apenas as tautologias, caso não consiga, dará um erro informando a falha. Já auto tentará
vários recursos, inclusive tauto, mas não dará erro se falhar. Apenas deixará
as coisas como estão. Por causa disso, a tática auto pode levar mais tempo
para resolver um objetivo, o que, às vezes, torna melhor usar outras no lugar
([The Coq Development Team, 2008c]).
É claro que nem todas as provas são fáceis de serem realizadas assim. Seria
bom se bastasse digitar auto e esperar o Coq resolver tudo. Vejamos uma prova
um pouco mais complexa (mas nem tanto) envolvendo inteiros, que necessitará da
biblioteca ZArith e de uma de suas provas. Assim, primeiro pedimos para o Coq
carregar a biblioteca ZArith que, entre outras coisas, possui o tipo Z definido:
Require Import ZArith.
Agora, o comando Print Z irá mostrar:
Inductive Z : Set := Z0 : Z | Zpos : positive -> Z
| Zneg : positive -> Z
For Zpos: Argument scope is [positive scope]
For Zneg: Argument scope is [positive scope]
59
Ou seja, o tipo Z é formado por três construtores. Z0, Zpos e Zneg. Sem entrar
muito em detalhes, um representa 0, outro, os positivos e o último, os negativos.
Vamos, agora, iniciar o seguinte lema:
Lemma exemplo apply:
forall n:Z, (n<=0)%Z ∨ (3*n-1>n)%Z.
No campo de prova, teremos o seguinte:
1 subgoal
-----------------------------(1/1)
forall n : Z, (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
O sinal %Z usado significa que tudo que está entre parêntesis deve ser considerado com relação ao tipo Z. A biblioteca ZArith, ao ser carregada, não muda o
escopo. Se escrevêssemos apenas forall n : Z, (n <= 0) ∨ (3 * n - 1 >
n), o Coq consideraria ainda o escopo dos naturais: a constante 3 seria vista como
nat, a multiplicação interpretada como entre tipos nat e as comparações também.
Assim, terı́amos uma expressão mal formada, já que n é tipo Z, e o Coq geraria
um erro. Algumas bibliotecas adicionais a ZArith, como Znumtheory, alteram
o escopo, de forma que terı́amos o contrário: se trabalharmos com o tipo nat,
teremos que indicar %nat. De qualquer forma, tanto a notação quanto o escopo
podem ser mudados a qualquer instante, inclusive no meio da prova, tornando o
Coq muito flexı́vel.
O nosso lema exemplo apply diz que, para qualquer n inteiro, então n ≤ 0
ou 3n − 1 > n. De fato, sempre que n ≤ 0, temos 3n − 1 < n. Podemos usar
a tática intros para preencher nosso campo de hipóteses e diminuir o termo de
prova. Assim, teremos:
1 subgoal
n : Z
-----------------------------(1/1)
(n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Olhando as provas da biblioteca ZArith, existe uma que pode nos ajudar. É
o teorema chamado Ztrichotomy. Esse teorema está carregado, pois faz parte
de ZArith. Podemos verificá-lo com o comando Check Ztrichotomy, que nos
fornece:
Ztrichotomy
: forall n m :
Z, (n < m)%Z ∨ n = m ∨ (n > m)%Z
O tipo de Ztrichotomy é uma fórmula com dois quantificadores universais.
Corresponde a uma prova de que para quaisquer 2 inteiros, ou eles são iguais, ou
um é maior que o outro. Podemos, então, usar a tática:
elim Ztrichotomy with n 0%Z.
60
A tática elim é a mais simples ([The Coq Development Team, 2008c]) das
táticas indutivas. Ztrichotomy representa uma disjunção a partir de quaisquer
2 inteiros. Neste caso, o comportamento de elim será o de eliminar a disjunção,
desde que indiquemos de quais inteiros estamos falando, por isso usamos o with.
Mas eliminar uma disjunção do campo de hipóteses é mostrar que qualquer um
dos lados da disjunção nos leva ao objetivo. Uma prova, como Ztrichotomy, pode
ser invocada a qualquer momento como se estivesse realmente entre as hipóteses.
Assim, teremos:
2 subgoals
n : Z
-----------------------------(1/2)
(n < 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
-----------------------------(2/2)
n = 0%Z ∨ (n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Perceba que a disjunção (n < 0) ∨ n = 0 ∨ (n > 0) é vista, pelo Coq,
como (n < 0) ∨ (n = 0 ∨ (n > 0)), ou seja, um disjunção entre dois membros. Por isso, agora temos que provar apenas 2 sub-objetivos, e não 3.
O sub-objetivo atual é o (1/2). Todas as táticas que usarmos serão aplicadas
a ele. Se quisermos mudar o foco de prova para o segundo sub-objetivo, podemos
usar o comando Focus 2. Mas, como isso não é necessário, continuemos com
o primeiro sub-objetivo, que se trata de uma implicação. Podemos, então, usar
novamente a tática intros e teremos:
2 subgoals
n : Z
H : (n < 0)%Z
-----------------------------(1/2)
(n <= 0)%Z ∨ (3 * n - 1 > n)%Z
-----------------------------(2/2)
n = 0%Z ∨ (n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
A nova hipótese H pertence apenas ao contexto do primeiro sub-objetivo, o
qual se trata de uma disjunção. Então, basta que provemos apenas um de seus
lados. Como H é suficiente para provar o lado esquerdo, usaremos a tática left:
2 subgoals
n : Z
H : (n < 0)%Z
-----------------------------(1/2)
(n <= 0)%Z
-----------------------------(2/2)
n = 0%Z ∨ (n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
61
Se temos uma prova de n < 0, então sabemos que n é menor ou igual a 0.
Será que não há uma prova dessa propriedade? Sim, a biblioteca ZArith possui o
lema Zlt le weak, que é uma prova para forall n m :Z, (n < m)%Z -> (n
<= m)%Z. Observe que o conseqüente dessa implicação se encaixa exatamente
no nosso sub-objetivo. Então, podemos usar a tática apply Zlt le weak, e
obtemos:
2 subgoals
n : Z
H : (n < 0)%Z
-----------------------------(1/2)
(n < 0)%Z
-----------------------------(2/2)
n = 0%Z ∨ (n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Veja que agora não precisamos provar n ≤ 0. Mas precisamos provar n < 0,
que é justamente o que diz o lema Zlt le weak: para uma prova de n ≤ 0, basta
uma de n < 0.
Agora, como temos a hipótese H a nosso favor, a tática assumption é suficiente
para completar o primeiro sub-objetivo, restando apenas o segundo:
1 subgoal
n : Z
-----------------------------(1/1)
n = 0%Z ∨ (n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Note que a hipótese H foi apagada do contexto, pois era parte do primeiro
sub-objetivo. Após usarmos novamente a tática intros, teremos:
1 subgoal
n : Z
H : n = 0%Z ∨ (n > 0)%Z
-----------------------------(1/1)
(n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Como a hipótese H é uma disjunção, podemos usar a tática elim H, o que
exigirá que provemos dois sub-objetivos:
2 subgoals
n : Z
H : n = 0%Z ∨ (n > 0)%Z
-----------------------------(1/2)
n = 0%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
62
-----------------------------(2/2)
(n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Agora, podemos usar intros novamente, e teremos:
2 subgoals
n : Z
H : n = 0%Z ∨ (n > 0)%Z
H0 : n = 0%Z
-----------------------------(1/2)
(n <= 0)%Z ∨ (3 * n - 1 > n)%Z
-----------------------------(2/2)
(n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
Poderı́amos proceder como fizemos antes, usando left e depois procurarmos
uma prova de que n = 0 implica em n ≤ 0 para aplicá-la2 . Mas a biblioteca
ZArith possui um novo conjunto de regras, Hint, para a tática auto seguir.
Basta usarmos a tática auto with zarith e a tática auto será executa com seu
comportamento padrão mais as novas regras definidas no Hint zarith. Desta
forma, o Coq consegue verificar que a hipótese H0 satisfaz o sub-objetivo. E
teremos:
1 subgoal
n : Z
H : n = 0%Z ∨ (n > 0)%Z
-----------------------------(1/1)
(n > 0)%Z -> (n <= 0)%Z ∨ (3 * n - 1 > n)%Z
No passo anterior, não era necessário a tática intros. Bastava a tática auto
with zarith diretamente. Podemos fazer isso agora e a prova estará completa.
Esta prova poderia ser terminada apenas com as táticas:
intros.
elim Ztrichotomy with n 0%Z; auto with zarith.
intros.
elim H; auto with zarith.
Qed.
O ponto-e-vı́rgula após as táticas elim significa que auto with zarith será
aplicado a todos os novos sub-objetivos gerados. No primeiro caso, o primeiro
sub-objetivo (aquele que utilizamos a prova Zlt le weak) será automaticamente
provado. Mas ainda teremos o segundo. Mas no segundo caso, o auto with
zarith conseguirá provar os dois sub-objetivos gerados após a eliminação de H.
2
E há! É o lema chamado Zeq le.
63
Agora, vejamos uma prova simples, mas não trivial, que parece ser fundamental para especificarmos a primeira etapa do algoritmo AKS. Vamos provar
formalmente que, para todo n inteiro positivo e todo m natural não nulo, n|nm :
Lemma n div pow n:
(Zpower nat n m)).
forall (n:Z) (m:nat), n>0 ->
(gt m 0) -> (n |
Antes de prosseguirmos, vamos explicar a notação. Neste momento, precisamos das bibliotecas ZArith e Zpower carregadas. Esta última altera o escopo
para Z. A relação > (maior que) será considerada entre tipos Z, pois a notação
foi alterada. O nosso Lema n div pow n requer que o nat m seja maior que 0.
Podemos fazer de duas maneiras: ou escrevemos (m>0)%nat, como fizemos nas
outras provas com Z, ou esquecemos a notação e usamos diretamente o predicado
binário gt (greater than), que compara dois termos nat. Print gt nos mostra:
gt = fun n m :
nat => (m < n)%nat :
nat -> nat -> Prop.
Assim, gt a b é verdadeiro se a>b (a e b do tipo nat). A sı́mbolo >, por
sua vez, agora está vinculado ao predicado Zgt, que faz o mesmo que gt, porém,
envolvendo tipos Z. Além disso, a biblioteca Zpower tem duas especificações para
potência. A função binária Zpower, que retorna, como tipo Z, o valor de um inteiro
elevado a outro inteiro, e a função binária Zpower nat, que retorna, também
como tipo Z, o valor de um inteiro elevado a um natural. O sı́mbolo ^ é vinculado
a Zpower (a ^ b = Zpower a b, para a e b do tipo Z). Mas, como queremos
considerar expoentes do tipo nat, usaremos diretamente a função Zpower nat.
Poderı́amos, também, criar um novo sı́mbolo de notação. Por exemplo:
Infix "**":= Zpower nat.
Isso faria com que o Coq passasse a reconhecer a**b como Zpower nat a b.
Voltando à nossa formalização, após introduzir o comando Lemma acima, teremos o seguinte no campo de prova:
1 subgoal
-----------------------------(1/1)
forall (n : Z) (m : nat), n > 0 -> (m > 0)%nat -> (n | Zpower nat
n m)
Usamos a tática intros e passamos a ter:
1 subgoal
n : Z
m : nat
H : n > 0
H0 : (m > 0)%nat
-----------------------------(1/1)
(n | Zpower nat n m)
64
Agora, usemos a seguinte tática:
replace (Zpower nat n m) with (n*(Zpower nat n (m-1))).
Passaremos a ter, no campo de prova:
2 subgoals
n : Z
m : nat
H : n > 0
H0 : (m > 0)%nat
-----------------------------(1/2)
(n | n * Zpower nat n (m - 1))
-----------------------------(2/2)
n * Zpower nat n (m - 1) = Zpower nat n m
A tática replace A with B procura a expressão A e a substitui pela expressão
B, desde que tenham o mesmo tipo. Além disso, gera um sub-objetivo para que
se valide a substituição. O nosso primeiro sub-objetivo é relativamente trivial
ao Coq, que pode verificar automaticamente que n inteiro divide um múltiplo de
n devido a algumas provas de ZArith, desde que n6= 0. Como temos n>0 como
hipótese, podemos, então, usar a tática auto with zarith:
1 subgoal
n : Z
m : nat
H : n > 0
H0 : (m > 0)%nat
-----------------------------(1/2)
n * Zpower nat n (m - 1) = Zpower nat n m
Devemos, ao final, provar que nossa substituição foi válida. Há uma prova de
que Zpower nat n m = n*(Zpower nat n (m - 1)), chamada n exp minus 1,
desenvolvida para o nosso projeto. Essa prova apenas requer n>0 e m>0. Porém, o
nosso o objetivo possui as expressões invertidas em relação a nossa prova. Temos,
então, que usar a tática symmetry, que implementa a simetria na relação de
igualdade. Assim, após a tática, teremos:
1 subgoal
n : Z
m : nat
H : n > 0
H0 : (m > 0)%nat
-----------------------------(1/2)
Zpower nat n m=n*(Zpower nat n (m -1))
65
Agora, podemos usar nossa outra prova. Como ela exigirá n>0 e m>0 e já
temos essas exigências como hipóteses, a usaremos aliada à tática assunption, o
que completa prova:
apply n exp minus 1; assumption.
Como podemos ver, há diversas formas de se realizar uma prova. Além disso,
existem diversas outras táticas. E mais: o usuário pode criar suas próprias táticas.
Isso é particularmente útil nos casos em que se usa várias vezes as mesmas táticas
seguidas. Pode-se, então, criar uma tática que faça o trabalho das táticas de uma
só vez.
Além disso, cabe ressaltar que o Coq possui o recurso de assumir a prova como
válida antes de terminá-la, para que continuemos a trabalhar com outras provas.
A prova passa a ser vista como um axioma. Isso é particularmente útil se tivermos
dependendo de uma prova auxiliar. Por exemplo, suponha que queiramos provar
A→E
e sabemos que essa prova, feita diretamente, é longa e trabalhosa, pois requer o uso
extensivo de táticas, tornando-a, inclusive, pouco legı́vel. Mas, ao examinarmos
a estrutura de E, vemos que é possı́vel construirmos uma prova de
D → E.
Além disso, procurando nas bibliotecas nativas, descobrimos que há uma prova
de
A→B
e, com um pouco mais de pesquisa, encontramos, nas contribuições de terceiros,
uma prova de
B→C
que, pela estrutura de C, nos faz imaginar que é possı́vel provar
C → D.
Assim, podemos montar uma possı́vel forma para nossa prova inicial, que será
A → B → C → D → E,
deixando, por enquanto, as provas D → E e C → D como axiomas.
Como, inicialmente, achamos possı́vel provar D → E, começamos por trabalhar nessa prova, enquanto somente a prova C → D fica assumida como axioma.
Ao final, poderemos até não completar a prova, mas temos uma possı́vel forma
para ela, mais organizada e mais legı́vel do que uma prova feita de uma vez só.
Aliás, se não conseguirmos provar uma prova extensa, feita de uma única vez,
o que faremos com o trabalho incompleto realizado? No caso da prova dividida
em provas auxiliares, mais pessoas trabalhando nos axiomas restantes poderão
66
completar a prova. Além disso, as provas intermediárias poderão ser úteis em
trabalhos futuros, desempenhando papel fundamental de suporte ao reuso.
67
Capı́tulo 4
Metodologia
A idéia é construir, inicialmente, uma especificação geral para o AKS e uma
prova de que sua saı́da é sempre correta. A partir desse objetivo, começaremos
a especificar os teoremas e as propriedades envolvidos em cada etapa do algoritmo como provas auxiliares que, temporariamente, serão iniciados como axiomas. Essas provas auxiliares também poderão, à medida em que forem sendo
desenvolvidas, ser divididas em provas ainda menores.
O trabalho desenvolvido dessa forma traz os benefı́cios apontados no final
da Seção 3.2. Assim, necessitaremos, também, de uma boa pesquisa nas bibliotecas nativas ([The Coq Development Team, 2008f]) e nas contribuições de terceiros
([The Coq Development Team, 2008g] e [INRIA, 2008]) à procura de provas que
possamos reutilizar em nosso projeto. Mas, muitas vezes, nos depararemos com
especificações que não foram construı́das de forma a atenderem particularmente
nosso problema. Neste caso, poderemos adaptá-las ou construir provas novas.
Esperamos, ao final, conseguir provar todos os axiomas intermediários.
Assim, começaremos com a especificação da seguinte fórmula:
φ(n) = n > 1∧¬(Pot n)∧rNdiv r(N n)∧((qDiv r n∧q = n)∨(¬(qDiv r n)∧IdAKS n)),
onde Pot, rNdiv, qDiv e IdAKS são fórmulas e N é uma função unária que iremos
especificar como:
Pot n = ∃m∃e(m > 1 ∧ e > 1 ∧ n = me );
rNdiv r n = Primo r ∧ r - n ∧ ∀s(Primo s → s - n → s ≥ r);
qDiv r n = ∃q(q < r ∧ Primo q ∧ q | n);
IdAKS n = ∀a(a ∈ {1, 2, ..., r} → (X + a)n = X n + a) em Zn [X]/(X r − 1);
2
N n = 2n(n − 1)(n2 − 1)(n3 − 1)...(n4dlog2 ne − 1).
Primo é um predicado que significa “é primo positivo”. Além disso, para não
sobrecarregar a notação, estamos considerando que todos os termos numéricos
acima estão em Z. Assim, a fórmula φ pode ser entendida como “n é inteiro
68
maior que 1, n não é potência de inteiro maior que 1 com expoente maior que 1,
r é o menor primo positivo que não divide N e, ou existe um primo positivo q
menor que r que divide n e q = n, ou não existe primo positivo q menor que r que
divide n e a congruência (X + a)n = X n + a no anel Zn [X]/(X r − 1) se verifica
para todo a em {1, 2, ..., r}”.
Além disso, teremos que provar nossa especificação com um teorema semelhante a
∀p(p ∈ Z → p > 1 → (p é primo ↔ φ(p)),
ou seja, um inteiro positivo p é primo se, e somente se, φ(p) é verdadeira.
Os predicados envolvidos em φ correspondem às etapas do AKS. Assim, ao
especificarmos aqueles predicados, estaremos especificando cada uma das etapas
do algoritmo. Mas, para isso, precisaremos, ainda, realizar algumas provas de
teoremas envolvidos em cada etapa. Por exemplo, se n é potência de inteiro
maior que 1 com expoente maior que 1, não pode ser primo. Uma prova como
essa será fundamental para a primeira etapa pois, se Pot n for verdadeiro, então
a fórmula φ será falsa.
Seguiremos, assim, uma estrutura de dependência como a mostrada na Figura
4.1.
AKS
Etapa 1
Etapa 3
Etapa 2
Etapa 4
Provas
Auxiliares
Provas das
bibliotecas nativas
Figura 4.1: Dependências que serão seguidas na formalização do AKS.
As provas auxiliares serão agrupadas em arquivos auxiliares. O arquivo principal só conterá as provas principais, que serão as formalizações das etapas e a
formalização final.
Após uma pesquisa em ([The Coq Development Team, 2008f]), decidimos que
a principal biblioteca do Coq que iremos usar é a ZArith, mais algumas de suas
auxiliares, como Znumtheory e Zpower. Essas bibliotecas possuem inúmeros lemas
e propriedades provados com respeito à Teoria dos Números, inclusive envolvendo
69
primalidade. A biblioteca Reals, inicialmente, pareceu muito interessante devido
à quantidade de especificações que possui, como, por exemplo, o teorema binomial,
e possibilidade de trabalhar com inteiros também (de fato, qualquer inteiro é
real). Mas, ao pesquisarmos um pouco mais, vimos de que se trata de uma
biblioteca fortemente fundamentada em axiomas, o que não ocorre com ZArith,
que possui apenas provas concluı́das. Assim, o tipo principal usado será o tipo Z.
A versão do Coq que utilizaremos será a 8.1pl3, aliada à interface gráfica CoqIDE
([The Coq Development Team, 2008b]).
70
Capı́tulo 5
Resultados
Construı́mos 3 arquivos de provas próprias, chamados AKS.v, aux AKS.v e
FermatGen.v. Além disso, há 1 arquivo de provas alteradas dos projetos “Correctness of RSA algorithm” ([Almeida and Thery, 1999]) e “Library for floating point
numbers” ([Thery and Boldo, 2001]), chamado de aux Sophia mod.v, e mais 11
arquivos de provas originais: Aux.v, Fermat.v, Iterator.v, Permutation.v, Tatic.v, Ulist.v, ZdivExp.v, Zfact.v, ZisMod.v, Zprod.v e Zprogession.v, do
projeto “Numbers equal to the sum of two square numbers” ([Thery, 2004]).
Nossas provas precisaram de muitas outras provas auxiliares que puderam
ser encontradas nas bibliotecas nativas ou nos projetos acima citados, disponı́veis
como contribuições ao Coq. Mas, dos 11 arquivos de provas originais de “Numbers
equal to the sum of two square numbers”, dois são de vital importância: Aux.v,
que possui muitas provas elementares, e tornou-se um ótimo complemento para
as provas nativas de ZArith, e Fermat.v, que traz uma única prova: a do Pequeno Teorema de Fermat (Teorema 2.2.2). Dos outros 9, apenas algumas poucas
provas de ZisMod.v e ZdivExp.v são utilizadas diretamente. No entanto, devido
à dependência criada para os arquivos ZisMod.v, ZdivExp.v, precisamos manter,
ao menos, os outros 7 arquivos do projeto original. Assim, esses arquivos podem
ser vistos como uma biblioteca extra. De fato, tentamos colocar todo o conteúdo
dos 11 arquivos num único, mas houve problemas de dependência. Como são
muitas, seria um esforço extra entender toda a dependência existente entre as
provas. Assim, preferimos deixar esses arquivos intactos por enquanto, havendo
a possibilidade de sintetizá-los no futuro.
Já o arquivo aux Sophia mod.v possui 17 provas e 2 definições modificadas
por nós para que atendessem nosso projeto. Em sua maioria, essas provas tratavam do tipo nat, e nós precisávamos que tratassem do tipo Z. Assim, agora
possuı́mos provas para somas iteradas, para o Teorema Binomial, entre outras,
que utilizam o tipo Z.
Mesmo com todas essas provas, os 3 arquivos totalmente criados por nós ainda
possuem 44 provas e 13 definições que são usadas na nossa especificação do AKS,
perfazendo, aproximadamente, 1000 linhas, excluindo os comentários. Há, ainda,
algumas provas não concluı́das que estão comentadas e outras que estão concluı́das, mas não utilizadas na especificação do AKS. Disponibilizamos todo o
material do projeto na Internet em [de Moura and Peixoto, 2008], para que possa
ser verificado.
71
Apenas uma das provas principais ainda não foi concluı́da. Trata-se da prova
congr AKS step 2, do arquivo AKS.v. Essa prova corresponde à volta da congruência (2.10) e foi deixada como axioma para que pudéssemos concluir a prova
geral.
Entre as provas importantes concluı́das que utilizamos, podemos citar:
• A forma usual para o Pequeno Teorema de Fermat (Corolário 2.2.3), que
chamamos fermat little def, do arquivo aux AKS.v, e que, por sua vez,
depende da prova do Pequeno Teorema de Fermat, que está no arquivo
Fermat.v, citado anteriormente.
• A ida do Teorema 2.3.1, que chamamos de gen lit fermat step 1, no
arquivo FermatGen.v, e que depende de fermat little def. Essa prova
é fundamental para a prova congr AKS step 1, também concluı́da, do arquivo AKS.v e que corresponde à ida da congruência (2.10).
• A prova de que, para todo inteiro positivo n, há um menor primo positivo
r que não divide n, que chamamos ex min r, no arquivo AKS.v.
• A existência de infinitos números primos (Teorema 2.2.5), que chamamos
inf prime, do arquivo aux AKS.v. Essa prova aparece em [Wiedijk, 2008]
como um dos 100 teoremas matemáticos importantes a serem formalizados e
que já teria sido provada em Coq por O’Connor, mas não estaria disponı́vel
nas contribuições. Não pudemos achar mais qualquer referência à prova
de O’Connor e, então, tivemos que desenvolver a nossa própria, pois era
fundamental para a prova ex min r.
As provas principais do AKS estão no arquivo AKS.v. São provas e definições
que representam as etapas do algoritmo, bem como a especificação final. A Figura
5.1 mostra a relação de dependência para as provas principais. Assim, tratase de uma relação geral de dependências, em que aparecem apenas as provas
diretamente utilizadas nas provas principais. Não vemos as dependências entre
as provas menores, até porque o diagrama ficaria enorme.
72
73
Legenda
power_not_prime
p_pow_Valid
Provas terminadas
Prova não terminada
Etapa 4 parte I
Zis_gcd_1
Zis_gcd_sym
Zabs_pos
Zabs_intro
Zdivide_trans
prime_dec
Zis_gcd_intro
prime_rel_prime
Zle_lt_or_eq
Zabs_eq_case
Zdivide_le
prime_le_2
not_prime_prime_divide
not_prime_prime_divide_gen
N_valid1
q_not_div_Valid1
Etapa 3
AKS_Valid
Zdivide_opp_l_rev
Ztrichotomy
Zis_mod_def_inv
Zis_mod_def
gen_lit_fermat_step_1
Congr_AKS_step_1
Figura 5.1: Dependências finais na formalização do algoritmo AKS.
Provas do arquivo AKS.v
Provas do arquivo aux.AKS,v
Provas do arquivo Aux.v
Provas do arquivo ZisMod.v
Provas do arquivo aux_Sophia_mod.v
Prova do arquivo FermaGen,v
Provas da biblioteca Arith
Provas da biblitoeca ZArith
le_S_gt
Etapa 2
inj_le_inv
max_ndiv_lt
ex_prime_p_ndiv_n
min_prime_ndiv
Z_of_nat_Zabs_nat
Zlt_0_ind
le_lt_or_eq
prime_le_2
prime_ndiv_le_ex_ndiv
prime_ndiv_Valid2
prime_ndiv_Valid1
ex_min_r
Etapa 1
Etapa 4 parte II
Zmult_1_r
Zle_or_lt
prime_divisors
prime_le_2
Congr_AKS_step_2
Provas de apoio
A Figura 5.2 mostra as codificações, em Coq, de nossa especificação e de nossa
prova do algoritmo AKS, que se encontram no arquivo AKS.v. Logo após, incluı́mos as descrições de nossas principais provas, as quais foram geradas pela ferramenta coqdoc ([The Coq Development Team, 2008d], Part IV, Practical Tools),
que tem a caracterı́stica de gerar documentação mais legı́vel, tanto em HTML
como em LATEX, a partir de arquivos fonte Coq. Além disso, com a utilização de
algumas macros, podemos inserir comentários mais apropriados entre o código.
Cabe também ressaltar que o código LATEX gerado ainda foi parcialmente alterado
por nós.
Figura 5.2: Especificação final do AKS e a prova de sua correção em Coq.
74
Prova gen lit fermat step 1, correspondente à ida do Teorema 2.3.1
Se n é primo, então (X + a)n = X n + a (mod n). Os polinômios serão
representados como expressões do tipo inteiro, mas com a variável X quantificada
universalmente. Usaremos binomiais e o Pequeno Teorema de Fermat para a
prova.
Lemma gen lit fermat step 1 : ∀ a n x :Z, a>0 → prime n →
Zis mod ((x +a)ˆn) (x ˆn +a) n.
Prova.
intros.
Se n é primo, então é maior ou igual a 2.
generalize H0 ; intros; apply prime le 2 in H0.
Como a prova da expansão binomial utiliza expoente natural, incluı́mos uma
premissa de que o módulo natural de n é maior que 1.
assert (gt (Zabs nat n) 1).
elim Zabs nat lt with 1 n; auto with zarith.
Definição de Zis mod: os dois primeiros termos geram o mesmo resto quando
divididos pelo terceiro. Zis mod ((x + a)n )(xn + a)n → ((x + a)n ) mod n =
(xn + a) mod n.
apply Zis mod def ; auto with zarith.
Reescrever expoentes como naturais.
repeat rewrite ← Zpower Zpower nat; auto with zarith.
Expansão binomial: soma iterada de coeficientes binomiais.
rewrite exp Pascal.
Separamos os termos inicial e final do somatório, que permanece somente com os
termos intermediários, que devem ser nulos modulo n.
rewrite sum nm fi inv ; auto with zarith.
Simplificamos, pois
n
0
=1e
n
n
= 1, (binomial def1 e binomial def3).
rewrite binomial def1.
replace (plus 1 (pred (Zabs nat n))) with (Zabs nat n); auto with zarith.
75
rewrite binomial def3.
Simplificar expressões. Teremos que provar depois.
replace (1*(x **(Zabs nat n-Zabs nat n)* a ** Zabs nat n)) with (aˆn).
replace (1*(x **(Zabs nat n-0)* a ** 0)) with (x ˆn).
rewrite Zmod plus eq; auto with zarith.
replace ((sum nm (pred (pred (Zabs nat n))) 1 (fun k : nat ⇒
binomial (Zabs nat n) k × (x ** (Zabs nat n - k ) × a ** k )) + aˆn) mod
n) with
((sum nm (pred (pred (Zabs nat n))) 1 (fun k : nat ⇒
binomial (Zabs nat n) k × (x ** (Zabs nat n - k ) × a ** k )) mod n +
aˆn mod n) mod n).
assert (sum nm (pred (pred (Zabs nat n))) 1 (fun k : nat ⇒
binomial (Zabs nat n) k × (x ** (Zabs nat n - k ) × a ** k )) mod n = 0).
replace (pred (pred (Zabs nat n))) with (Zabs nat n - 2)%nat; auto with zarith.
apply inv sum nm.
intros.
rewrite Zmod plus eq; auto with zarith.
rewrite H3 ; rewrite H4 ; auto with zarith.
intros.
rewrite Zmod mult; auto with zarith.
O núcleo da prova:
n
k
mod n = 0, com 0 < k < n e n primo.
rewrite bin mod n prime; auto with zarith.
apply inj le in H3.
rewrite inj minus1 in H3 ; auto with zarith.
rewrite Z of nat Zabs nat in H3 ; auto with zarith.
simpl in H3.
rewrite inj plus.
replace (Z of nat 1) with 1; auto with zarith.
rewrite H3.
simpl.
rewrite ← Zmod plus eq; auto with zarith.
Ajuste final, com o Pequeno Teorema de Fermat: (X n + an ) = (X n + a) mod n
quando n é primo.
rewrite fermat little def ; auto with zarith.
Provas finais, remanescentes dos passos anteriores.
rewrite
rewrite
rewrite
rewrite
Zpower Zpower nat; auto with zarith.
Zmod plus eq; auto with zarith.
Zmod mod ; auto with zarith.
← Zmod plus eq; auto with zarith.
76
rewrite Zplus comm; auto with zarith.
symmetry.
rewrite Zmod plus eq; auto with zarith.
rewrite ← Zpower Zpower nat; auto with zarith.
replace (a**0) with 1; auto with zarith.
rewrite Zmult 1 r ; rewrite Zmult 1 l ; auto with zarith.
rewrite Zmult 1 l.
replace (minus (Zabs nat n) (Zabs nat n)) with 0%nat; auto with zarith.
replace (x **0) with 1; auto with zarith.
rewrite Zpower Zpower nat; auto with zarith.
Qed.
Prova congr AKS step 1, correspondente à ida da identidade 2.10
Ao chegar à Etapa 4, o algoritmo AKS verifica a congruência (X +a)n = X n +a
(mod X r −1, n) para todo a entre 1 e r. Como estamos tratando o polinômio X r −1
como expressão de tipo inteiro, com a variável X quantificada universalmente,
temos que descartar a possibilidade de ser igual 0.
Definition congr AKS (n r :Z ): Prop:=
∀ x a: Z, (x ˆr -1) 6= 0 → 1≤a≤r →
Zis mod (((x +a)ˆn) mod n) (((x ˆn)+a) mod n) (x ˆr -1).
Se n é primo, então congr AKS se verifica para qualquer a.
Lemma congr AKS step 1 : ∀ n r : Z, prime n → congr AKS n r.
Prova
unfold congr AKS.
intros.
generalize H ; intros; apply prime le 2 in H.
Incluı́mos a assertiva de que (X + a)n = X n + a (mod n), pois temos essa prova
para quando n é primo.
assert (Zis mod ((x + a)ˆn) (x ˆn + a) n).
apply gen lit fermat step 1 ; auto with zarith.
apply Zis mod def inv in H3 ; auto with zarith.
Já que xr − 1 6= 0, temos apenas dois casos a considerar.
assert ((x ˆr -1) < 0 ∨ (x ˆr -1) > 0).
elim Ztrichotomy with (x ˆr -1) 0; auto with zarith.
elim H4.
77
Caso X r − 1 < 0, tratamos o sinal e depois substituı́mos a assertiva (X + a)n =
X n + a (mod n).
intros.
unfold Zis mod.
apply Zdivide opp l rev.
apply Zis mod def ; auto with zarith.
rewrite H3 ; auto with zarith.
Caso X r − 1 > 0, apenas substituı́mos a assertiva (X + a)n = X n + a(mod n).
intros.
apply Zis mod def ; auto with zarith.
rewrite H3 ; auto with zarith.
Qed.
Especificação e prova final do AKS
AKS (n) é verdadeiro se, e somente se, n é inteiro maior que 1, n não é potência
de inteiro positivo maior que 1 e de expoente maior que 1, e, sendo r o menor
primo positivo que não divide N (n), ou há um primo positivo q menor que r que
divide n e é igual a n, ou não existe primo positivo menor que r que divide n e a
congruência (X − a)n = (X n − a) (mod X r − 1, n) se verifica para todo 1 ≤ a ≤ r.
Definition AKS (n: Z ): Prop:=
n>1 ∧ ¬p pow n ∧ (∃ r :Z, min r r (N n) ∧ (q div eq r n ∨ (q not div r n
∧ congr AKS n r ))).
p é um inteiro primo positivo se, e somente se, AKS (p) for verdadeiro.
Theorem AKS Valid : ∀ p:Z, prime p ↔ AKS p.
Prova
intros.
Iniciamos decompondo a bi-implicação.
unfold iff.
Ida: se p é um inteiro primo positivo, então AKS (p) é verdadeiro.
split; intros.
AKS (p) = p > 1 ∧ ¬(p pow n) ∧ (∃r:Z, (min r r (N n) ∧ (q div eq r n ∨
(q not div r n ∧ congr AKS n)))).
78
unfold AKS.
Se p é primo, então p > 1.
split; generalize H ; intros; apply prime le 2 in H ; auto with zarith.
Se p é primo, temos um prova de que p não é potência.
split.
red ; intros; apply p pow Valid in H1 ; auto with zarith.
Para todo inteiro positivo n, existe um menor primo positivo que não divide n.
Seja r o menor primo que não divide o positivo N (p). Provaremos depois que N
(p) realmente é positivo.
elim ex min r with (N p).
intros r H1 ; ∃ r ; split; auto with zarith.
Mas r ≤ p ou p < r.
elim Zle or lt with r p.
Supondo r ≤ p,
intros.
como p é primo positivo, não pode haver primo positivo q que divida p, pois os
únicos divisores de p são −p, p, 1 e −1.
right; split; unfold q not div ; intros.
red ; intros; apply prime divisors in H5 ; apply prime le 2 in H4 ; auto with
zarith.
A cong AKS (p r) = (X + a)p = X p + a (mod X r − 1, p) para todo a em
{1, 2, 3, ..., r}.
Mas, como p é primo, sabemos que a identidade irá valer para quaisquer valores
de r e a, como conseqüência da identidade (X + a)p = X p + a (mod p).
apply congr AKS step 1 ; auto with zarith.
Agora, suponha p < r.
intros.
Neste caso, há um primo menor que r que divide p: o próprio p.
79
left; unfold q div eq; ∃ p; auto with zarith.
Tudo isso foi baseado no fato de que N (p) seria positivo.
Então, segue a prova.
unfold N ; auto with zarith.
Volta: se AKS (p) é verdadeiro, então p é um inteiro primo positivo.
AKS (p) = p > 1 ∧ ¬(p pow n) ∧ (∃r:Z, (min r r (N n) ∧ (q div eq r n ∨
(q not div r n ∧ congr AKS n)))).
unfold AKS in H ; intuition.
Há dois casos a considerar: suponha que há um primo x menor que r que divide
n e é igual a n.
elim H2 ; clear H2 ; intros r H1 ; intuition.
elim H1 ; clear H1 ; intros; intuition.
Logo, se x = p e x é primo, então p é primo.
rewrite H6 in H1 ; auto with zarith.
Por outro lado, se não existe primo menor que r que divide n e a congruência for
verificada para todo 1 ≤ a ≤ r, então p é potência de primo positivo, desde que
todo 1 ≤ a ≤ r seja relativamente primo a p. Supondo esta última propriedade:
apply congr AKS step 2 in H4 ; auto with zarith.
Mas se p não é potência, pois passou pela Etapa 1, então o expoente não deve ser
maior que 1.
elim H4 ; clear H4 ; intros;
elim H1 ; clear H1 ; intros;
elim H4 ; clear H4 ; intros;
elim H4 ; clear H4 ; intros;
assert (∼x0 >1)%nat.
Prova por absurdo: se o expoente é maior que 1, então p é potência, contrariando
o que temos.
red ; intros; assert (p pow p); unfold p pow.
∃ x ; split; apply prime le 2 in H1 ; auto with zarith; ∃ x0 ; split; auto with zarith.
80
Absurdo.
auto with zarith.
Portanto, se o expoente é maior que 0 e não é maior que 1, sobra o caso de ser
igual a 1.
assert (x0 =1)%nat; auto with zarith.
Logo, p = x1 , mas, como x é primo, p tem que ser primo.
rewrite H7 in H5.
unfold Zpower nat in H5 ; unfold iter nat in H5.
rewrite Zmult 1 r in H5.
rewrite H5 ; auto with zarith.
Fizemos isso baseados no fato de todo 1 ≤ a ≤ r ser relativamente primo a p.
Como não há primo positivo menor que r que divida p, e r também não divide p,
segue imediatamente a prova.
intros.
apply q not div Valid1 with p r a in H3 ; auto with zarith.
Qed.
81
Conclusão
Neste trabalho, apresentamos uma prova formal da correção do algoritmo AKS
feita em Coq. A prova ainda não está completa, pois falta a formalização da álgebra necessária à prova congr AKS step 2, que corresponde à volta da identidade
(2.10). Mas, além de termos formalizado uma prova geral para o AKS, terminamos uma das partes fundamentais do algoritmo, a prova congr AKS step 1,
que corresponde à ida da identidade (2.10). Além disso, as Etapas 1, 2 e 3 estão totalmente formalizadas. Portanto, provamos, formalmente, que o algoritmo
AKS responde corretamente quando recebe um primo como entrada, e estamos
a caminho da formalização completa da correção do algoritmo, o que pode ser
realizado por qualquer pessoa interessada no assunto e que queira contribuir com
o projeto.
Como provas auxiliares, temos, ainda, a formalização da ida do Lema 2.3.1, a
prova do Corolário 2.2.3, uma prova da existência de infinitos números primos, a
prova da existência, para um inteiro positivo n, do menor primo positivo que não
divide n, além da formalização de outras estruturas algébricas menores importantes ao algoritmo, que podem ser úteis a projetos futuros, incluindo software
certificado.
Esperamos ter construı́do, também, um texto que sirva tanto de referência ao
bom entendimento do algoritmo AKS, explicando, em uma linguagem mais clara
para o estudante de graduação, as suas caracterı́sticas e os principais teoremas
relacionados, como uma pequena introdução ao uso do assistente de prova Coq,
assunto com pouquı́ssimas referências em lı́ngua portuguesa.
Ressaltamos, ainda, que todas as provas desenvolvidas para este projeto serão
disponibilizadas sob licença GNU LGPL, para que possam ser reutilizadas em
outros projetos.
82
Capı́tulo 6
Alguns trabalhos
Artigos
• [Agrawal et al., 2004] - A versão final do artigo PRIMES is in P, em que o
algoritmo AKS é apresentado com a alteração proposta por Lenstra Jr..
• [Bernstein, 2003] - Trabalho ainda não publicado, de Daniel Bernstein, especialista em criptografia de dados. É um dos mais referenciados quando o
assunto é o algoritmo AKS, pois possui explicação e prova bem elegantes e
completas, mas sem explicar os fundamentos algébricos menores.
• [Bernstein, 2004] - Nesse trabalho, Bernstein apresenta um algoritmo probabilı́stico que certifica a primalidade de um número e que utiliza a idéia
do algoritmo AKS.
• [Crandall and Papadopoulos, 2003] - Uma discussão sobre uma possı́vel implementação do algoritmo AKS, referenciado em muitos trabalhos quando
se tem essa preocupação especı́fica.
• [Agrawal and Biswas, 2003] - A idéia central do AKS surgiu desse artigo,
em que o Lema 2.3.1 aparece pela primeira vez, como base para um teste
probabilı́stico de primalidade.
• [Barendregt and Geuvers, 2001] - Uma introdução ao uso de assistentes de
prova, com foco na utilização da teoria de tipos para formalizar propriedades
matemáticas.
Livros
• [Coutinho, 2004] - Pensando em explicar o algoritmo AKS, bem como todos
os fundamentos algébricos relacionados, Coutinho publicou esse livro que,
em grande parte, foi base para nosso trabalho. Sua única falha do livro,
segundo o próprio autor, é não possuir uma implementação do algoritmo.
• [Ribenboim, 2004] - Uma grande referência, citada em vários trabalhos
quando o assunto se trata dos números primos. Ribenboim nos apresenta
83
tudo que se possa pensar sobre os primos, de uma forma descontraı́da e até
bem humorada.
• [Cormen et al., 2001] - Um bom livro para estudantes que queiram compreender melhor a complexidade de algoritmos, que está diretamente ligada à
importância do algoritmo AKS.
• [Bertot and Castéran, 2004] - Primeiro livro sobre o assistente de prova Coq
e sua teoria do cálculo de construções indutivas. Importante para uma boa
compreensão do assistente, que inclui exemplos de estrutura, bem como sua
explicação formal.
• [Friedman, 2006] - A verificação matemática e sua importância são tratadas
neste trabalho, que as defende com argumentos lógicos e de forma apaixonada.
84
Apêndice A
Princı́pio da Indução Matemática
O Princı́pio da Indução Matemática, também conhecido como Princı́pio da
Indução Finita, é uma ferramenta útil para demonstrar propriedades e teoremas
que envolvem os números inteiros.
Teorema A.0.1 (Princı́pio da Indução Matemática). Seja P (n) uma sentença aberta no conjunto {n ≥ n0 | n, n0 ∈ Z} tal que:
(i) P (n0 ) é verdadeira;
(ii) para todo n ≥ n0 , P (n) é verdadeira implica que P (n + 1) é verdadeira.
Então, P (n) é verdadeira para todo n ≥ n0 .
Prova. Seja S = {n ∈ Z | n ≥ n0 e P (n) é falsa}. De acordo com o teorema,
o conjunto S é vazio. Vamos supor, por absurdo, que S 6= ∅. Neste caso, S é
um conjunto de inteiros limitado inferiormente por algum b ≥ n0 . De (i), n0 ∈
/ S,
logo b > n0 . Então, (b − 1) ≥ n0 . Como b é o menor elemento de S, sabemos que
(b − 1) ∈
/ S. Portanto, P (b − 1) é verdadeira. Logo, de (ii), P (b) é verdadeira.
Assim, b ∈
/ S, o que é uma contradição à nossa hipótese.
Para utilizarmos o Princı́pio da Indução Matemática na demonstração de alguma proposição que envolva números inteiros, temos que checar a propriedade
(i), chamada base de indução, e a propriedade (ii), chamada passo indutivo.
Exemplo A.0.2.
12 + 22 + 32 + 42 + ... + n2 =
n3 n2 n
+
+ .
3
2
6
Prova. Vamos dividir a prova em duas partes:
(i) Base de indução
Podemos verificar que a equação (A.1) é válida para n = 1:
12 =
13 12 1
+
+ = 1.
3
2
6
85
(A.1)
(ii) Passo indutivo
Se, para qualquer n, temos
12 + 22 + ... + n2 =
n3 n2 n
+
+ ,
3
2
6
então, adicionando (n + 1)2 , teremos
n3 n2 n
+
+ + (n + 1)2 .
3
2
6
(A.2)
(n + 1)3 (n + 1)2 n + 1
+
+
.
3
2
6
(A.3)
12 + 22 + ... + n2 + (n + 1)2 =
Mas, de acordo com (A.1),
12 + 22 + ... + n2 + (n + 1)2 =
Então, temos que verificar se (A.2) e (A.3) são iguais:
(n + 1)3 (n + 1)2 n + 1
n3 + 3n2 + 3n + 1 n2 + 2n + 1 n + 1
+
+
=
+
+
3
2
6
3
2
6
2
3
1 n
1
1
n
+ n2 + n + +
+n+ +n+
=
3
3
2
2
6
n3 n2 n 1 1 1
=
+
+ + + + + n2 + 2n
3
2
6 3 2 6
3
2
n
n
n
=
+
+ + n2 + 2n + 1
3
2
6
n3 n2 n
+
+ + (n + 1)2 .
=
3
2
6
Exemplo A.0.3.
1 + 2 + 3 + 4 + ... + n =
n(n + 1)
.
2
Prova.
(i) Base de indução
Podemos verificar que a equação (A.4) é válida para n = 1:
1=
1.2
= 1.
2
(ii) Passo indutivo
Se, para qualquer n, temos
1 + 2 + ... + n =
86
n(n + 1)
,
2
(A.4)
então, adicionando (n + 1), teremos
1 + 2 + ... + n + (n + 1) =
n(n + 1)
+ (n + 1).
2
(A.5)
(n + 1)(n + 2)
.
2
(A.6)
Mas, de acordo com (A.4),
1 + 2 + ... + n + (n + 1) =
Então, temos que verificar se (A.5) e (A.6) são iguais:
n(n + 1)
n(n + 1) 2n + 2
+n+1 =
+
2
2
2
2
n + n + 2n + 2
=
2
n2 + 3n + 2
=
2
(n + 1)(n + 2)
=
.
2
Para explicações adicionais, veja [Monteiro, 1978] e [Hefez, 2002].
87
Apêndice B
Teorema de Lagrange
Sejam n ≥ 2 ∈ Z, r um primo positivo, S o conjunto {1, 2, ..., r} e U (r) o grupo
abeliano dos elementos inversı́veis de Zr . Podemos construir o grupo quociente
U (r)/hn̄, p̄i e chamemos de d sua ordem (sua quantidade de elementos). Para
entendermos a estrutura desse grupo quociente, veja o seguinte
Exemplo B.0.4. Suponha r = 13, n = 12 e p = 3.
¯ 11,
¯ 12}.
¯
Neste caso, Zr = Z13 = {0̄, 1̄, 2̄, 3̄, 4̄, 5̄, 6̄, 7̄, 8̄, 9̄, 10,
O conjunto das
classes inversı́veis, isto é, que possuem inverso multiplicativo, de Z13 é U (13) =
¯ 11,
¯ 12}.
¯ As classes n̄ e p̄ correspondem a 12
¯ e 3̄, respecti{1̄, 2̄, 3̄, 4̄, 5̄, 6̄, 7̄, 8̄, 9̄, 10,
vamente. A classe 3̄ tem ordem 3, pois 3 é o menor expoente positivo k de 3̄, tal
¯ é 2 (12
¯ 2 = 144
¯ = 1̄). O subgrupo hn̄, p̄i passa a ser
que 3̄k = 1̄. Já a ordem de 12
¯ 3̄i (gerado pelas classes 12
¯ e 3̄, e corresponde às multiplicações das potências
h12,
¯
¯ 3̄i = {1̄, 3̄, 4̄, 9̄, 10,
¯ 12},
¯ um subgrupo de U (13),
de 12 e 3̄ em Z13 ). Ou seja, h12,
conforme a Tabela B.1. Veja que não precisamos multiplicar as potências com
expoente acima da ordem das classes, pois voltaremos a ter os mesmos valores.
Tabela Multiplicativa das Potências
¯0
¯1
×
12
12
¯
¯
3̄0 1̄ × 1̄ = 1̄ 1̄ × 12
= 12
1
¯
¯
¯
3̄ 3̄ × 1̄ = 3̄ 3̄ × 12 = 36
= 10
2
¯ = 108
¯
3̄ 9̄ × 1̄ = 9̄ 9̄ × 12
=
4̄
¯ 3̄i
Tabela B.1: Elementos do conjunto h12,
¯ 3̄i é o conjunto dos elementos de U (13) separaO grupo quociente U (13)/h12,
¯ 3̄i. Em outras palavras,
dos por classes de restos pelos elementos do subgrupo h12,
¯ 3̄i} para cada
é o conjunto de todos os elementos x̂ = {ȳ ∈ U (13) : ȳ ≡ x̄ mod h12,
x̄ ∈ U (13). Podemos determinar esses elementos fazendo, para todo x̄ ∈ U (13),
¯ 3̄i}. Assim, fica evidente que 1̂ é igual a h12,
¯ 3̄i. Como a ordem
x̄ · h̄, h̄ ∈ h12,
(número de elementos) de U (13) = 12, vemos que U (13) tem 6 elementos fora
¯ 3̄i deve possuir pelo menos mais uma classe de equivalênde 1̂, então U (13)/h12,
¯ 3̄i, vamos começar por multiplicar 2̄
cias além de 1̂. Como 2̄ não está em h12,
¯
¯
¯ = 7̄, 24
¯ = 11}.
¯
por h12, 3̄i. Assim, obtemos {2̄, 6̄, 8̄, 18 = 5̄, 20
Ordenando, te¯ justamente as 6 classes que faltavam para completar U (13).
mos {2̄, 5̄, 6̄, 7̄, 8̄, 11},
88
Qualquer outra multiplicação de elementos de U (13) por elementos do seu sub¯ 3̄i resultará em um desses conjuntos de classes. Assim, o grupo quogrupo h12,
¯ 3̄i tem apenas 2 elementos, as classes 1̂ e 2̂, que representam
ciente U (13)/h12,
¯ 3̄i. Portanto, a
uma relação de equivalência dos elementos de U (13) módulo h12,
¯
ordem d de U (13)/h12, 3̄i é 2.
¯ 3̄i gera conjuntos disjuntos
De fato, isso era esperado, pois o subgrupo h12,
das classes de equivalência de U (13). Cada conjunto tem, obviamente, o mesmo
¯ 3̄i, devido à forma que são gerados (multiplicando os
número de elementos de h12,
¯
elementos de h12, 3̄i pelos de U (13)). Como são disjuntos, ou seja, não possuem
elementos em comum, o número total de conjuntos de classes distintas de U (13)
¯ 3̄i, isto é, a ordem do grupo quociente U (13)/h12,
¯ 3̄i, será igual ao
módulo h12,
¯ 3̄i. No
número de elementos de U (13) dividido pelo número de elementos de h12,
nosso caso, 12/6 = 2. Isso será usado para provar o
Teorema B.0.5 (Teorema de Lagrange). Em um grupo finito, a ordem de
qualquer subgrupo divide a ordem do grupo.
Prova. Sejam (G, ?) um grupo finito e H um subgrupo de G. Mas G é a união
das classes de equivalência distintas em relação a congruência módulo H. Além
disso, duas classes de equivalência distintas são disjuntas. Ou seja, considerando
C1 , C2 , · · · Ct todas as classes de equivalências distintas, temos
G = C1 ∪ C2 ∪ · · · ∪ Ct , onde Ci ∩ Cj = ∅.
Mas isso quer dizer que
|G| = |C1 | + |C2 | + · · · + |Ct |.
(B.1)
Seja e o elemento neutro de G. Então, ē é uma das classes de G/H e coincide
com H, pois ē = {e ? x : x ∈ H}.
Em particular, para cada elemento a ∈ G temos ā = {a ? x : x ∈ H}. Assim, ā
não pode ter mais elementos que H. E, para que tenha menos elementos, terı́amos
que ter a ? x1 = a ? x2 para ao menos dois elementos x1 e x2 distintos em H. Mas,
se isso fosse possı́vel, multiplicando a?x1 = a?x2 pelo inverso de a em G, terı́amos
que x1 = x2 em H, contrariando o fato de que seriam distintos. Logo, cada classe
ā possui a mesma quantidade de elementos de H.
Assim, de (B.1), temos:
|G| = |H| + |H| + · · · + |H|
|G| = t|H|.
Portanto, a ordem de G é múltipla da ordem de H, completando a prova.
Corolário B.0.6. A ordem de um elemento inversı́vel de um grupo divide a
ordem do grupo.
Prova. Sejam (G, ?) um grupo finito, a um elemento inversı́vel de G e k a
ordem de a em G, ou seja, o menor expoente positivo tal que ak = e, onde e é o
89
elemento neutro de G. Neste caso, o subconjunto hai, gerado pelas potências de
a, é {e, a, a2 , ..., ak−1 }. Potências a partir de k serão repetidas em hai. Logo, hai
tem k elementos distintos (sua ordem é k). Além disso, hai é um subgrupo de G,
pois e ∈ hai, ar ? as = ar+s ∈ hai e (as )−1 = a−s ∈ hai. Assim, a ordem k de
a em G, que é igual ordem de hai, divide a ordem de G, como conseqüência do
Teorema de Lagrange.
90
Referências
[Agrawal and Biswas, 2003] Agrawal, M. and Biswas, S. (2003). Primality and
Identity Testing via Chinese Remaindering. Journal of the ACM, 50(3):429–
443.
[Agrawal et al., 2004] Agrawal, M., Kayal, N., and Saxena, N. (2004). PRIMES
is in P. Annals of Mathematics, 160(2):781–793.
[Almeida and Thery, 1999] Almeida, J. C. and Thery, L. (1999). Correctness of
RSA algorithm.
http://coq.inria.fr/contribs/RSA.html.
[Bach and Shallit, 1996] Bach, E. and Shallit, J. (1996). Algorithmic Number
Theory. MIT Press.
[Barendregt and Geuvers, 2001] Barendregt, H. and Geuvers, H. (2001). Proofassistants using Dependent Type Systems. In Handbook of Automated
Reasoning, pages 1149–1238.
http://citeseer.ist.psu.edu/article/barendregt99proofassistant.html.
[Barendregt, 1992] Barendregt, H. P. (1992). λ-Calculi with Types. Handbook
of Logic in Computer Science, II.
[Bernstein, 2003] Bernstein, D. J. (2003).
Kayal-Sexena.
http://cr.yp.to/papers/aks.pdf.
Proving Primality After Agrawal-
[Bernstein, 2004] Bernstein, D. J. (2004). Proving Primality in Essentially Quartic Random Time.
http://cr.yp.to/primetests/quartic-20060914-ams.pdf.
[Bertot and Castéran, 2004] Bertot, Y. and Castéran, P. (2004). Interactive
Theorem Proving and Program Development. Springer, Sophia Antipolis.
[Campello and Leal, 2007] Campello, A. C. and Leal, I. (2007). Teoria Aritmética
dos Números e Criptografia RSA.
http://www.ime.unicamp.br/∼ftorres/ENSINO/MONOGRAFIAS/antonio RSA .pdf.
[Cormen et al., 2001] Cormen, T. H., Leiserson, C. E., Rivest, R. L., and Stein,
C. (2001). Introduction to Algorithms. MIT Press, Cambridge, MA.
91
[Coutinho, 2004] Coutinho, S. C. (2004). Primalidade em Tempo Polinomial.
Coleção Iniciação Cientı́fica. SBM, Rio de Janeiro.
[Crandall and Papadopoulos, 2003] Crandall, R. and Papadopoulos, J. (2003).
On the implementation of AKS-class primality tests.
http://developer.apple.com/hardware/ve/pdf/aks3.pdf.
[de Moura and Peixoto, 2008] de Moura, F. L. C. and Peixoto, R. (2008). A Verificação Formal do Algoritmo AKS em Coq. Site do Professor Flávio Leonardo
Cavalcanti de Moura - CIC/UnB.
http://www.cic.unb.br/∼flavio/AKS.
[Friedman, 2006] Friedman, H. M. (2006). Adventures in the Verification of
Mathematics. In Computer Science Colloquium. Ohio State University.
[Hefez, 2002] Hefez, A. (2002).
Curso de Álgebra, volume 1 of Coleção
Matemática Universitária. IMPA, Rio de Janeiro, 3 edition.
[Hopcroft et al., 2001] Hopcroft, J. E., Motwani, R., and Ullman, J. D. (2001).
Introduction to Automata Theory, Languages and Computation. AddisonWesley, 2 edition.
[INRIA, 2008] INRIA (2008). Coq Contribs: Project Info.
http://gforge.inria.fr/projects/coq-contribs.
[Knuth, 1968] Knuth, D. E. (1968). Seminumerical Algorithms, volume 1 of The
Art Of Computer Programming. Addison-Wesley, Reading.
[López-Ortiz, 1997] López-Ortiz, A. (1997). Frequently Asked Questions in
Mathematics.
http://www.cs.uwaterloo.ca/∼alopez-o/math-faq/math-faq.html.
[Monteiro, 1978] Monteiro, L. H. J. (1978). Elementos de Álgebra. Livros Técnicos e Cientı́ficos, Rio de Janeiro, 2 edition.
[Paulson and Nipkow, 2008] Paulson, L. and Nipkow, T. (2008). Isabelle Home
Page. University of Cambridge and Technical University of Munich.
http://isabelle.in.tum.de/.
[Research Triangle Institute, 2002] Research Triangle Institute (2002). The Economic Impacts of Inadequate Infrastructure for Software Testing. Sponsored
by the Department of Commerce’s National Institute of Standards and Technology.
[Ribenboim, 1995] Ribenboim, P. (1995).
Records. Springer, New York, NY.
The New Book of Prime Number
[Ribenboim, 2004] Ribenboim, P. (2004). The Little Book of Bigger Primes.
Springer, New York, NY, 3 edition.
92
[Santos et al., 2002] Santos, P., Neto, R. X., and Enoque, T. (2002). Uma tentativa de implementação do algoritmo de primalidade “AKS”. Site do Professor
Pedro Rezende, CIC-UnB.
http://www.cic.unb.br/docentes/pedro/trabs/primal.htm.
[Schoof, 2003] Schoof, R. (2003). Agrawal-Kayal-Saxena primality test.
http://www.mat.uniroma2.it/∼schoof/agrawalma.pdf.
[SRI International, 2008] SRI International (2008). PVS Specification and Verification System.
http://pvs.csl.sri.com/index.shtml.
[The Coq Development Team, 2008a] The Coq Development Team (2008a). The
Coq Distribution. INRIA-Rocquencourt.
http://coq.inria.fr/distrib-eng.html.
[The Coq Development Team, 2008b] The Coq Development Team (2008b). The
Coq Proof Assistant. INRIA-Rocquencourt.
http://coq.inria.fr.
[The Coq Development Team, 2008c] The Coq Development Team (2008c).
The Coq Proof Assistant Documentation. INRIA-Rocquencourt.
http://coq.inria.fr/doc-eng.html.
[The Coq Development Team, 2008d] The Coq Development Team (2008d).
The Coq Proof Assistant Reference Manual. INRIA-Rocquencourt.
http://coq.inria.fr/V8.1pl3/refman/index.html.
[The Coq Development Team, 2008e] The Coq Development Team (2008e). The
Coq Proof Assistant Related Tools. INRIA-Rocquencourt.
http://coq.inria.fr/tools-eng.html.
[The Coq Development Team, 2008f] The Coq Development Team (2008f). The
Coq Standard Library. INRIA-Rocquencourt.
http://coq.inria.fr/library-eng.html.
[The Coq Development Team, 2008g] The Coq Development Team (2008g). The
Coq User Contributions. INRIA-Rocquencourt.
http://coq.inria.fr/contribs-eng.html.
[Thery, 2004] Thery, L. (2004). Numbers equal to the sum of two square numbers.
http://coq.inria.fr/contribs/SumOfTwoSquare.html.
[Thery and Boldo, 2001] Thery, L. and Boldo, S. (2001). Library for floating
point numbers.
http://coq.inria.fr/contribs/Float.html.
[Tou and Alexander, 2005] Tou, C.-S. and Alexander, T. (2005). AKS Algorithm.
http://padic.mathstat.uottawa.ca/∼MAT3166/reports/AKS.pdf.
93
[Wiedijk, 2008] Wiedijk, F. (2008). Formalizing 100 Theorems.
http://www.cs.ru.nl/∼freek/100/index.html.
94

Documentos relacionados