AC: Jogo da Vida
Anterior: Modelos Logísticos | Índice: Ecologia | Próximo: Modelo de Lotka-Volterra
Modelos conceituais
O "Jogo da Vida" é um jogo criado em 1970 pelo matemático Horton Conway usando autômatos celulares. Na proposta original o jogo da vida é composto por uma grade de células quaradas, onde cada célula pode estar morta ou viva, e seu estado é atualizado a cada passo finito de tempo baseado no estado atual da própria célula e das 8 células vizinhas (vizinhança de Moore). Estas regras podem ser enunciadas da seguinte forma:
- Reprodução: Qualquer célula morta com exatamente três vizinhos vivos será uma célula viva no próximo passo.
- Sobrevivência: Qualquer célula viva com 2 ou 3 vizinhos vivos permanece vivo no próximo passo.
- Subpopulação: A célula morre se tiver menos de 2 vizinhos vivos na vizinhança.
- Superpopulação: A célula morre se tiver mais de 3 vizinhos na vizinhança.
Um autômato celular probabilístico foi proposto como uma extensão no jogo da vida. A intenção é possuir um modelo mais flexível com a possibilidade de alterar os limites do Jogo da Vida clássico. A ideia básica do PCAEGOL (Probabilistic Cellular Automata, Extension of the Game Of Life) é que os estados das células vizinhas de uma dada célula não são conhecidos com exatidão, dessa forma o estado resultante não é determinístico e há uma certa probabilidade de uma célula que teria determinado resultado deterministicamente, agora tenha outro. A ideia é que o sistema nem sempre se comporte conforme é esperado. As probabilidades podem ser definidas individualmente para cada célula vizinha de uma determinada célula. Por exemplo, tendo a célula central C0 e chamando as 8 células vizinhas de Ci (onde i=1,2,...,8), então temos os vetores:
- pl = (pl0,pl1,...,pl8), onde pli é a probabilidade da célula Ci ser considerada viva quando está viva.
- pd = (pd0,pd1,...,pd8), onde pdi é a probabilidade da célula Ci ser considerada viva quando está morta.
Podemos recuperar o jogo da vida facilmente fazendo pli=1$ e pdi=0. Além disso as regras clássicas do jogo da vida podem ser estendidas fazendo:
- MAXO = Número máximo de células vizinhas vivas para que não ocorre superpopulação
- MINU = Número máximo de células vizinhas vivas para que não ocorre superpopulação
- MAXB = Número máximo de células vizinhas vivas para que a célula nasça no próximo passo
- MINB = Número mínimo de células vizinhas vivas para que a célula nasça no próximo passo
Novamente podemos recuperar o Jogo da Vida clássico fazendo MAXO=3,MINU=2, MAXB=3 e MINB=3.
Código do Jogo da Vida
Mesa é um framework em Python para desenvolvimento de modelos baseados em agentes, porém também podemos simular autômatos celulares e visualizar o resultado, por isto o mesmo foi utilizado para desenvolver ambos os códigos a seguir. Algun tutoriais podem ser consultados aqui e aqui.
Para começar, precisamos definir duas classes: a de cada agent (no nosso caso cada célula) e a própria do modelo. Cada classe tem algumas obrigatórias relacionadas a forma com que avançamos o modelo. Como é assíncrono, o agente tem uma função para preparar as mudanças (step) e outra para aplicar as mudanças (advance). Além disso precisamos passar algumas informações para o modelo: que as células estão distribuídas em grade, a largura e altura da grade, e que as alterações ocorrem de forma assíncrona.
#Bibliotecas necessárias
from mesa import Agent, Model #Importamos as classes Agente e Modelo
from mesa.time import SimultaneousActivation #Importamos o agendador por ordem aleatória
from mesa.space import MultiGrid #Importamos a malha
from mesa.visualization.modules import CanvasGrid #Gerar o JSON
from mesa.visualization.ModularVisualization import ModularServer #Ler o JSON
import random #Biblioteca para números aleatórios
#O Modelo em si
class Planta(Agent):
"""Dinâmica da planta inspirada no jogo da vida"""
def __init__(self, ide, modelo,frac):
super().__init__(ide, modelo) #Requisito da biblioteca
self.vivo = random.random() < frac #Guardar o estado atual
self.passo=self.vivo #Guardar o próximo estado
def step(self): # Método que aplica as mudanças a cada passo
#Pegamos a coordenada da vizinhança
vizinhos = self.model.grid.get_neighborhood(self.pos,moore=True,include_center=False)
nviz=0
agentes = self.model.grid.get_cell_list_contents(vizinhos) #E o conteúdo de cada célula
for planta in agentes:
if (planta.vivo==True):
nviz+=1
if (self.vivo==True):
if (nviz<2 or nviz>3):
self.passo = False
elif (nviz==3):
self.passo=True
def advance(self): #E no simultâneo precisa de um método avance() que é o que aplica as mudanças assíncronas
self.vivo=self.passo
class Modelo(Model):
"""Modelo geral"""
def __init__(self, frac, largura, altura):
self.frac = frac
self.grid = MultiGrid(largura, altura, True) #Grade
self.schedule = SimultaneousActivation(self) #Assíncrono
self.running = True #Sem codições de parada
# Criar agentes
i=0
for X in range(largura):
for Y in range(altura):
a = Planta(i, self,self.frac)
self.schedule.add(a)
self.grid.place_agent(a, (X, Y))
i+=1
def step(self): #Avança o modelo
self.schedule.step()
Para exibir a simulação na tela, ainda precisamos definir como gerar o JSON em que especificamos como cada célula deve ser exibida na tela. No caso omo retângulos 1x1 onde a cor depende se a célula esta viva ou morta.
#Define como gerar esse JSON
def retrato(planta):
"""Definir o retrato do agente"""
portrayal = {"Shape": "rect","Filled": "true","h": 1.0,"w":1.0,"Layer":1}
if (planta.vivo == True):
portrayal["Color"] = "green"
else:
portrayal["Color"] = "grey"
return portrayal
Definimos então a grade para exibir na tela:
#Definimos uma grade de 10x10 células e 500x500 píxeis grade = CanvasGrid(retrato, 10, 10, 500, 500)
E lançamos o servidor:
#Lançamo o servidor
servidor = ModularServer(Modelo, [grade], "Jogo da Vida",{"frac":0.1, "largura":10, "altura":10})
servidor.port = 8521
servidor.launch() #Lançamos o servidor
Código do PCAEGOL
O código do PCAEGL segue a mesma ideia do anterior, com as alterações pertinentes devido às diferenças de modelo.
#Bibliotecas necessárias
from mesa import Agent, Model #Classes Agente e Modelo
from mesa.time import SimultaneousActivation #Agendador simultâneo
from mesa.space import MultiGrid #Malha multigrid
from mesa.visualization.modules import CanvasGrid #Gerar o JSON
from mesa.visualization.ModularVisualization import ModularServer #Ler o JSON
import random #Número aleatórios
#Classe do agente
class Agente(Agent):
"""Classe do agente"""
def __init__(self, ide, modelo):
"""Função incializadora"""
super().__init__(ide, modelo) #Necessário para funcionar o modelo
F = 0.5 #Fração de plantas vivas
self.vivo = random.random() < F #Definimos o estado inicial da planta
self.passo = self.vivo #E já definimos a situação no próximo estado
def step(self):
"""Método obrigatório que prepara as mudanças"""
#pl = 1;pd = 0;MAXO=3;MINU=2;MAXB=3;MINB=3
pl = 1;pd = 0;MAXO=6;MINU=2;MAXB=3;MINB=3 #Parâmetros do PCAEGOL
vizinhos = self.model.grid.get_neighborhood(self.pos,moore=True,include_center=False) #Coordenada da vizinhança
nviz=0 #Contador de vizinhos
agentes = self.model.grid.get_cell_list_contents(vizinhos) #Agentes na vizinhança
for planta in agentes: #Percorre o agentes
estado = (random.random() <pl) if (planta.vivo) else (random.random() <pd) #Se está vivo ou não
nviz = (nviz+1) if (estado) else (nviz) #Soma o mais um vizinho
estado = (random.random() <pl) if (self.vivo) else (random.random() <pd)
if (estado):
if (nviz<MINU or nviz>MAXO): #Sub ou super população
self.passo = False
elif (nviz >= MINB and nviz<=MAXB): #Reprodução
self.passo = True
def advance(self):
"""Método obrigatório que aplica as mudanças"""
self.vivo=self.passo #Aplica as mudanças
class Modelo(Model):
"""Modelo geral"""
def __init__(self, largura, altura):
"""Função chamada quando o modelo é inicializazdo"""
self.grid = MultiGrid(largura, altura, True) #Configura a grade
self.schedule = SimultaneousActivation(self) #Configura o agendador
self.running = True #Condiçao para seguir executando o modelo
# Distribuir agentes em toda grade
i=0
for X in range(largura):
for Y in range(altura):
a = Agente(i, self)
self.schedule.add(a)
self.grid.place_agent(a, (X, Y))
i+=1
def step(self):
"""Avançar um passo do modelo"""
self.schedule.step()
#Define como gerar esse JSON
def retrato(agente):
"""Definir o retrato do agente"""
portrayal = {"Shape": "rect","Filled": "true","h": 1.0,"w":1.0,"Layer":1}
if (agente.vivo == True):
portrayal["Color"] = "green"
else:
portrayal["Color"] = "grey"
return portrayal
#Lançamo o servidor
grade = CanvasGrid(retrato, 20, 20, 500, 500)
servidor = ModularServer(Modelo, [grade], "Jogo da Vida",{"largura":20, "altura":20})
servidor.port = 8521
servidor.launch()
Principal material utilizado
A probabilistic extension to Conway’s Game of Life (Gabriel Aguilera-Venegas e outros,)
Anterior: Modelos Logísticos | Índice: Ecologia | Próximo: Modelo de Lotka-Volterra