Escolar Documentos
Profissional Documentos
Cultura Documentos
Versão 2.3
Pense Complexidade
Versão 2.3
Allen B. Downey
Permission is granted to copy, distribute, transmit and adapt this work under a
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
License: http://creativecommons.org/licenses/by-nc-sa/4.0/.
http://greenteapress.com/complexity
iv
Sumário
Prefácio xi
0.1 Para quem é este livro? . . . . . . . . . . . . . . . . . . . . . xiii
0.2 Usando o código . . . . . . . . . . . . . . . . . . . . . . . . . xiii
1 Ciência da Complexidade 1
1.1 Mudança de paradigma? . . . . . . . . . . . . . . . . . . . . 3
1.2 Os eixos dos modelos científicos . . . . . . . . . . . . . . . . 5
1.3 Um novo tipo de modelo . . . . . . . . . . . . . . . . . . . . 7
1.4 Um novo tipo de engenharia . . . . . . . . . . . . . . . . . . 8
1.5 Um novo tipo de pensamento . . . . . . . . . . . . . . . . . 9
2 Grafos 11
2.1 O que é um grafo? . . . . . . . . . . . . . . . . . . . . . . . 11
2.2 NetworkX . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3 Grafos aleatórios . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4 Gerando grafos . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.5 Grafos conectados . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6 Gerando grafos ER . . . . . . . . . . . . . . . . . . . . . . . 21
2.7 Probabilidade de conectividade . . . . . . . . . . . . . . . . 22
2.8 Análise de algoritmos de grafos . . . . . . . . . . . . . . . . 25
vi SUMÁRIO
2.9 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5 Autômatos Celulares 71
SUMÁRIO vii
5.1 Um AC simples . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.2 A experiência de Wolfram . . . . . . . . . . . . . . . . . . . 72
5.3 Classificação de ACs . . . . . . . . . . . . . . . . . . . . . . 74
5.4 Aleatoriedade . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.5 Determinismo . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.6 Espaçonaves . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.7 Universalidade . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.8 Falseabilidade . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.9 Isso é um modelo de quê? . . . . . . . . . . . . . . . . . . . 83
5.10 Implementação de ACs . . . . . . . . . . . . . . . . . . . . . 85
5.11 Correlação cruzada . . . . . . . . . . . . . . . . . . . . . . . 87
5.12 Tabelas AC . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.13 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6 Jogo da Vida 93
6.1 Jogo da Vida de Conway . . . . . . . . . . . . . . . . . . . . 93
6.2 Padrões de vida . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.3 Conjectura de Conway . . . . . . . . . . . . . . . . . . . . . 97
6.4 Realismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.5 Instrumentalismo . . . . . . . . . . . . . . . . . . . . . . . . 101
6.6 Implementando Vida . . . . . . . . . . . . . . . . . . . . . . 102
6.7 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
11 Evolution 177
11.1 Simulating evolution . . . . . . . . . . . . . . . . . . . . . . 178
11.2 Fitness landscape . . . . . . . . . . . . . . . . . . . . . . . . 179
11.3 Agents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
11.4 Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
11.5 No differentiation . . . . . . . . . . . . . . . . . . . . . . . . 183
11.6 Evidence of evolution . . . . . . . . . . . . . . . . . . . . . . 183
11.7 Differential survival . . . . . . . . . . . . . . . . . . . . . . . 186
11.8 Mutation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
11.9 Speciation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
11.10 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
13 Outro 213
Este livro trata principalmente sobre ciência da complexidade, mas trata tam-
bém sobre estruturas de dados e algoritmos, programação intermediária em
Python, modelagem computacional e filosofia da ciência:
Se você ainda não estiver familiarizado com Python, pode ser de seu inte-
resse começar com o meu outro livro, Pense Python, que é uma introdução
ao Python para aqueles que nunca programaram, ou com o livro Aprendendo
Python de Mark Lutz, que pode ser mais adequado para aqueles que já têm
alguma experiência com programação.
• Você pode simplesmente clonar o meu repositório. Para isso você não
precisa de uma conta no GitHub, mas você não será capaz de escrever
suas alterações de volta para o GitHub.
• Se você não quiser usar o Git de maneira nenhuma, você pode baixar
os arquivos em um arquivo Zip usando o botão verde que diz “Clone or
download”.
Todo o código foi escrito para funcionar tanto em Python 2 como em Python
3 sem a necessidade de tradução.
Apesar de esses pacotes serem bastante usados, eles não estão incluídos em
todas as instalações de Python e pode ser difícil instalá-los em alguns ambien-
tes. Se você tiver problemas instalando-os, eu recomendo usar Anaconda ou
uma das outras distribuições Python que incluem esses pacotes.
Lista de Contribuidores
Se você tiver uma sugestão ou correção, por favor envie um email para
downey@allendowney.com3 . Se eu fizer uma alteração baseada no seu co-
mentário, eu acrescentarei o seu nome à lista de contribuidores (a não ser que
você peça para ser omitido).
Se você incluir pelo menos uma parte da frase em que o erro aparece, facilitará
que eu o encontre. Números de página e seção também servem, mas com elas
não é tão fácil de se trabalhar. Obrigado!
• John Harley, Jeff Stanton, Colden Rouleau e Keerthik Omanakuttan são es-
tudantes de Modelagem Computacional que apontaram erros de digitação.
• Phillip Loh, Corey Dolphin, Noam Rubin e Julian Ceipek encontraram erros
de digitação e fizeram sugestões úteis.
Outras pessoas que relataram erros são Richard Hollands, Muhammad Najmi bin
Ahmad Zabidi, Alex Hantman e Jonathan Harford.
3
Comentários a respeito da edição em português do livro devem ser encaminhados para
rodrigo.carlson@ufsc.br
Capítulo 1
Ciência da Complexidade
Eu não acho que complexidade seja algo novo por aplicar ferramentas da ci-
ência para um novo assunto, mas porque usa ferramenta diferentes, permite
tipos diferentes de trabalho e, em última análise, muda o que queremos dizer
com “ciência”.
A maioria das pessoas acha este tipo de explicação satisfatória. Ela inclui
uma derivação matemática—então tem algum rigor de uma prova—e explica
1
N.T.: Um Novo Tipo de Ciência, livro sem tradução para a língua portuguesa.
2 Capítulo 1 Ciência da Complexidade
Se você começar com uma cidade simulada que é totalmente não segregada e
executar o modelo por um curto período de tempo, agrupamentos de agentes
similares aparecem. Com o passar do tempo, os agrupamentos crescem e
coalescem até que haja uma pequena quantidade de agrupamentos grandes e
a maioria dos agentes viva em vizinhanças homogêneas.
Para piorar as coisas, o modelo de Schelling não recorre a nenhuma lei da física,
usa apenas computação simples, e não derivação matemática. Modelos como
o de Schelling não se parecem com ciência clássica e muitas pessoas os acham
menos convincentes, pelo menos no início. Mas, como vou tentar demonstrar,
esses modelos fazem trabalhos úteis, incluindo predição, explicação e projeto.
Um dos objetivos deste livro é explicar como.
pelos quais os modelos são julgados e nos tipos de modelos que são considerados
aceitáveis.
Nem todos acham esses modelos satisfatórios. Por exemplo, em Sync4 , Steven
Strogatz escreve sobre seu modelo de sincronização espontânea em algumas es-
pécies de vaga-lumes. Ele apresenta uma simulação que demonstra o fenômeno
e então escreve:
(Sync: Como a ordem surge do caos no universo, natureza e dia-a-dia), livro sem tradução
para a língua portuguesa.
1.2 Os eixos dos modelos científicos 5
dizer que o Strogatz está errado, mas sim que as pessoas têm opiniões dife-
rentes sobre quais perguntas fazer e quais ferramentas usar para respondê-las.
Essas opiniões são baseadas em julgamentos de valor, portanto não há motivo
para esperar que estejam de acordo.
Eu alego, e esta é a tese central deste livro, que os critérios em que se baseia
este consenso muda ao longo do tempo, e que o surgimento da ciência da
complexidade reflete a mudança gradual nesses critérios.
Análise → computação
Essas são generalização, então não devemos levá-las muito a sério. E não é
minha intenção denegrir a ciência clássica. Um modelo mais complicado não
é necessariamente melhor; na verdade, geralmente é pior.
Além disso, eu não estou dizendo que essas mudanças são abruptas ou comple-
tas. Pelo contrário, há uma migração gradual na fronteira do que é considerado
um trabalho aceitável, respeitável. Algumas ferramentas que costumavam ser
consideradas duvidosas agora são comuns, enquanto alguns modelos ampla-
mente aceitos são agora considerados com escrutínio.
Por exemplo, quando Appel e Haken provaram o teorema das quatro cores
em 1976, eles usaram um computador para enumerar 1936 casos especiais que
6
Este livro não cobre caos, mas você pode ler a respeito em https://pt.wikipedia.
org/wiki/Caos (http://en.wikipedia.org/wiki/Chaos).
7
N.T.: Sem artigo correspondente na Wikipédia.
1.3 Um novo tipo de modelo 7
Projeto → busca Engenharia é descrita algumas vezes com uma busca por
soluções num cenário de projetos possíveis. Cada vez mais, o processo de
busca pode ser automatizado. Por exemplo, algoritmos genéticos explo-
ram uma gama enorme de projetos e descobrem soluções que engenheiros
humanos não imaginariam (ou gostariam). O melhor algoritmo genético,
evolution, sabidamente gera projetos que violam as regras da engenharia
humana.
Este capítulo é uma visão geral dos temas que serão vistos no livro, mas nem
tudo terá sentido até que você veja os exemplos. Quando chegar ao final do
livro, você pode achar útil ler este capítulo novamente.
Capítulo 2
Grafos
Os três primeiros capítulos deste livro são sobre modelos que descrevem siste-
mas formados por componentes e conexões entre componentes. Por exemplo,
em uma rede social, os componentes são pessoas e as conexões representam
amizades, relações comerciais, etc. Em uma rede trófica, os componentes são
espécies e as conexões representam relações predador-presa.
Bob
Alice
Chuck
Albany
3
Boston
4
4
NYC
Philly
Os grafos também são úteis, porque existem muitos problemas do mundo real
que podem ser resolvidos usando algoritmos de grafos. Por exemplo, o algo-
ritmo de caminho mais curto de Dijkstra é uma maneira eficiente de encontrar
o caminho mais curto entre um nó e todos os outros nós em um grafo. Um
caminho é uma sequência de nós com uma aresta entre cada par consecutivo.
O grafo não direcionado da Figura 2.2 mostra quatro cidades no nordeste dos
Estados Unidos; as etiquetas nas arestas indicam tempo de viagem em horas.
Neste exemplo, o posicionamento dos nós corresponde aproximadamente à
geografia das cidades, mas, em geral, o desenho de um grafo é arbitrário.
14 Capítulo 2 Grafos
2.2 NetworkX
Para representar grafos, usaremos um pacote chamado NetworkX, que é a bi-
blioteca de rede mais utilizada em Python. Você pode ler mais sobre ela em
https://networkx.github.io/, mas vou explicá-la à medida que avançar-
mos.
import networkx as nx
G = nx.DiGraph()
Neste ponto, G é um objeto DiGraph que não contém nós nem arestas. Podemos
acrescentar nós usando o método add_node:
G.add_node('Alice')
G.add_node('Bob')
G.add_node('Chuck')
>>> G.nodes()
['Alice', 'Bob', 'Chuck']
G.add_edge('Alice', 'Bob')
G.add_edge('Alice', 'Chuck')
G.add_edge('Bob', 'Alice')
G.add_edge('Bob', 'Chuck')
>>> G.edges()
[('Alice', 'Bob'), ('Alice', 'Chuck'),
('Bob', 'Alice'), ('Bob', 'Chuck')]
nx.draw_circular(G,
node_color=COLORS[0],
node_size=2000,
with_labels=True)
Esse é o código que eu usei para gerar a Figura 2.1. A opção with_labels
faz com que os nós sejam etiquetados. No próximo exemplo, veremos como
etiquetar as arestas.
Para gerar a Figura 2.2, eu começo com um dicionário que mapeia cada nome
da cidade para sua longitude e latitude aproximadas:
G = nx.Graph()
G.add_nodes_from(pos)
G.add_edges_from(drive_times)
nx.draw(G, pos,
node_color=COLORS[1],
node_shape='s',
node_size=2500,
with_labels=True)
pos é um dicionário que mapeia cada cidade para suas coordenadas; draw usa
esse dicionário para determinar a posição dos nós.
nx.draw_networkx_edge_labels(G, pos,
edge_labels=drive_times)
Em ambos os exemplos, os nós são strings, mas, em geral, podem ser qualquer
tipo hashable2 .
N.T.: Todos os tipos imutáveis do Python, bem como objetos que sejam instâncias
2
3 2
4 1
5 0
6 9
7 8
Para testar essa afirmação, nós desenvolveremos algoritmos para gerar grafos
aleatórios e verificar se eles estão conectados.
A função geradora a seguir recebe uma lista de nós e enumera todos os pares
distintos. Se você não está familiarizado com as funções geradoras, você pode
ler sobre elas em http://intermediatepythonista.com/python-generators4 .
def all_pairs(nodes):
for i, u in enumerate(nodes):
for j, v in enumerate(nodes):
if i>j:
yield u, v
Podemos usar all_pairs para construir um grafo completo:
def make_complete_graph(n):
G = nx.Graph()
nodes = range(n)
G.add_nodes_from(nodes)
G.add_edges_from(all_pairs(nodes))
return G
make_complete_graph recebe o número de nós, n, e devolve um novo Graph
com n nós e arestas entre todos os pares de nós.
O código a seguir faz um grafo completo com 10 nós e o desenha.
complete = make_complete_graph(10)
nx.draw_circular(complete,
node_color=COLORS[2],
node_size=1000,
with_labels=True)
A Figura 2.3 mostra o resultado. Em breve, modificaremos esse código para
gerar grafos ER, mas primeiro desenvolveremos funções para verificar se um
grafo está conectado.
A classe Graph fornece um método chamado neighbors que devolve uma lista
de vizinhos de um determinado nó. Por exemplo, no grafo completo que gera-
mos na seção anterior:
>>> complete.neighbors(0)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Inicialmente, o conjunto seen está vazio e criamos uma lista chamada stack
que mantém o registro dos nós que descobrimos, mas que ainda não foram
processados. Inicialmente, a pilha contém um único nó, start.
Quando a pilha está vazia, não podemos alcançar mais nós, então saímos do
laço e devolvemos seen.
Como exemplo, podemos encontrar todos os nós no grafo completo que podem
ser alcançados a partir do nó 0:
>>> reachable_nodes(complete, 0)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
Observe que o mesmo nó pode aparecer mais de uma vez na pilha; na ver-
dade, um nó com k vizinhos será adicionado à pilha k vezes. Mais tarde,
procuraremos por maneiras de tornar esse algoritmo mais eficiente.
def is_connected(G):
start = next(iter(G.nodes()))
reachable = reachable_nodes(G, start)
return len(reachable) == len(G)
reachable recebe o conjunto dos nós que podem ser alcançados a partir de
start. Se o tamanho deste conjunto for igual ao tamanho do grafo, significa
que podemos alcançar todos os nós e, portanto, que o grafo é conectado.
2.6 Gerando grafos ER 21
3 2
4 1
5 0
6 9
7 8
>>> is_connected(complete)
True
A função geradora a seguir enumera todas as arestas possíveis e usa uma função
auxiliar, flip, para escolher quais arestas devem ser adicionadas ao grafo:
def flip(p):
return random() < p
1.0
0.8
prob conectado
0.6
0.4
0.2
0.0
10 -1 10 0
p
1.0
n=30
n=100
0.8
n=300
prob conectado
0.6
0.4
0.2
0.0
10 -2 10 -1 10 0
p
Dos 10000 grafos ER gerados com esses parâmetros, 6498 são conectados.
Então estimamos que 65% deles são conectados. Eu escolhi 0,3 porque é um
valor próximo do valor crítico para o qual a probabilidade de conectividade vai
de quase de 0 para quase 1. De acordo com Erdős e Rényi, p∗ = ln n/n = 0,23.
import numpy as np
n = 10
ps = np.logspace(-2.5, 0, 11)
ys = [prob_connected(n, p) for p in ps]
Para calcular ys, uso uma abrangência de listas7 que itera os elementos de ps
e calcula, para cada valor de p, a probabilidade de que um grafo aleatório seja
conectado.
Se você ainda não está familiarizado com análise de algoritmos, você deveria
ler o Apêndice A antes de continuar.
Cada nó é adicionado a seen apenas uma vez, então o número total de adições
é n.
26 Capítulo 2 Grafos
Mas os nós podem ser adicionados a stack muitas vezes, dependendo de quan-
tos vizinhos eles têm. Se um nó tiver k vizinhos, ele é adicionado a stack k
vezes. Claro, já que se ele tem k vizinhos, significa que ele está conectado a k
arestas.
Assim, a ordem de crescimento para esta função é O(n + m), o que é uma ma-
neira conveniente de dizer que o tempo de execução cresce proporcionalmente
a n ou m, o que for maior.
2.9 Exercícios
O código para este capítulo está em chap02.ipynb, um Jupyter notebook no
repositório deste livro. Para obter mais informações sobre como trabalhar com
este código, consulte a Seção 0.2.
def is_connected(G):
start = next(G.nodes_iter())
reachable = reachable_nodes(G, start)
return len(reachable) == len(G)
Exercício 2.4 Na verdade, existem dois tipos de grafos ER. Aquele que
geramos neste capítulo, G(n, p), caracteriza-se por ter por dois parâmetros, o
número de nós e a probabilidade de uma aresta entre um par de nós.
Uma definição alternativa, denotada G(n, m), também é caracterizada por dois
parâmetros: o número de nós, n, e o número de arestas, m. Sob esta definição,
o número de arestas é fixo, mas sua posição é aleatória.
Repita as experiências que fizemos neste capítulo usando esta definição alter-
nativa. Seguem algumas sugestões sobre como proceder:
1. Escreva uma função chamada m_pairs que recebe uma lista de nós e o
número de arestas, m, e devolve uma seleção aleatória de m arestas. Uma
maneira simples de fazer isso é gerar uma lista de todas as arestas possíveis e
usar random.sample.
punha uma explicação para o fenômeno de pequeno mundo. Você pode baixá-lo
de http://www.nature.com/nature/journal/v393/n6684/abs/393440a0.html.
Watts e Strogatz começam com dois tipos de grafos que foram bem compre-
endidos: grafos aleatórios e grafos regulares. Em um grafo aleatório, os nós
são conectados aleatoriamente. Em um grafo regular, todos os nós possuem o
mesmo número de vizinhos. Eles consideram duas propriedades desses grafos,
agrupamento e comprimento do caminho:
Uma rede em anel é um tipo de grafo regular que Watts e Strogatz usam
como a base de seu modelo. Em uma rede em anel com n nós, os nós podem
ser organizados em um círculo com cada nó conectado aos k vizinhos mais
próximos.
3 2
4 1
5 0
6 9
7 8
Observe que make_ring_lattice usa divisão inteira para calcular halfk, tal
que, se k é ímpar, o resultado da divisão será arredondado para baixo e irá
gerar uma rede em anel com grau k-1. Provavelmente não é o que queremos,
mas é bom o suficiente por enquanto.
lattice = make_ring_lattice(10, 4)
3.4 Grafos WS
Para fazer um grafo Watts-Strogatz (WS), começamos com uma rede em anel e
“religamos” algumas das arestas. Em seu artigo, Watts e Strogatz consideram
as arestas em uma ordem específica e religam cada uma com probabilidade p.
Se uma aresta é religada, eles deixam o primeiro nó inalterado e escolhem o
segundo nó aleatoriamente. Eles não permitem auto-laço ou múltiplas arestas;
3.4 Grafos WS 35
isto é, você não pode ter uma aresta de um nó para ele mesmo, e você não
pode ter mais de uma aresta entre o mesmo par de nós.
Esta função não considera as arestas na ordem especificada por Watts e Stro-
gatz, mas isso não parece afetar os resultados.
A Figura 3.2 mostra grafos WS com n = 20, k = 4, para uma faixa de valores
de p. Quando p = 0, o gráfico é uma rede em anel. Quando p = 1, o grafo
é completamente aleatório. Como veremos, as coisas interessantes acontecem
entre esses valores.
36 Capítulo 3 Grafos de pequeno mundo
3.5 Agrupamento
O próximo passo é calcular o coeficiente de agrupamento, que quantifica a
tendência para que os nós formem cliques. Um clique é um conjunto de nós
que estão completamente conectados. Ou seja, existem arestas entre todos os
pares de nós do conjunto.
total = k * (k-1) / 2
exist = 0
for v, w in all_pairs(neighbors):
if G.has_edge(v, w):
exist +=1
return exist / total
def clustering_coefficient(G):
cc = np.mean([node_clustering(G, node) for node in G])
return cc
np.mean é uma função do NumPy que calcula a média dos números em uma
lista ou array.
>>> clustering_coefficient(lattice)
0.5
def path_lengths(G):
length_map = {k:v for (k,v) in nx.shortest_path_length(G)}
lengths = [length_map[u][v] for u, v in all_pairs(G)]
return lengths
def characteristic_path_length(G):
return np.mean(path_lengths(G))
Neste exemplo, todos os 3 nós estão conectados entre si, então o comprimento
médio do caminho é 1.
3.7 A experiência de WS
Agora estamos prontos para replicar a experiência de WS, que mostra que
para uma faixa de valores de p, um grafo WS possui alto agrupamento como
um grafo regular e comprimentos de caminhos curtos como um grafo aleatório.
1.0
0.8
C(p) / C(0)
0.6
0.4
Watts e Strogatz executaram sua experiência com n=1000 e k=10. Com esses
parâmetros, run_one_graph leva cerca de um segundo no meu computador.
A maior parte desse tempo é gasto calculando o comprimento médio dos ca-
minhos.
ps = np.logspace(-4, 0, 9)
Para cada valor de p, eu gero 3 grafos aleatórios e faço a média dos resultados.
A função a seguir executa a experiência:
L = []
C = []
for p, t in sorted(res.items()):
mpls, ccs = zip(*t)
mpl = np.mean(mpls)
cc = np.mean(ccs)
L.append(mpl)
C.append(cc)
A cada execução do laço, obtemos um valor de p e uma lista de pares (mpl, cc).
Nós usamos zip para extrair duas listas, mpls e ccs, e então computarmos
suas médias e adicioná-las a L e C, que são as listas de comprimentos dos
caminhos e de coeficientes de agrupamento, respectivamente.
L = np.array(L) / L[0]
C = np.array(C) / C[0]
Como resultado, existe uma ampla faixa de valores de p para a qual um grafo
WS possui as propriedades de um grafo de pequeno mundo, alto agrupamento
e comprimentos de caminho curtos.
É por isso que Watts e Strogatz propõem grafos WS como um modelo para
redes do mundo real que exibem o fenômeno de pequeno mundo.
Por fim, acho que esse tipo de explicação é atraente porque tem a forma de
uma prova matemática. Começa a partir de um conjunto de axiomas e o
resultado é derivado por lógica e análise. Mas é importante lembrar que a
prova pertence ao modelo e não ao mundo real. Ou seja, podemos provar
que um modelo idealizado de um planeta produz uma órbita elíptica, mas não
podemos provar que o modelo é próprio aos planetas reais (na verdade, não).
Muitos dos modelos deste livro são como o modelo de Watts e Strogatz: abs-
tratos, baseados em simulação e (pelo menos superficialmente) menos formais
do que os modelos matemáticos convencionais. Um dos objetivos deste livro é
refletir sobre as questões que esses modelos suscitam:
• Que tipo de trabalho esses modelos podem fazer: são preditivos ou ex-
plicativos, ou ambos?
Ao longo do livro irei oferecer minhas respostas a essas questões, mas elas são
preliminares e às vezes especulativas. Eu encorajo você a considerá-las com
ceticismo e a tirar as suas próprias conclusões.
Eu não disse isso antes, mas reachable_nodes faz uma busca em profundi-
dade10 . Agora vamos modificá-la para realizar uma busca em largura.
Para entender a diferença, imagine que você está explorando um castelo. Você
começa em uma sala com três portas marcadas A, B e C. Você abre a porta
C e descobre outra sala, com as portas marcadas D, E e F.
Qual porta você abre depois? Se você está se sentindo aventureiro, você pode
querer ir mais fundo no castelo e escolher D, E ou F. Isso seria uma busca em
profundidade.
Mas se você quisesse ser mais sistemático, você poderia voltar e explorar A e
B antes de D, E e F. Essa seria uma busca em largura.
Mas se quisermos fazer uma busca em largura, a solução mais simples é tirar
o primeiro elemento da pilha:
node = stack.pop(0)
Isso funciona, mas é lento. Em Python, tirar o último elemento de uma lista
requer tempo constante, mas tirar o primeiro elemento requer tempo linear
com o tamanho da lista. No pior dos casos, o comprimento da pilha é O(n), o
que torna esta implementação da busca em largura O(nm). Isto é muito pior
do que deveria ser, O(n + m).
Podemos resolver este problema com uma fila dupla, também conhecida como
deque. A característica importante de uma deque é que você pode adici-
onar e remover elementos do início ou do final em tempo constante. Para
ver como é implementada, veja https://pt.wikipedia.org/wiki/Deque_
(estruturas_de_dados)11 .
As diferenças são:
Esta versão volta a ser O(n + m). Agora estamos prontos para encontrar os
caminhos mais curtos.
Dijkstra é famoso (e notório) como o autor de uma série de ensaios sobre ciên-
cias da computação. Alguns, como “A Case against the GO TO Statement”15 ,
tiveram um efeito profundo na prática de programação. Outros, como “On
12
http://en.wikipedia.org/wiki/Dijkstra’s_algorithm
13
http://en.wikipedia.org/wiki/Semaphore_(programming)
14
N.T.: O Pequeno Livro de Semáforos, livro sem tradução para a língua portuguesa
15
N.T.: Um caso contra o comando GO TO.
3.10 O algoritmo de Dijkstra 45
the Cruelty of Really Teaching Computing Science”16 , são divertidos com sua
rabugice, mas menos eficazes.
Começamos com uma versão simplificada do algoritmo que considera que todas
as arestas têm o mesmo comprimento. A versão mais geral funciona com
qualquer comprimento não negativo de aresta.
queue.extend(neighbors)
return dist
marathoncode.blogspot.com.br/2012/07/sobre-crueldade-de-ensinar-realmente.
html
46 Capítulo 3 Grafos de pequeno mundo
E assim por diante. Se você está familiarizado com prova por indução, você
pode ver para onde isto está indo.
Nos exercícios no final deste capítulo, você escreverá uma versão do algoritmo
de Dijkstra usando busca em profundidade, e então você terá a chance de ver
o que dá errado.
3.11 Exercícios
Exercício 3.1 Em uma rede em anel, cada nó possui o mesmo número de
vizinhos. O número de vizinhos é chamado de grau do nó, e um grafo no qual
todos os nós têm o mesmo grau é chamado de grafo regular.
3.11 Exercícios 47
Todas as estruturas em anel são regulares, mas nem todos os grafos regulares
são estruturas em anel. Em particular, se k for ímpar, não podemos construir
uma rede em anel, mas talvez possamos construir um grafo regular.
Exercício 3.4 Na Seção 3.10, afirmei que o algoritmo de Dijkstra não fun-
ciona a menos que ele use busca em largura. Escreva uma versão da função
shortest_path_dijkstra que usa busca em profundidade e teste-a em alguns
exemplos para ver o que está errado.
Claro que uma opção é executar o algoritmo de Dijkstra n vezes, uma vez para
cada nó de inicial. E para algumas aplicações, essa solução provavelmente é
boa o suficiente. Mas existem alternativas mais eficientes.
17
N.T.: O artigo na Wikipédia não oferece a mesma subseção: https://pt.wikipedia.
org/wiki/Problema_do_caminho_mínimo.
50 Capítulo 3 Grafos de pequeno mundo
Capítulo 4
Neste capítulo, trabalharemos com dados de uma rede social online e usaremos
um grafo Watts-Strogatz para modelá-la. O modelo de WS tem características
de uma rede de pequeno mundo, como esses dados que temos de uma rede
social, mas tem baixa variabilidade no número de vizinhos de um nó para o
outro, ao contrário do que ocorre com esses dados.
O arquivo de dados contém uma linha por aresta, com usuários identificados
por números inteiros de 0 a 4038. O código a seguir lê o arquivo:
def read_graph(filename):
G = nx.Graph()
array = np.loadtxt(filename, dtype=int)
G.add_edges_from(array)
return G
O NumPy fornece uma função chamada loadtxt que lê o arquivo fornecido e
devolve o conteúdo como um array NumPy. O parâmetro dtype especifica o
tipo dos elementos do array.
2012.
4.1 Dados de redes sociais 53
Porém, para grafos maiores ambas as funções são muito lentas, levando um
tempo proporcional a nk 2 , com n o número de nós e k o número de vizinhos
aos quais cada nó está conectado.
C = average_clustering(fb)
54 Capítulo 4 Redes livres de escala
L = estimate_path_lengths(fb)
E o caminho médio é 3,7, que é bastante curto em uma rede de mais de 4000
usuários. É um mundo pequeno, afinal.
4.2 Modelo WS
No conjunto de dados do Facebook, o número médio de arestas por nó é de
cerca de 22. Como cada aresta está conectada a dois nós, o grau médio é o
dobro do número de arestas por nó:
>>> k = int(round(2*m/n))
>>> k
44
lattice = nx.watts_strogatz_graph(n, k, 0)
random_graph = nx.watts_strogatz_graph(n, k, 1)
0.025
0.25
0.020
0.20
0.015
FMP
FMP
0.15
0.010
0.10
0.005 0.05
0.000 0.00
4.3 Grau
Lembre-se de que o grau de um nó é o número de vizinhos a que está conectado.
Se o grafo WS for um bom modelo para a rede do Facebook, ele deve ter o
mesmo grau total (ou médio) e, idealmente, a mesma variância do grau entre
nós.
A função a seguir devolve uma lista de graus em um grafo, um para cada nó:
def degrees(G):
return [G.degree(u) for u in G]
O que está acontecendo aqui? Para obter uma visão melhor, temos que olhar
para a distribuição de graus, não apenas para a média e o desvio padrão.
G = nx.Graph()
G.add_edge(1, 0)
G.add_edge(2, 0)
G.add_edge(3, 0)
nx.draw(G)
>>> degrees(G)
[3, 1, 1, 1]
O nó 0 tem grau 3, os outros têm grau 1. Agora eu posso fazer uma Pmf que
representa esta distribuição de grau:
O resultado é um objeto Pmf que mapeia de cada grau para uma fração ou
probabilidade. Neste exemplo, 75% dos nós têm grau 1 e 25% têm grau 3.
3
N.T.: Do inglês, probability mass function (PMF).
4.4 Distribuições de cauda pesada 57
Agora podemos fazer uma Pmf que contém graus de nós do conjunto de dados
e calcular a média e o desvio padrão:
>>> from thinkstats2 import Pmf
>>> pmf_fb = Pmf(degrees(fb))
>>> pmf_fb.Mean(), pmf_fb.Std()
(43.691, 52.414)
O mesmo pode ser feito para o modelo WS:
>>> pmf_ws = Pmf(degrees(ws))
>>> pmf_ws.mean(), pmf_ws.std()
(44.000, 1.465)
Podemos usar o módulo thinkplot para mostrar graficamente os resultados:
thinkplot.Pdf(pmf_fb, label='Facebook')
thinkplot.Pdf(pmf_ws, label='WS graph')
A Figura 4.1 mostra as duas distribuições. Elas são muito diferentes.
Distribuições como esta, com muitos valores pequenos e alguns valores muito
grandes, são chamadas distribuições de cauda pesada.
Podemos obter uma imagem mais clara de uma distribuição de cauda pesada,
fazendo um gráfico log-log, como mostrado na Figura 4.2. Essa transformação
enfatiza a cauda da distribuição, isto é, as probabilidades de valores grandes.
Facebook grafo WS
1
10
2
10
2
10
FMP
FMP
3
10
3
10
FMP(k) ∼ k −α
Todas as distribuições de lei do potência são caudas pesadas, mas existem ou-
tras distribuições de cauda pesada que não seguem a lei de potência. Veremos
mais exemplos em breve.
Eles também propõem um modelo que gera grafos com a mesma propriedade.
As características essenciais do modelo, que o distinguem do modelo WS, são:
Os grafos com esta propriedade às vezes são chamados redes livres de escala,
por razões que não vou explicar. Se você é curioso, pode ler mais em https:
//pt.wikipedia.org/wiki/Rede_sem_escala5 .
O NetworkX fornece uma função que gera grafos BA. Primeira vamos usá-la
e na sequência eu vou mostrar como funciona.
ba = nx.barabasi_albert_graph(n=4039, k=22)
4
N.T.: Surgimento de escala em redes aleatórias
5
http://en.wikipedia.org/wiki/Scale-free_network
60 Capítulo 4 Redes livres de escala
1
10
Facebook modelo BA
2
10
2
10
FMP
FMP
3
10
3
10
O gráfico resultante possui 4039 nós e 21,9 arestas por nó. Uma vez que cada
aresta está conectada a dois nós, o grau médio é 43,8, muito próximo ao grau
médio no conjunto de dados, 43,7.
mais “pequeno mundo” do que a rede real, que tem L = 3,69. O resultado é
bom, mas talvez seja bom demais.
Nos exercícios no final deste capítulo, você pode explorar outros modelos des-
tinados a capturar todas essas características.
Nas seções anteriores, usamos uma função do NetworkX para gerar grafos BA.
Agora vamos ver como isso funciona. Aqui está uma versão de barabasi_albert_graph,
com algumas mudanças que fiz para facilitar a leitura:
62 Capítulo 4 Redes livres de escala
G = nx.empty_graph(k)
targets = list(range(k))
repeated_nodes = []
G.add_edges_from(zip([source]*k, targets))
repeated_nodes.extend(targets)
repeated_nodes.extend([source] * k)
targets = _random_subset(repeated_nodes, k)
return G
Começamos com um grafo que tem k nós e sem arestas. Em seguida, iniciali-
zamos duas variáveis:
Finalmente, escolhemos alguns dos nós para serem alvos na próxima iteração.
A definição de _random_subset é a seguinte:
4.7 Distribuições acumulativas 63
0.8 0.8
0.6 0.6
FDA
FDA
0.4 0.4
0.2 0.2
0.0 0.0
Dada uma Pmf, a maneira mais simples de calcular uma probabilidade acumu-
lada é adicionar as probabilidades de valores até x inclusive:
As FDAs são melhores para a visualização porque são menos poluídas do que
as FMPs. Depois de se acostumar a interpretar FDAs, eles fornecem uma
imagem mais clara da forma de uma distribuição do que FMPs.
O módulo thinkstats fornece uma classe chamada Cdf que representa uma
função distribuição acumulada. Podemos usá-la para calcular a FDA do grau
no conjunto de dados.
E thinkplot fornece uma função chamada Cdf que traça funções distribuição
acumulada.
thinkplot.Cdf(cdf_fb)
1 1
10 10
FDAC
FDAC
2 2
10 10
3 3
10 10
FDAC(x) = 1 − FDA(x)
É útil porque se a FMP segue uma lei de potência, a FDAC também segue
uma lei de potência:
−α
x
FDAC(x) =
xm
derivação
Modelo Comportamento
abstração analogia
observação
Sistema Observável
4.9 Exercícios
Há dados que você poderia coletar ou experimentos que você poderia realizar
que forneceriam provas em favor de um ou outro modelo?
Qual critério Kuhn propõe para escolher entre modelos concorrentes? Esses
critérios influenciam sua opinião sobre os modelos WS e BA? Existem outros
critérios que você acha que devem ser considerados?
7
N.T.: Objetividade, julgamento de valor e escolha de teoria.
4.9 Exercícios 69
import gzip
Nota: A rede de atores não está conectada, então você pode usar nx.connected_component_sub
para encontrar subconjuntos conectados dos nós.
70 Capítulo 4 Redes livres de escala
Capítulo 5
Autômatos Celulares
5.1 Um AC simples
Autômatos celulares1 são regidos por regras que determinam como o sistema
evolui no tempo. O tempo é dividido em passos discretos, e as regras especifi-
1
N.T: Na versão em inglês, o autor faz uma observação sobre a possibilidade do uso
automata como plural de automaton, além de automatons.
72 Capítulo 5 Autômatos Celulares
cam como calcular o estado do mundo no próximo passo com base no estado
atual.
Mas este AC é atípico uma vez que, normalmente, o número de estados pos-
síveis é finito. Para adequá-lo, vou escolher o menor número interessante de
estados, 2, e outra regra simples, xi = (xi−1 + 1)%2, em que % é o operador
que fornece o resto da divisão inteira.
A maioria dos ACs são determinísticos, o que significa que as regras não
têm elementos aleatórios; dado o mesmo estado inicial, elas sempre produzem
o mesmo resultado. Também existem ACs não-determinísticos, mas não os
abordarei aqui.
Dizer que um AC tem dimensões significa dizer que as células estão dispostas
em um espaço contíguo, de modo que algumas delas são consideradas “vizi-
nhas”. Em uma dimensão, existem três configurações naturais:
A primeira linha mostra os oito estados em que uma vizinhança pode estar.
A segunda linha mostra o estado da célula central durante o próximo passo.
Como uma codificação concisa desta tabela, Wolfram sugeriu a leitura da linha
inferior como um número binário. Como 00110010 em binário é 50 em decimal,
Wolfram chama isso de AC “Regra 50”.
A Figura 5.1 mostra o efeito da Regra 50 ao longo de 10 passos. A primeira
linha mostra o estado do sistema durante o primeiro passo; começa com uma
célula “ligada” e o resto “desligadas”. A segunda linha mostra o estado do
sistema durante o próximo passo e assim por diante.
A forma triangular na figura é típica desses ACs; é uma consequência da forma
da vizinhança. Em um passo, cada célula influencia o estado de uma vizinha
em qualquer direção. Durante o próximo passo, essa influência pode propagar
mais uma célula em cada direção. Assim, cada célula no passado tem um
“triângulo de influência” que inclui todas as células que podem ser afetadas
por ela.
74 Capítulo 5 Autômatos Celulares
Uma das primeiras experiências de Wolfram com ACs foi testar todas as 256
possibilidades e tentar classificá-las.
Esse padrão se assemelha ao Triângulo de Sierpiński , sobre o qual você pode ler
a respeito em https://pt.wikipedia.org/wiki/Triângulo_de_Sierpinski2 .
5.4 Aleatoriedade
A Classe 3 contém ACs que geram aleatoriedade. A Regra 30 é um exemplo.
A Figura 5.3 mostra o resultado depois de 100 passos.
dos testes estatísticos que as pessoas usam para testar se uma sequência de
bits é aleatória.
Programas que produzem números que parecem aleatórios são chamados gera-
dores de números pseudo-aleatórios (PRNGs3 ). Eles não são considerados
verdadeiramente aleatórios porque:
• Muitos deles produzem sequências com regularidades que podem ser de-
tectadas estatisticamente. Por exemplo, a implementação original de
rand na biblioteca C usou um gerador congruente linear que produziu
sequências com correlações seriadas facilmente detectáveis.
• Qualquer PRNG que usa uma quantidade finita de estados (ou seja,
armazenamento) acabará por se repetir. Uma das características de um
gerador é o período desta repetição.
5.5 Determinismo
A existência de ACs de Classe 3 é surpreendente. Para explicar o quão sur-
preendente, deixe-me começar com o determinismo filosófico (veja https:
//pt.wikipedia.org/wiki/Determinismo4 ). Muitas posturas filosóficas são
difíceis de definir precisamente porque elas vêm em uma variedade de sabores.
Muitas vezes acho útil defini-los com uma lista de afirmações ordenadas da
mais fraca para a mais forte:
3
N.T.: Sigla da expressão em inglês pseudo-random number generators.
4
http://en.wikipedia.org/wiki/Determinism
5.5 Determinismo 77
D1: Modelos determinísticos podem fazer previsões precisas para alguns sis-
temas físicos.
D2: Muitos sistemas físicos podem ser modelados por processos determinísti-
cos, mas alguns são intrinsecamente aleatórios.
D3: Todos os eventos são causados por eventos anteriores, mas muitos siste-
mas físicos ainda assim são fundamentalmente imprevisíveis.
D4: Todos os eventos são causados por eventos anteriores e podem (pelo me-
nos em princípio) ser previstos.
O meu objetivo ao construir essa lista é tornar D1 tão fraca que virtualmente
todos a aceitariam, D4 tão forte que quase ninguém a aceitaria, com afirmações
intermediárias que algumas pessoas aceitam.
5.6 Espaçonaves
O comportamento dos ACs Classe 4 é ainda mais surpreendente. Vários ACs
1-D, mais notavelmente a Regra 110, são Turing-completos, o que significa
5.7 Universalidade 79
A Figura 5.4 mostra o resultado da Regra 110 com uma condição inicial de
uma única célula e 100 passos. Nesta escala de tempo, não é aparente que
algo de especial esteja acontecendo. Existem alguns padrões regulares, mas
também alguns detalhes que são difíceis de caracterizar.
A Figura 5.5 mostra uma imagem maior, começando com uma condição inicial
aleatória e 600 passos.
5.7 Universalidade
Para entender a universalidade, temos que entender a teoria da computabili-
dade, que versa sobre modelos de computação e o que eles computam.
Figura 5.5: Regra 110 com condições iniciais aleatórias e 600 passos.
5.7 Universalidade 81
Dizer que uma máquina de Turing pode computar qualquer função Turing-
computável é uma tautologia: é verdadeira por definição. Mas a computabi-
lidade de Turing é mais interessante que isso.
Em A New Kind of Science, Wolfram afirma uma variação desta tese, que ele
chama de “princípio da equivalência computacional”:
82 Capítulo 5 Autômatos Celulares
5.8 Falseabilidade
Wolfram sustenta que seu princípio é uma afirmação mais forte do que a Tese de
Church-Turing porque se trata do mundo natural e não de modelos abstratos
de computação. Mas dizer que os processos naturais “podem ser vistos como
computações” me parece mais uma afirmação sobre a escolha da teoria do que
uma hipótese sobre o mundo natural.
Além disso, com qualificações como “quase” e termos indefinidos como “ob-
viamente simples”, sua hipótese pode ser não falseável. A falseabilidade é
uma ideia da filosofia da ciência, proposta por Karl Popper como uma demar-
cação entre hipóteses científicas e pseudociência. Uma hipótese é falseável se
houver um experimento, pelo menos no campo da praticidade, que contradiria
a hipótese se ela fosse falsa.
Por outro lado, “criação divina”, a alegação de que todas as espécies foram
criadas em sua forma atual por um agente sobrenatural, é não falseável por-
que não há nada que possamos observar sobre o mundo natural que possa
contradizê-la. Qualquer resultado de qualquer experimento pode ser atribuído
à vontade do criador.
Hipóteses não falseáveis podem ser atraentes porque são impossíveis de refutar.
Se o seu objetivo for nunca provarem que você está errado, você deve escolher
hipóteses que sejam tão não falseáveis quanto possível.
Mas se o seu objetivo é fazer previsões confiáveis sobre o mundo - e este é, pelo
menos, um dos objetivos da ciência - hipóteses não falseáveis são inúteis. O
problema é que elas não têm consequências (se tivessem consequências, seriam
falseáveis).
Por exemplo, se a teoria da criação divina fosse verdadeira, que bem me faria
saber disso? Não me diria nada sobre o criador, exceto que ele tem um “apego
desmedido por besouros” (atribuído a J. B. S. Haldane). Ao contrário da
teoria da origem comum, que é informativa para muitas áreas da ciência e da
bioengenharia, não seria útil para entender o mundo ou agir nele.
Mas não está claro que eles sejam modelos de sistemas físicos. E se são, são
altamente abstratos, o que significa que não são muito detalhados ou realistas.
Modelo Modelo
x, y, z w, x, y
Modelo derivação
x, y Comportamento
abstração analogia
observação
Sistema Observável
Claro, isso só é verdade até certo ponto. Modelos mais detalhados são mais
difíceis de se trabalhar e geralmente menos passíveis de análise. Em algum
momento, um modelo se torna tão complexo que é mais fácil experimentar o
sistema.
1 2 3 4 1 2 3 4
5 6 7 8
5 6 7 8 9 10 11 12
9 10 11 12
Para este tipo de argumento, adicionar mais características não ajuda. Tornar
o modelo mais realista não torna o modelo mais confiável; ele apenas obscurece
a diferença entre as características essenciais que causam O e as características
incidentais específicas de S.
Para explicar como meu código funciona, começarei com um AC que computa
a “paridade” das células em cada vizinhança. A paridade de um número é 0
se o número for par e 1 se for ímpar.
>>> rows = 5
>>> cols = 11
>>> ca = np.zeros((rows, cols))
>>> ca[0, 5] = 1
print(ca)
[[ 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
>>> print(array[4])
>>> print(window * array[4])
[0 1 0 0 0 1 0 0 0 1 0]
[0 1 0 0 0 0 0 0 0 0 0]
roll desloca a janela para a direita (ela também leva de volta para o início,
mas isso não afeta essa função).
step2 produz os mesmos resultados que step. Ainda não é muito rápida, mas
é um passo na direção certa porque a operação que acabamos de realizar—
multiplicando por uma janela, somando o resultado, deslocando a janela e
repetindo—é usada para uma variedade de aplicações. É chamada de corre-
lação cruzada e o NumPy fornece uma função chamada correlate que a
computa.
Podemos usá-la para escrever uma versão mais rápida e simples de step:
5.12 Tabelas AC
Agora há apenas mais um passo. A função que temos até agora funciona se a
regra do AC depender apenas da soma das vizinhas, mas a maioria das regras
também depende de quais vizinhos estão ligados e desligados. Por exemplo,
100 e 001 podem produzir resultados diferentes.
Podemos tornar step mais geral usando uma janela com elementos [4, 2, 1],
que interpreta a vizinhança como um número binário. Por exemplo, a vizi-
nhança 100 produz 4; 010 resulta 2, e 001 resulta 1. Então podemos pegar
esses resultados e procurá-los na tabela de regras.
As duas primeiras linhas são quase iguais. A última linha procura cada ele-
mento de corr em table e atribui os resultados a array[i].
def make_table(rule):
rule = np.array([rule], dtype=np.uint8)
table = np.unpackbits(rule)[::-1]
return table
5.13 Exercícios
Exercício 5.1 O código para este capítulo está no Jupyter notebook chap05.ipynb
no repositório deste livro. Abra este notebook, leia o código e execute as célu-
las. Você pode usar este notebook para trabalhar nos exercícios deste capítulo.
Minhas soluções estão em chap05soln.ipynb.
Exercício 5.2 Este exercício pede que você experimente a Regra 110 e al-
gumas de suas espaçonaves.
1. Leia a página da Wikipedia sobre a Regra 110, que descreve seu padrão
de plano de fundo e naves espaciais: https://en.wikipedia.org/wiki/
Rule_1108 .
2. Crie um AC Regra 110 com uma condição inicial que produz o padrão
de plano de fundo estável.
Observe que a classe CA fornece start_string, que permite inicializar o
estado do array usando uma cadeia de 1s e 0s.
3. Escreva uma classe chamada TuringDrawer que gera uma imagem que
representa o estado da fita e a posição e estado do cabeçote. Para um
exemplo de como isso se parece, consulte http://mathworld.wolfram.
com/TuringMachine.html.
Exercício 5.4 Este exercício pede que você implemente e teste vários PRNGs.
Para os testes, você precisará instalar o DieHarder, que pode ser baixado em
https://www.phy.duke.edu/~rgb/General/dieharder.php, ou que pode es-
tar disponível como um pacote para o seu sistema operacional.
4. Você pode resumir uma ou mais das objeções que filósofos e historiadores
da ciência levantaram à alegação de Popper?
Jogo da Vida
Como os ACs 1-D nos capítulos anteriores, o Jogo da Vida evolui ao longo do
tempo de acordo com regras, que são como simples leis da física.
• Outro fator que gerou interesse foi a conjectura de Conway—de que não
existe condição inicial que produza crescimento ilimitado no número de
células vivas—e a recompensa de $50 que ele ofereceu a qualquer um que
pudesse provar ou refutar isso.
e nenhuma das células mortas adjacentes à colmeia tem 3 vizinhas vivas, então
não nascem novas células.
Outros padrões “oscilam”; isto é, eles mudam com o tempo, mas eventualmente
retornam à sua configuração inicial (desde que eles não colidam com outro
padrão). Por exemplo, a Figura 6.2 mostra um padrão chamado de “sapo”5 ,
um oscilador que alterna entre dois estados. O “período” deste oscilador é 2.
Mas existem algumas condições iniciais simples que levam muito tempo para
se estabilizar e resultam em um número surpreendente de células vivas. Esses
padrões são chamados de “Matusaléns” porque são longevos.
Um dos mais simples é o r-pentomino, que tem apenas cinco células, aproxi-
madamente na forma da letra “r”. A Figura 6.4 mostra a configuração inicial
do r-pentomino e a configuração final após 1103 passos.
5
N.T.: Do inglês, toad.
6
N.T.: Do inglês, glider.
98 Capítulo 6 Jogo da Vida
r-pentomino
N.T.:
7
Do inglês, block.
N.T.:
8
Do inglês, blinker.
9
N.T.: Do inglês, boat.
10
N.T.: Do inglês, ship.
11
N.T.: Do inglês, loaf.
6.4 Realismo 99
Acontece que esses dois padrões existem. Uma equipe liderada por Bill Gos-
per descobriu o primeiro, uma arma de planador agora chamada de Arma de
Gosper, mostrada na Figura 6.5. Gosper também descobriu o primeiro trem
de bombardeio.
Existem muitos padrões de ambos os tipos, mas eles não são fáceis de projetar
ou de encontrar. Isso não é uma coincidência. Conway escolheu as regras do
GoL para que sua conjectura não fosse obviamente verdadeira ou falsa. De
todas as regras possíveis para uma AC 2-D, a maioria resulta em comporta-
mentos simples: a maioria das condições iniciais se estabilizam rapidamente
ou têm crescimento ilimitado. Ao evitar ACs desinteressantes, Conway tam-
bém estava evitando o comportamento Classe 1 e Classe 2 de Wolfram, e
provavelmente também a Classe 3.
6.4 Realismo
Padrões estáveis no GoL não são difíceis de serem notados, especialmente
os que se movem. É natural pensar neles como entidades persistentes, mas
lembre-se de que um AC é feito de células. Não existe um sapo ou um pão.
Planadores e outras espaçonaves são ainda menos reais, porque nem sequer
são compostas pelas mesmas células ao longo do tempo. Assim, esses padrões
são como constelações de estrelas. Nós os percebemos porque somos bons em
ver padrões, ou porque temos imaginação ativa, mas eles não são reais.
12
N.T.: Do inglês, gun.
13
N.T.: Do inglês, puffer train.
100 Capítulo 6 Jogo da Vida
Certo?
Bem, não tão rápido. Muitas entidades que consideramos “reais” também são
padrões persistentes de entidades em menor escala. Os furacões são apenas
padrões de fluxo de ar, mas nós lhes damos nomes de pessoas. E as pessoas,
como planadores, não são compostas das mesmas células ao longo do tempo.
Mas mesmo que você substitua todas as células do seu corpo, o consideramos
a mesma pessoa.
Esta não é uma observação nova—cerca de 2500 anos atrás, Heráclito salientou
que você não pode pisar no mesmo rio duas vezes—mas as entidades que
aparecem no Jogo da Vida são um caso útil para se pensar em realismo
filosófico.
Mas essas entidades são reais? Ou seja, eles existem no mundo independente
de nós e de nossas teorias?
Mais uma vez, acho útil afirmar posições filosóficas com forças diferentes. Aqui
estão quatro afirmações do realismo científico em ordem crescente de força:
SR4 é tão forte que provavelmente é insustentável; por um critério tão rigoroso,
quase todas as teorias atuais são tidas como falsas. A maioria dos realistas
aceitaria algo no espaço entre SR1 e SR3.
6.5 Instrumentalismo
Mas a SR1 é tão fraca que beira o instrumentalismo, que é a visão de que
não podemos dizer se uma teoria é verdadeira ou falsa porque não podemos
saber se uma teoria corresponde à realidade. Teorias são instrumentos que
usamos para nossos propósitos; uma teoria é útil, ou não, na medida em que
é adequada ao seu propósito.
“Alguns objetos têm limites nítidos, mas muitos são confusos. Por
exemplo, quais moléculas fazem parte do seu corpo: ar nos pul-
mões? Comida no seu estômago? Nutrientes no seu sangue? Nu-
trientes em uma célula? Água em uma célula? Partes estruturais
de uma célula? Cabelo? Pele morta? Sujeira? Bactérias na sua
pele? Bactérias no seu intestino? Mitocôndria? Quantas dessas
moléculas você inclui quando se pesa? Conceber o mundo em ter-
mos de objetos discretos é útil, mas as entidades que identificamos
não são reais.”
Se você está mais confortável com algumas dessas afirmações do que com
outras, pergunte-se por quê. Quais são as diferenças nesses cenários que influ-
enciam sua reação? Você pode fazer uma distinção de princípios entre elas?
minha implementação do GoL, a qual você pode usar como ponto de partida
para suas experiências.
Para representar o estado das células, eu uso um array NumPy com o tipo
uint8, que é um inteiro sem sinal de 8 bits. Como exemplo, a linha a seguir
cria um array de 10 por 10 inicializado com valores aleatórios de 0 e 1.
b = np.zeros_like(a)
rows, cols = a.shape
for i in range(1, rows-1):
for j in range(1, cols-1):
state = a[i, j]
neighbors = a[i-1:i+2, j-1:j+2]
k = np.sum(neighbors) - state
if state:
if k==2 or k==3:
b[i, j] = 1
else:
if k == 3:
b[i, j] = 1
Essa implementação é uma tradução direta das regras, mas é detalhada e lenta.
Podemos fazer melhor usando correlação cruzada, como vimos na Seção 5.11.
Lá, usamos np.correlate para calcular uma correlação 1-D. Agora, para re-
alizar a correlação 2-D, usaremos correlate2d de scipy.signal, um módulo
SciPy que fornece funções relacionadas a processamento de sinais:
104 Capítulo 6 Jogo da Vida
A primeira linha calcula um array booleano com True onde deveria haver
uma célula viva e False nos outros lugares. Então astype converte o array
booleano em uma matriz de inteiros.
Isso pode não parecer uma grande melhoria, mas permite mais uma simpli-
ficação: com esse kernel, podemos usar uma tabela para procurar valores de
célula, como fizemos na Seção 5.12.
table tem zeros em todos os lugares, exceto nos locais 3, 12 e 13. Quando
usamos c como um índice em table, o NumPy faz uma pesquisa elementar;
isto é, ele pega cada valor de c, pesquisa em table e coloca o resultado em b.
Life.py, incluído no repositório deste livro, fornece uma classe Life que en-
capsula essa implementação das regras. Se você executar Life.py, deverá ver
uma animação de um “trem de bombardeio”, uma espaçonave que deixa um
rastro de detritos por onde passa.
6.7 Exercícios
Exercício 6.1 O código deste capítulo está no Jupyter notebook chap06.ipynb
no repositório deste livro. Abra este notebook, leia o código e execute as célu-
las. Você pode usar este notebook para trabalhar nos exercícios deste capítulo.
Minhas soluções estão em chap06soln.ipynb.
Exercício 6.3 Muitos dos padrões que receberam nomes estão disponíveis
em formatos portáveis de arquivo. Modifique Life.py para analisar um desses
formatos e inicialize a grade.
Por exemplo, uma variação do GoL, chamada “Highlife”, tem as mesmas regras
do GoL, mais uma regra adicional: uma célula morta com 6 vizinhas vivas volta
a viver.
Escreva uma classe chamada Highlife que herda de Cell2D e implemente esta
versão das regras. Escreva também uma classe chamada HighlifeViewer que
herda de Cell2DViewer e tente diferentes maneiras de visualizar os resultados.
Como um simples exemplo, use um mapa de cores diferente.
A formiga é uma cabeçote de leitura-escrita com quatro estados, que você pode
imaginar como sendo voltados para o norte, sul, leste ou oeste. As células têm
dois estados, preto e branco.
As regras são simples. Durante cada passo, a formiga verifica a cor da célula
em que está. Se for preta, a formiga vira para a direita, muda a célula para
branca e avança um espaço. Se a célula é branca, a formiga vira à esquerda,
muda a célula para preta e avança.
saber que não é bem assim. Começando com todos as células brancas, a
formiga de Langton move-se em um padrão aparentemente aleatório por mais
de 10000 passos antes de entrar em um ciclo com um período de 104 passos.
Depois de cada ciclo, a formiga é transladada na diagonal, por isso deixa uma
trilha chamada “auto-estrada”.
Os modelos que vimos até agora podem ser caracterizados como “baseados
em regras” no sentido de que eles envolvem sistemas governados por regras
simples. Neste e nos capítulos seguintes exploramos modelos baseados em
agentes.
A todo momento, um agente pode estar feliz ou infeliz, dependendo dos outros
agentes da vizinhança, onde a “vizinhança” de cada casa é o conjunto de oito
células adjacentes. Em uma versão do modelo, os agentes estão felizes se têm
pelo menos dois vizinhos como eles e infelizes se tiverem um ou zero.
Você pode não se surpreender ao ouvir que este modelo leva a alguma segre-
gação, mas você pode se surpreender com o grau. Rapidamente, grupos de
agentes similares aparecem. Os agrupamentos crescem e coalescem ao longo
do tempo até que haja um pequeno número de grandes agrupamentos e a
maioria dos agentes viva em vizinhanças homogêneas.
segregação em uma cidade real, você não pode concluir que o racismo indivi-
dual é a causa imediata, ou mesmo que as pessoas na cidade são racistas.
É claro que temos que ter em mente as limitações desse argumento: o modelo
de Schelling demonstra uma possível causa de segregação, mas nada diz sobre
as causas reais.
Agora, para cada célula, podemos calcular a fração de vizinhos que são ver-
melhos e a fração de vizinhos que têm a mesma cor:
Nesse caso, onde quer que red seja True, frac_same obtém o elemento cor-
respondente de frac_red. Onde red é False, frac_same obtém o elemento
correspondente de frac_blue.
def locs_where(condition):
return np.transpose(np.nonzero(condition))
9.3 Segregação 151
empty_locs = locs_where(a==0)
9.3 Segregação
Agora vamos ver o que acontece quando executamos o modelo. Começarei
com n = 100 e p = 0.3 e executarei 10 passos.
np.sum(frac_same) / np.sum(occupied)
1.0
Segregação 0.8
0.6
0.4
p = 0.5
0.2 p = 0.4
p = 0.3
p = 0.2
0.0
0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5
Passos
9.4 Sugarscape
Em 1996, Joshua Epstein e Robert Axtell propuseram Sugarscape, um modelo
baseado em agentes de uma “sociedade artificial” destinada a apoiar experi-
mentos relacionados à economia e outras ciências sociais.
Sugarscape é um modelo versátil que foi adaptado para uma ampla variedade
de tópicos. Como exemplos, vou replicar as primeiras experiências do livro de
Epstein e Axtell, Growing Artificial Societies2 .
A Figura 9.3 (esquerda) mostra a configuração inicial, com as áreas mais es-
curas indicando as células com maior capacidade e as bolinhas representando
os agentes.
Açúcar: Cada agente começa com uma dotação de açúcar escolhida de uma
distribuição uniforme entre 5 e 25 unidades.
9.4 Sugarscape 155
Metabolismo: Cada agente tem uma certa quantidade de açúcar que deve
consumir por passo, escolhida uniformemente entre 1 e 4.
Visão: Cada agente pode “ver” a quantidade de açúcar nas células próximas
e passar para a célula com maior quantidade, mas alguns agentes podem
enxergar melhor que os outros. A distância que os agentes enxergam é
escolhida uniformemente entre 1 e 6.
A Figura 9.3 (centro) mostra o estado do modelo após dois passos. A maioria
dos agentes está se movendo em direção às áreas com mais açúcar. Agentes
com visão alta também se movem mais rápido. Agentes com baixa visão
tendem a ficar presos nos planaltos, vagando aleatoriamente até chegar perto
o suficiente para ver a área com mais açúcar.
Dentro das áreas de alto teor de açúcar, os agentes competem uns com os
outros para encontrar e recolher o açúcar à medida que ele cresce. Agentes
com metabolismo alto ou visão baixa são os mais propensos a morrer de fome.
156 Capítulo 9 Modelos baseados em agentes
Quando o açúcar cresce 1 unidade por passo, não há açúcar suficiente para sus-
tentar os 400 agentes com os quais começamos. A população cai rapidamente
no início, depois mais devagar e estabiliza em torno de 250.
A Figura 9.3 (direita) mostra o estado do modelo após 100 passos, com cerca
de 250 agentes. Os agentes que sobrevivem tendem a ser os sortudos, nascidos
com visão alta e/ou metabolismo baixo. Tendo sobrevivido até este ponto,
eles provavelmente sobreviverão para sempre, acumulando estoques ilimitados
de açúcar.
Nesta versão do modelo, os agentes têm uma idade que é incrementada a cada
passo e uma vida útil aleatória que é uniforme entre 60 e 100. Se a idade de
um agente exceder seu tempo de vida, ele morre.
1.0 1.0
0.8 0.8
0.6 0.6
FDA
FDA
0.4 0.4
0.2 0.2
0.0 0.0
0 50 100 150 200 10 -2 10 -1 10 0 10 1 10 2 10 3
Riqueza Riqueza
Figura 9.4: Distribuição de açúcar (riqueza) após 100, 200, 300 e 400 passos
(linhas cinzas) e 500 passos (linha escura). Escala linear (esquerda) e escala
log-x (direita).
Após cerca de 200 passos (o dobro da vida útil mais longa), a distribuição não
muda muito. E é inclinada para a direita.
e outras transferências de renda sugerem que isso não é algo fácil de evitar ou
mitigar.
class Agent:
class Sugarscape(Cell2D):
def step(self):
Se você estiver interessado em aprender mais sobre o NumPy, você pode ver
em mais detalhes make_visible_locs, que constrói um array em que cada
linha contém as coordenadas de uma célula visível para um agente, ordenadas
por distância, mas com células à mesma distância em ordem aleatória.
160 Capítulo 9 Modelos baseados em agentes
Os que têm o maior alcance de visão cruzam o vale entre os picos primeiro e
se propagam em direção ao nordeste em um padrão que se assemelha a uma
frente de onda. Uma vez que eles deixam uma faixa de células vazias atrás
9.8 Emergência 161
deles, outros agentes não seguem em direção ao pico até que o açúcar volte a
crescer.
9.8 Emergência
Os exemplos deste capítulo demonstram uma das ideias mais importantes da
ciência da complexidade: a emergência. Uma propriedade emergente é uma
característica de um sistema que resulta da interação de seus componentes e
não de suas propriedades.
O mesmo pode ser verdade para sistemas complexos em geral. Para siste-
mas físicos com mais do que alguns componentes, geralmente não há modelo
que produza uma solução analítica. Métodos numéricos fornecem um tipo de
atalho computacional, mas ainda existe uma diferença qualitativa.
Para algumas pessoas, “emergência” é outro nome para a ignorância; por esse
viés, uma propriedade é emergente se não tivermos uma explicação reduci-
onista, mas se chegarmos a entendê-la melhor no futuro, ela não será mais
emergente.
9.9 Exercícios
Exercício 9.1 Bill Bishop, autor de The Big Sort4 , argumenta que a soci-
edade americana está cada vez mais segregada pela opinião política, pois as
pessoas escolhem viver entre vizinhos que pensam como eles.
O mecanismo que Bishop propõe não é que as pessoas, como os agentes do mo-
delo de Schelling, têm maior probabilidade de se mover se estiverem isoladas,
mas que, quando se mudam por qualquer motivo, provavelmente escolherão
um bairro com pessoas como elas.