diff --git a/.gitignore b/.gitignore index 1e7ce8f..0d761d5 100644 --- a/.gitignore +++ b/.gitignore @@ -135,4 +135,5 @@ dmypy.json run.sh benchmarks/ -tree_search_legacy.py \ No newline at end of file +tree_search_legacy.py +.vscode/ \ No newline at end of file diff --git a/graph.py b/graph.py index 4f6ca35..8785f54 100644 --- a/graph.py +++ b/graph.py @@ -1,4 +1,6 @@ import matplotlib.pyplot as plt +import numpy as np +import os def linear_graph(filename, type_): x = [] @@ -25,6 +27,89 @@ def bar_graph(filename): y.append(int(line[1])) return x, y +def plot_linear_regression(xlist, ylist, labels): + for i in range(len(xlist)): + x = xlist[i] + y = ylist[i] + m, b = np.polyfit(x, y, 1) + plt.plot(x, [m * i + b for i in x], label=labels[i]) + +def show_graphs(LEVELS_PACK): + # Gráfico com retas x = nível, y = tempo de execução, para cada strategy + x1, y1 = linear_graph(f"benchmarks/{LEVELS_PACK}/depth.csv", "time") + x2, y2 = linear_graph(f"benchmarks/{LEVELS_PACK}/breadth.csv", "time") + x3, y3 = linear_graph(f"benchmarks/{LEVELS_PACK}/uniform.csv", "time") + x4, y4 = linear_graph(f"benchmarks/{LEVELS_PACK}/greedy.csv", "time") + x5, y5 = linear_graph(f"benchmarks/{LEVELS_PACK}/a*.csv", "time") + + plot_linear_regression([x1, x2, x3, x4, x5], [y1, y2, y3, y4, y5], ["Depth", "Breadth", "Uniform", "Greedy", "A*"]) + + if len(x1) < 20: + plt.xticks(np.arange(min(x1), max(x1) + 1, 1.0)) + + axes = plt.gca() + axes.set_xlim([0, None]) + axes.set_ylim([0, None]) + plt.xlabel("Level") + plt.ylabel("Time (s)") + plt.title(f"Linear regression of time per level ({LEVELS_PACK})") + plt.legend() + plt.savefig(f"graphs/{LEVELS_PACK}/time_graph.png") + plt.show() + + # Gráfico com retas x = nível, y = número de nós expandidos, para cada strategy + x1, y1 = linear_graph(f"benchmarks/{LEVELS_PACK}/depth.csv", "nodes") + x2, y2 = linear_graph(f"benchmarks/{LEVELS_PACK}/breadth.csv", "nodes") + x3, y3 = linear_graph(f"benchmarks/{LEVELS_PACK}/uniform.csv", "nodes") + x4, y4 = linear_graph(f"benchmarks/{LEVELS_PACK}/greedy.csv", "nodes") + x5, y5 = linear_graph(f"benchmarks/{LEVELS_PACK}/a*.csv", "nodes") + + plot_linear_regression([x1, x2, x3, x4, x5], [y1, y2, y3, y4, y5], ["Depth", "Breadth", "Uniform", "Greedy", "A*"]) + + if len(x1) < 20: + plt.xticks(np.arange(min(x1), max(x1) + 1, 1.0)) + + axes = plt.gca() + axes.set_xlim([0, None]) + axes.set_ylim([0, None]) + plt.xlabel("Level") + plt.ylabel("Number of expanded nodes") + plt.title(f"Linear regression of number of expanded nodes per level ({LEVELS_PACK})") + plt.legend() + plt.savefig(f"graphs/{LEVELS_PACK}/nodes_graph.png") + plt.show() + + # Gráfico com retas x = nível, y = número de peças movidas, para cada strategy + x1, y1 = linear_graph(f"benchmarks/{LEVELS_PACK}/depth.csv", "moves") + x2, y2 = linear_graph(f"benchmarks/{LEVELS_PACK}/breadth.csv", "moves") + x3, y3 = linear_graph(f"benchmarks/{LEVELS_PACK}/uniform.csv", "moves") + x4, y4 = linear_graph(f"benchmarks/{LEVELS_PACK}/greedy.csv", "moves") + x5, y5 = linear_graph(f"benchmarks/{LEVELS_PACK}/a*.csv", "moves") + + plot_linear_regression([x1, x2, x3, x4, x5], [y1, y2, y3, y4, y5], ["Depth", "Breadth", "Uniform", "Greedy", "A*"]) + + if len(x1) < 20: + plt.xticks(np.arange(min(x1), max(x1) + 1, 1.0)) + + axes = plt.gca() + axes.set_xlim([0, None]) + axes.set_ylim([0, None]) + plt.xlabel("Level") + plt.ylabel("Number of moved pieces") + plt.title(f"Linear regression of number of moved pieces per level ({LEVELS_PACK})") + plt.legend() + plt.savefig(f"graphs/{LEVELS_PACK}/moves_graph.png") + plt.show() + +if not os.path.exists("graphs"): + os.mkdir("graphs") +if not os.path.exists("graphs/levels1"): + os.mkdir("graphs/levels1") +if not os.path.exists("graphs/levels2"): + os.mkdir("graphs/levels2") +if not os.path.exists("graphs/levels"): + os.mkdir("graphs/levels") + """ levels1.txt: pack de níveis 6x6 da entrega preliminar 1) Breadth 488 640 @@ -33,10 +118,20 @@ def bar_graph(filename): 4) A* 489 576 """ -# Gráfico com retas x = nível, y = tempo de execução, para cada strategy -# Gráfico com retas x = nível, y = número de nós expandidos, para cada strategy -# Gráfico com retas x = nível, y = número de peças movidas, para cada strategy +show_graphs("levels1") + # Gráfico de barras x = strategy, y = pontos +x = ["Breadth", "Uniform", "Greedy", "A*"] +y = [488640, 489739, 487299, 489576] + +plt.bar(x, y, color=["orange", "green", "red", "purple"]) +plt.bar_label(plt.gca().containers[0], padding=3) +plt.xlabel("Strategy") +plt.ylabel("Points") +plt.title("Points per strategy (levels1)") +plt.ylim(487000, 490000) +plt.savefig("graphs/levels1/points_graph.png") +plt.show() """ levels2.txt: pack de níveis 8x8 @@ -46,10 +141,21 @@ def bar_graph(filename): 4) A* 1 039 496 """ -# Gráfico com retas x = nível, y = tempo de execução, para cada strategy -# Gráfico com retas x = nível, y = número de nós expandidos, para cada strategy -# Gráfico com retas x = nível, y = número de peças movidas, para cada strategy -# Gráfico de barras x = strategy, y = pontos +show_graphs("levels2") + +# Gráfico de barras x = strategy, y = pontos +x = ["Breadth", "Uniform", "Greedy", "A*"] +y = [1041039, 1040375, 1041987, 1039496] + +plt.bar(x, y, color=["orange", "green", "red", "purple"]) +plt.bar_label(plt.gca().containers[0], padding=3, fmt="%d") +plt.xlabel("Strategy") +plt.ylabel("Points") +plt.title("Points per strategy (levels2)") +plt.ylim(1039000, 1043000) +plt.ticklabel_format(style='plain', axis='y') +plt.savefig("graphs/levels2/points_graph.png") +plt.show() """ levels.txt: pack definitivo para a entrega final @@ -60,7 +166,79 @@ def bar_graph(filename): 5) Híbrido (Uniform para 6x6 + Greedy para 8x8) 1 558 015 """ -# Gráfico com retas x = nível, y = tempo de execução, para cada strategy -# Gráfico com retas x = nível, y = número de nós expandidos, para cada strategy -# Gráfico com retas x = nível, y = número de peças movidas, para cada strategy +# # Gráfico com retas x = nível, y = tempo de execução, para cada strategy +# x1, y1 = linear_graph(f"benchmarks/levels/depth.csv", "time") +# x2, y2 = linear_graph(f"benchmarks/levels/breadth.csv", "time") +# x3, y3 = linear_graph(f"benchmarks/levels/uniform.csv", "time") +# x4, y4 = linear_graph(f"benchmarks/levels/greedy.csv", "time") +# x5, y5 = linear_graph(f"benchmarks/levels/a*.csv", "time") +# x6, y6 = linear_graph(f"benchmarks/levels/hybrid.csv", "time") + +# plt.plot(x1, y1, label="Depth", color="orange") +# plt.plot(x2, y2, label="Breadth", color="green") +# plt.plot(x3, y3, label="Uniform", color="red") +# plt.plot(x4, y4, label="Greedy", color="purple") +# plt.plot(x5, y5, label="A*", color="blue") + +# plt.xlabel("Level") +# plt.ylabel("Time (s)") +# plt.title("Time per level (levels)") +# plt.legend() +# #plt.show() + +# # Gráfico com retas x = nível, y = número de nós expandidos, para cada strategy +# x1, y1 = linear_graph(f"benchmarks/levels/depth.csv", "nodes") +# x2, y2 = linear_graph(f"benchmarks/levels/breadth.csv", "nodes") +# x3, y3 = linear_graph(f"benchmarks/levels/uniform.csv", "nodes") +# x4, y4 = linear_graph(f"benchmarks/levels/greedy.csv", "nodes") +# x5, y5 = linear_graph(f"benchmarks/levels/a*.csv", "nodes") +# x6, y6 = linear_graph(f"benchmarks/levels/hybrid.csv", "nodes") + +# plt.plot(x1, y1, label="Depth", color="orange") +# plt.plot(x2, y2, label="Breadth", color="green") +# plt.plot(x3, y3, label="Uniform", color="red") +# plt.plot(x4, y4, label="Greedy", color="purple") +# plt.plot(x5, y5, label="A*", color="blue") +# plt.plot(x6, y6, label="Hybrid", color="black") + +# plt.xlabel("Level") +# plt.ylabel("Number of expanded nodes") +# plt.title("Expanded nodes per level (levels)") +# plt.legend() +# #plt.show() + +# # Gráfico com retas x = nível, y = número de peças movidas, para cada strategy +# x1, y1 = linear_graph(f"benchmarks/levels/depth.csv", "moves") +# x2, y2 = linear_graph(f"benchmarks/levels/breadth.csv", "moves") +# x3, y3 = linear_graph(f"benchmarks/levels/uniform.csv", "moves") +# x4, y4 = linear_graph(f"benchmarks/levels/greedy.csv", "moves") +# x5, y5 = linear_graph(f"benchmarks/levels/a*.csv", "moves") +# x6, y6 = linear_graph(f"benchmarks/levels/hybrid.csv", "moves") + +# plt.plot(x1, y1, label="Depth", color="orange") +# plt.plot(x2, y2, label="Breadth", color="green") +# plt.plot(x3, y3, label="Uniform", color="red") +# plt.plot(x4, y4, label="Greedy", color="purple") +# plt.plot(x5, y5, label="A*", color="blue") +# plt.plot(x6, y6, label="Hybrid", color="black") + +# plt.xlabel("Level") +# plt.ylabel("Number of moved pieces") +# plt.title("Moved pieces per level (levels)") +# plt.legend() +# #plt.show() + # Gráfico de barras x = strategy, y = pontos + +x = ["Breadth", "Uniform", "Greedy", "A*", "Hybrid"] +y = [1556748, 1557267, 1556135, 1556969, 1558015] + +plt.bar(x, y, color=["orange", "green", "red", "purple", "blue"]) +plt.bar_label(plt.gca().containers[0], padding=3, fmt="%d") +plt.xlabel("Strategy") +plt.ylabel("Points") +plt.title("Points per strategy (levels)") +plt.ylim(1555000, 1559000) +plt.ticklabel_format(style='plain', axis='y') +plt.savefig("graphs/levels/points_graph.png") +plt.show() \ No newline at end of file diff --git a/graphs/levels/points_graph.png b/graphs/levels/points_graph.png new file mode 100644 index 0000000..efa0c38 Binary files /dev/null and b/graphs/levels/points_graph.png differ diff --git a/graphs/levels1/moves_graph.png b/graphs/levels1/moves_graph.png new file mode 100644 index 0000000..4a677ae Binary files /dev/null and b/graphs/levels1/moves_graph.png differ diff --git a/graphs/levels1/nodes_graph.png b/graphs/levels1/nodes_graph.png new file mode 100644 index 0000000..d4c6b6c Binary files /dev/null and b/graphs/levels1/nodes_graph.png differ diff --git a/graphs/levels1/points_graph.png b/graphs/levels1/points_graph.png new file mode 100644 index 0000000..9922a2e Binary files /dev/null and b/graphs/levels1/points_graph.png differ diff --git a/graphs/levels1/time_graph.png b/graphs/levels1/time_graph.png new file mode 100644 index 0000000..75e6507 Binary files /dev/null and b/graphs/levels1/time_graph.png differ diff --git a/graphs/levels2/moves_graph.png b/graphs/levels2/moves_graph.png new file mode 100644 index 0000000..7d0864e Binary files /dev/null and b/graphs/levels2/moves_graph.png differ diff --git a/graphs/levels2/nodes_graph.png b/graphs/levels2/nodes_graph.png new file mode 100644 index 0000000..50b23cd Binary files /dev/null and b/graphs/levels2/nodes_graph.png differ diff --git a/graphs/levels2/points_graph.png b/graphs/levels2/points_graph.png new file mode 100644 index 0000000..9b669f7 Binary files /dev/null and b/graphs/levels2/points_graph.png differ diff --git a/graphs/levels2/time_graph.png b/graphs/levels2/time_graph.png new file mode 100644 index 0000000..1e9fbb4 Binary files /dev/null and b/graphs/levels2/time_graph.png differ diff --git a/report.md b/report.md index 16af2de..8cb190a 100644 --- a/report.md +++ b/report.md @@ -38,6 +38,8 @@ Numa primeira fase do projeto, entendemos que deveríamos dividir o desenvolvime - Numa primeira versão do search, a estrutura grid_visited era uma lista. Após discutirmos estratégias de otimização, com o grupo "102536_102778", concluímos que seria mais eficiente utilizar um conjunto (set), uma vez que a operação de verificação de pertença (lookup) apresenta uma complexidade O(1), enquanto que, nas listas, é O(n). Em versões posteriores, mantivemos esta estrutura. Contudo, há que salientar que a complexidade do lookup com chave, nos dicionários, é também O(1). - Tendo por base conhecimentos prévios de Algoritmos e Estruturas de Dados, ponderámos a utilização de uma min-heap, como estrutura para guardar os nós por explorar. Para esse efeito, aproveitámos o módulo heapq, nativo do Python. Os resultados foram bastante positivos, dado que cada inserção garante a ordenação da heap, não sendo necessária qualquer operação ulterior, com vista a obter o valor mínimo. O critério de ordenação é definido pelo método **lt** da classe de objetos que compõem a heap, neste caso, a Matrix ou a MatrixForGreedy. +### HEURÍSTICA + ### Agente O agente em cada iteração sensoriza o jogo através das funções detectCrazy e detectStuck. diff --git a/tree_search.py b/tree_search.py index d28d339..01fa71d 100644 --- a/tree_search.py +++ b/tree_search.py @@ -107,7 +107,13 @@ def __lt__(self, other): return (self.heuristic, self.idx) < (other.heuristic, other.idx) class MatrixForAStar(Matrix): + """ + Tree node with a puzzle state, for A* search. + Instances are firstly compared by total cost (heuristic + cost) and then, if necessary, by creation order. + """ + counter = 0 + def __init__(self, grid, action=[], parent=None, cost=0, heuristic=0, cursor=[3, 3]): super().__init__(grid, action, parent, cost, heuristic, cursor) MatrixForAStar.counter += 1 @@ -341,7 +347,7 @@ def main(): LEVELS_PACKS = ["levels1", "levels2", "levels"] STRATEGIES = ["depth", "breadth", "greedy", "uniform", "a*"] - SKIP_CONTEXTS = {("levels2", "a*"), ("levels", "a*")} + SKIP_CONTEXTS = {} # ex.: SKIP_CONTEXTS = [("levels2", "depth"")] for LEVELS_PACK in LEVELS_PACKS: with open(LEVELS_PACK + ".txt", "r") as f, open(f"benchmarks/{LEVELS_PACK}/hybrid.csv", "w") as fout: @@ -405,6 +411,7 @@ def main(): result = f"{i},{time_},{t.expanded_nodes},{total_moves}" fout.write(result + "\n") print(f"{LEVELS_PACK} {STRATEGY} -> {total_time} seconds, {t.expanded_nodes} nodes expanded, {total_moves} moves") - + print() + if __name__ == "__main__": main() \ No newline at end of file