TP1 - Busca no espaço de estados

Introdução

Neste projeto, você irá implementar os principais algoritmos de busca para resolver o problema do caminho mais curto em mapas com estrutura de grade. Para isso, você utilizará uma página web acessada por meio de um servidor web local (que será executado em seu próprio computador). A partir desta página, será possível carregar mapas pré-existentes e criar novos. Além disso, você terá a capacidade de enviar os mapas para o servidor executar buscas com os algoritmos e heurísticas selecionados. O objetivo principal deste projeto é praticar a implementação de algoritmos de busca e compreender a dinâmica de exploração do espaço de estados de cada um deles. Portanto, além de implementar os algoritmos, será necessário redigir um pequeno relatório analisando os resultados obtidos de forma objetiva.

Código-base

Executar servidor

Clique aqui para baixar o código-base, que inclui o cliente em JavaScript para desenhar e carregar os mapas, bem como o servidor em Python para implementar e executar os algoritmos de busca. Para executar o servidor, você precisará instalar as dependências do projeto e iniciar o servidor com os seguintes comandos:

pip install -r requirements.txt # Instalar dependencias
python3 server.py -port 5001 # Executar o servidor

Assim que o servidor iniciar, você pode abrir o seguinte endereço em qualquer navegador: http://localhost:5001.

Estados

Todo o seu trabalho será realizado no arquivo search.py. Abra esse arquivo e se familiarize com as funções definidas. Nesta implementação, os estados são representados por tuplas (x,y) com as coordenadas das células do mapa (grid). Além disso, cada estado tem um valor 1 <= v <= 9 associado, que será utilizado para calcular o custo das ações de movimentação no mapa. Segue um exemplo de mapa:

XXXXXXXXXXXXXXXX
XS1111111111111X
X11111111111111X
X11111111111111X
X11111111111111X
X11111111111111X
X1111111111111GX
XXXXXXXXXXXXXXXX

Os caracteres X representam paredes, S = (1,1) é o estado inicial e G = (13,6) é o estado final. Todos os outros caracteres numéricos representam espaços vazios. Neste exemplo, existem apenas espaços vazios com valor v = 1. Note que S e G também são espaços vazios (onde pode ocorrer movimento) e seus estados, por definição do problema, têm valor v = 1.

O ponto de entrada de qualquer algoritmo de busca é a função plan(map, algorithm, heuristic), que já está implementada. Ela realiza o parser do mapa, transformando-o de uma matriz de caracteres em um dicionário level. Esse dicionário contém um conjunto walls, um dicionário spaces, e duas tuplas start e goal com os estados inicial e final, respectivamente. Em seguida, a função executa um algoritmo de busca para encontrar um caminho entre start e goal. O algoritmo executado dependerá do parâmetro algorithm, que por padrão é definido como a busca em largura "bfs". Para algoritmos de busca informada, a função utiliza o parâmetro heuristic para definir a função heurística utilizada durante a busca.

Tarefas

1. Modelo de transição de estados

1.1 Função de custo

Sua primeira tarefa é implementar a função de custo cost_function(state1, state2, cost1, cost2) entre dois estados state1 e state2. O custo das ações entre estados deve ser calculado pela fórmula cost = dist(state1, state2) * (cost1 + cost2)/2, onde dist é a distância euclidiana entre state1 e state2. O exemplo abaixo ilustra essa fórmula:

image

1.2 Função de transição

Em seguida, você irá implementar a função de transição de estados transition_model(level, state1), que dado um mapa representado pelo dicionário level ({walls, spaces, start, goal}) e um estado state1, retorna os próximos estados permitidos a partir de state1 e seus respectivos custos. Sua implementação deve permitir movimentos horizontais, verticais e diagonais. O custo das ações entre o estado state1 e seus vizinhos deve ser calculado com a função cost_function implementada na etapa anterior. Antes de prosseguir para a próxima etapa, teste as duas funções que você implementou verificando os vizinhos e seus custos para estados nos mapas dados de exemplo.

2. Busca não-informada

Após concluir a etapa anterior, você pode dar início à implementação dos algoritmos de busca.

2.1 Busca em largura

Para implementar a busca em largura, você deve criar a função bfs(s, g, level, adj), que recebe como parâmetros o estado inicial s, o estado final g, o mapa representado pelo dicionário level e a função de transição adj. A função bfs deve retornar uma lista path com o caminho entre s e g encontrado pela busca em largura e um dicionário visited contendo todos os estados visitados durante o processo. Se o problema não tiver solução, o caminho retornado deve ser uma lista vazia, mas o dicionário visited ainda conterá todos os nós visitados até a parada do algoritmo. O resultado esperado para os mapas 1, 3 e 7 são os seguintes:

Mapa 1

Mapa 3

Mapa 7

Visitados

434658296

Tamanho

31449

Custo

35.3846.3131.11

2.2 Busca em profundidade

A implementação da busca em profundidade é muito similar à da busca em largura. A única diferença é que na busca em profundidade, a fronteira é implementada como uma pilha (LIFO), ao invés de uma fila (FIFO). O resultado esperado para os mapas 1, 3 e 7 são os seguintes:

Mapa 1

Mapa 3

Mapa 7

Visitados

98677234

Tamanho

3134099

Custo

35.38351.42131.32

2.3 Busca de custo uniforme

A implementação da busca de custo uniforme também é muito similar à da busca em largura. A diferença é que na busca de custo uniforme, a fronteira é implementada como uma fila de prioridades (heap), onde as prioridades dos nós são dadas pelo custo de seus caminhos até a raiz. O Python possui o módulo heapq que já foi importado para você criar e manipular a fila de prioridade. Além disso, um estado já visitado pode ser adicionado novamente na fronteira caso o custo de seu caminho atual seja menor do que o custo do caminho encontrado até agora. O resultado esperado para os mapas 1, 3 e 7 são os seguintes:

Mapa 1

Mapa 3

Mapa 7

Visitados

434607328

Tamanho

314415

Custo

35.3844.6515.65

3. Busca informada

Após concluir a implementação dos algoritmos de busca não informada, você pode iniciar a implementação dos algoritmos de busca informada.

3.1 Funções heurísticas

O primeiro passo consiste em implementar as funções heurísticas de distância euclidiana h_euclidean e de distância de Manhattan h_manhattan. Ambas recebem como parâmetros um estado s e um estado final g, e devem retornar a distância entre esses dois estados.

3.2 Busca gulosa por melhor escolha

O primeiro algoritmo de busca informada que você irá implementar é a busca gulosa por melhor escolha. Assim como os algoritmos de busca não informada, este também será muito similar à implementação da busca em largura. A diferença é que a fronteira é implementada como uma fila de prioridades (heap), onde a prioridade de um nó s é dada pela função heurística h(s, g). O resultado esperado para os mapas 1, 3 e 7 com a heurística de distância euclidiana são os seguintes:

Mapa 1

Mapa 3

Mapa 7

Visitados

9845430

Tamanho

31509

Custo

35.3854.7922.0

3.3 Algoritmo A*

O segundo algoritmo é o A*, que combina a busca gulosa por melhor escolha e por custo uniforme. Dessa forma, a fronteira também é implementada como uma fila de prioridades (heap), porém a prioridade de um nó s é dada pela soma g(s) + h(s, g), da função heurística h(s, g) com o custo g(s) do melhor caminho encontrado até agora para s. Além disso, assim como na busca por custo uniforme, um estado já visitado pode ser adicionado novamente na fronteira caso o custo de seu caminho atual seja menor do que o custo do caminho encontrado até agora. O resultado esperado para os mapas 1, 3 e 7 com a heurística de distância euclidiana são os seguintes:

Mapa 1

Mapa 3

Mapa 7

Visitados

319582165

Tamanho

314415

Custo

35.3844.6515.65

4. Relatório

Após concluir a implementação dos algoritmos de busca, você irá criar casos de teste e reportar os resultados encontrados. Crie pelo menos 7 novos mapas (com tamanhos distintos) para analisar sua implementação, cada um com uma das seguintes características:

  • BFS não é ótimo
  • BFS é equivalente à UCS
  • DFS retorna a solução ótima
  • Greedy é ótimo
  • Greedy não é ótimo
  • A* é melhor que UCS
  • A* é equivalente à UCS

Compare o desempenho dos algoritmos em relação ao número de nós visitados, custo e tamanho do caminho retornado (esses valores são impressos no terminal pelo servidor após a execução da busca). Nos mapas de teste dos algoritmos de busca informada, discuta o impacto das diferentes heurísticas no desempenho dos algoritmos. O seu relatório deve conter no máximo 2 páginas no formato da Association for the Advancement of Artificial Intelligence (AAAI) (disponível também no Overleaf).

Submissão

Para submeter o seu trabalho, submeta o seu arquivo search.py e o seu relatório relatorio.pdf (em formato pdf) na tarefa TP1 - Busca no espaço de estados no PVANet.

Referências