From af91c3effe36b997fb17e5ec78442099bef6e4f4 Mon Sep 17 00:00:00 2001 From: quirinecker Date: Sun, 19 Oct 2025 17:26:54 +0200 Subject: [PATCH] first commit --- boards/cyclic_map.json | 15 + boards/large.json | 1 + boards/narrow_path.json | 19 + boards/no_possible_path.json | 15 + boards/start_is_goal.json | 13 + boards/tiny0.json | 17 + boards/tiny1.json | 17 + boards/tiny2.json | 17 + boards/tiny3.json | 17 + boards/tiny4.json | 17 + pig_lite/.gitignore | 1 + pig_lite/.idea/.gitignore | 8 + .../inspectionProfiles/Project_Default.xml | 24 + .../inspectionProfiles/profiles_settings.xml | 6 + pig_lite/.idea/misc.xml | 7 + pig_lite/.idea/modules.xml | 8 + pig_lite/.idea/pig_lite.iml | 12 + pig_lite/.idea/vcs.xml | 6 + pig_lite/README.md | 3 + pig_lite/bayesian_net/__init__.py | 0 pig_lite/bayesian_net/bayesian_net.py | 154 ++ pig_lite/datastructures/__init__.py | 0 pig_lite/datastructures/priority_queue.py | 61 + pig_lite/datastructures/queue.py | 27 + pig_lite/datastructures/stack.py | 21 + pig_lite/decision_tree/dt_base.py | 61 + pig_lite/decision_tree/dt_node.py | 70 + pig_lite/decision_tree/training_set.py | 84 + pig_lite/environment/__init__.py | 0 pig_lite/environment/base.py | 60 + pig_lite/environment/gridworld.py | 360 ++++ pig_lite/game/__init__.py | 0 pig_lite/game/base.py | 87 + pig_lite/game/tictactoe.py | 371 ++++ pig_lite/instance_generation/__init__.py | 0 pig_lite/instance_generation/enc.py | 5 + .../instance_generation/problem_factory.py | 96 + pig_lite/problem/base.py | 92 + pig_lite/problem/simple_2d.py | 529 +++++ shell.nix | 15 + uninformed_search.ipynb | 1797 +++++++++++++++++ 41 files changed, 4113 insertions(+) create mode 100644 boards/cyclic_map.json create mode 100644 boards/large.json create mode 100644 boards/narrow_path.json create mode 100644 boards/no_possible_path.json create mode 100644 boards/start_is_goal.json create mode 100644 boards/tiny0.json create mode 100644 boards/tiny1.json create mode 100644 boards/tiny2.json create mode 100644 boards/tiny3.json create mode 100644 boards/tiny4.json create mode 100644 pig_lite/.gitignore create mode 100644 pig_lite/.idea/.gitignore create mode 100644 pig_lite/.idea/inspectionProfiles/Project_Default.xml create mode 100644 pig_lite/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 pig_lite/.idea/misc.xml create mode 100644 pig_lite/.idea/modules.xml create mode 100644 pig_lite/.idea/pig_lite.iml create mode 100644 pig_lite/.idea/vcs.xml create mode 100644 pig_lite/README.md create mode 100644 pig_lite/bayesian_net/__init__.py create mode 100644 pig_lite/bayesian_net/bayesian_net.py create mode 100644 pig_lite/datastructures/__init__.py create mode 100644 pig_lite/datastructures/priority_queue.py create mode 100644 pig_lite/datastructures/queue.py create mode 100644 pig_lite/datastructures/stack.py create mode 100644 pig_lite/decision_tree/dt_base.py create mode 100644 pig_lite/decision_tree/dt_node.py create mode 100644 pig_lite/decision_tree/training_set.py create mode 100644 pig_lite/environment/__init__.py create mode 100644 pig_lite/environment/base.py create mode 100644 pig_lite/environment/gridworld.py create mode 100644 pig_lite/game/__init__.py create mode 100644 pig_lite/game/base.py create mode 100644 pig_lite/game/tictactoe.py create mode 100644 pig_lite/instance_generation/__init__.py create mode 100644 pig_lite/instance_generation/enc.py create mode 100644 pig_lite/instance_generation/problem_factory.py create mode 100644 pig_lite/problem/base.py create mode 100644 pig_lite/problem/simple_2d.py create mode 100644 shell.nix create mode 100644 uninformed_search.ipynb diff --git a/boards/cyclic_map.json b/boards/cyclic_map.json new file mode 100644 index 0000000..be0b52c --- /dev/null +++ b/boards/cyclic_map.json @@ -0,0 +1,15 @@ +{ + "type": "Simple2DProblem", + "board": [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ], + "costs": [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1] + ], + "start_state": [0, 0], + "end_state": [2, 2] +} diff --git a/boards/large.json b/boards/large.json new file mode 100644 index 0000000..7ca4dff --- /dev/null +++ b/boards/large.json @@ -0,0 +1 @@ +{"type": "Simple2DProblem", "board": [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1], [0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], [0, 0, 0, 1, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1], [1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1], [0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1], [0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], [0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 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, 1, 0, 0, 0, 1, 0, 0, 0, 1], [0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1], [0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1], [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 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, 1], [1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], [0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1], [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1], [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1], [0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1], [0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1], [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1]], "costs": [[4, 4, 1, 4, 4, 4, 1, 1, 2, 2, 3, 1, 4, 2, 1, 1, 2, 9, 3, 2, 3, 1, 1, 9, 3, 3, 4, 4, 2, 1, 3, 3, 3, 3, 4, 3, 4, 3, 3, 2, 3, 2, 3, 4, 1, 2, 4, 4, 1, 9], [1, 2, 4, 4, 1, 3, 3, 2, 3, 9, 9, 4, 4, 3, 3, 1, 3, 3, 3, 3, 2, 1, 2, 3, 4, 2, 2, 1, 3, 2, 2, 4, 4, 2, 4, 1, 2, 9, 4, 2, 3, 3, 9, 1, 2, 3, 4, 3, 2, 3], [3, 2, 4, 3, 3, 1, 4, 9, 4, 1, 4, 3, 2, 3, 1, 4, 4, 2, 3, 1, 4, 2, 3, 2, 3, 2, 2, 3, 3, 1, 2, 3, 1, 4, 2, 3, 2, 1, 1, 2, 4, 2, 1, 1, 3, 1, 2, 3, 3, 3], [1, 4, 4, 3, 2, 3, 3, 2, 1, 1, 2, 2, 3, 2, 1, 9, 3, 4, 2, 9, 4, 1, 4, 4, 4, 1, 3, 4, 1, 4, 3, 1, 4, 3, 2, 4, 4, 1, 3, 3, 9, 3, 1, 2, 9, 4, 1, 4, 4, 2], [1, 4, 3, 2, 3, 3, 3, 2, 2, 1, 4, 1, 2, 1, 4, 3, 1, 4, 2, 3, 3, 1, 3, 4, 2, 1, 3, 4, 3, 4, 3, 3, 1, 3, 1, 2, 3, 4, 2, 4, 2, 1, 2, 2, 4, 1, 1, 3, 4, 4], [3, 1, 2, 4, 9, 4, 9, 2, 3, 4, 1, 4, 1, 2, 3, 2, 4, 9, 4, 2, 2, 4, 4, 2, 1, 3, 4, 4, 4, 1, 1, 4, 1, 4, 9, 9, 2, 9, 2, 3, 3, 1, 9, 4, 3, 1, 3, 3, 1, 9], [4, 3, 2, 3, 3, 2, 2, 3, 2, 4, 3, 2, 2, 4, 4, 9, 2, 4, 2, 4, 1, 3, 2, 9, 2, 4, 4, 1, 2, 3, 4, 4, 2, 2, 2, 2, 1, 1, 3, 3, 4, 2, 4, 4, 1, 2, 3, 4, 3, 9], [4, 1, 9, 9, 1, 4, 4, 1, 4, 4, 9, 9, 4, 1, 4, 1, 4, 1, 4, 3, 4, 1, 1, 4, 1, 4, 2, 2, 3, 2, 3, 3, 4, 1, 1, 2, 2, 3, 3, 4, 3, 1, 1, 4, 2, 3, 2, 4, 3, 1], [2, 4, 4, 2, 4, 1, 3, 2, 3, 2, 4, 1, 4, 3, 3, 1, 1, 2, 4, 4, 1, 4, 1, 9, 2, 1, 2, 3, 4, 1, 1, 2, 3, 2, 1, 1, 4, 4, 2, 9, 1, 3, 3, 1, 1, 2, 1, 3, 2, 3], [2, 4, 3, 4, 4, 3, 1, 3, 3, 1, 3, 4, 1, 4, 4, 9, 3, 1, 3, 4, 3, 1, 2, 2, 1, 9, 3, 2, 9, 9, 2, 9, 3, 9, 2, 3, 2, 1, 1, 2, 4, 1, 9, 3, 3, 4, 2, 2, 2, 2], [4, 3, 2, 4, 2, 3, 2, 4, 2, 3, 4, 4, 3, 1, 2, 4, 4, 3, 4, 2, 1, 3, 1, 2, 2, 3, 4, 2, 1, 3, 4, 9, 1, 3, 1, 2, 2, 4, 2, 1, 1, 1, 2, 1, 3, 1, 3, 1, 3, 1], [2, 2, 2, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 2, 9, 2, 4, 4, 1, 4, 3, 4, 1, 4, 2, 4, 9, 2, 2, 4, 1, 4, 3, 4, 2, 1, 2, 1, 3, 2, 4, 1, 1, 1, 2, 4, 2, 3, 2, 1], [4, 1, 4, 2, 3, 4, 1, 2, 3, 2, 2, 1, 1, 2, 4, 1, 4, 1, 3, 2, 2, 2, 3, 9, 2, 3, 1, 2, 3, 1, 1, 3, 4, 1, 1, 2, 3, 1, 4, 4, 1, 1, 3, 1, 1, 3, 1, 2, 3, 3], [2, 2, 3, 4, 1, 4, 1, 9, 3, 2, 1, 3, 2, 3, 4, 4, 1, 1, 1, 2, 4, 3, 9, 1, 3, 4, 4, 4, 1, 1, 3, 9, 2, 3, 2, 1, 2, 3, 4, 4, 3, 2, 2, 4, 3, 1, 4, 4, 1, 2], [4, 1, 4, 1, 1, 1, 4, 3, 4, 3, 1, 3, 2, 2, 2, 2, 1, 1, 3, 4, 2, 3, 3, 1, 4, 2, 3, 4, 3, 4, 4, 1, 1, 3, 3, 4, 3, 3, 3, 4, 4, 4, 4, 4, 1, 3, 1, 4, 3, 3], [4, 9, 4, 2, 3, 4, 3, 9, 4, 3, 3, 9, 2, 2, 2, 4, 2, 2, 1, 2, 3, 1, 1, 2, 3, 4, 1, 4, 2, 3, 4, 2, 9, 1, 3, 2, 3, 4, 2, 1, 4, 1, 2, 9, 3, 4, 4, 3, 3, 9], [3, 2, 2, 4, 1, 4, 1, 4, 1, 3, 3, 1, 1, 2, 3, 1, 3, 1, 3, 1, 3, 4, 4, 1, 2, 4, 3, 1, 1, 4, 4, 3, 4, 9, 4, 1, 2, 1, 4, 2, 1, 3, 4, 1, 4, 2, 3, 2, 3, 4], [2, 9, 3, 9, 1, 1, 1, 2, 2, 9, 4, 9, 3, 9, 4, 4, 2, 1, 1, 3, 4, 2, 1, 1, 1, 2, 3, 1, 1, 3, 3, 4, 4, 1, 4, 2, 4, 4, 1, 1, 1, 1, 4, 2, 3, 4, 9, 3, 3, 1], [1, 9, 4, 9, 4, 2, 3, 1, 2, 3, 2, 3, 4, 3, 2, 2, 3, 3, 3, 3, 2, 3, 4, 2, 4, 1, 2, 4, 3, 3, 3, 4, 1, 3, 4, 4, 1, 2, 2, 3, 1, 2, 2, 3, 2, 4, 3, 2, 1, 9], [4, 4, 2, 3, 4, 9, 1, 4, 1, 3, 2, 1, 2, 1, 3, 3, 2, 9, 3, 2, 1, 2, 1, 3, 2, 1, 3, 3, 4, 3, 4, 3, 4, 3, 2, 2, 3, 3, 2, 3, 2, 3, 3, 1, 1, 2, 3, 9, 1, 3], [3, 1, 1, 3, 1, 3, 2, 3, 1, 4, 4, 3, 4, 3, 1, 3, 2, 1, 4, 3, 2, 3, 3, 4, 3, 3, 2, 1, 1, 2, 4, 3, 1, 4, 4, 1, 4, 3, 2, 2, 3, 4, 3, 1, 1, 1, 2, 4, 4, 2], [1, 3, 1, 1, 4, 3, 1, 4, 3, 1, 1, 4, 1, 2, 3, 9, 4, 1, 4, 2, 3, 3, 3, 2, 2, 4, 2, 1, 4, 4, 3, 4, 1, 2, 9, 2, 3, 2, 3, 4, 3, 2, 1, 3, 1, 3, 2, 3, 2, 2], [2, 3, 2, 3, 2, 2, 1, 4, 1, 3, 1, 1, 2, 1, 3, 9, 4, 3, 1, 4, 3, 4, 3, 4, 3, 2, 3, 1, 2, 1, 2, 1, 3, 2, 4, 4, 1, 1, 1, 3, 1, 4, 3, 1, 3, 1, 2, 2, 3, 2], [4, 1, 2, 2, 4, 2, 2, 1, 3, 1, 1, 1, 4, 3, 3, 3, 1, 2, 4, 3, 4, 3, 3, 2, 1, 4, 9, 9, 4, 2, 2, 4, 2, 9, 1, 4, 4, 2, 3, 1, 4, 9, 2, 9, 3, 2, 4, 2, 4, 4], [1, 1, 3, 3, 4, 4, 2, 3, 2, 4, 4, 2, 3, 1, 1, 3, 1, 1, 3, 1, 3, 3, 4, 2, 3, 3, 4, 4, 3, 3, 3, 2, 1, 1, 4, 4, 3, 9, 4, 3, 4, 1, 3, 4, 1, 1, 3, 9, 1, 1], [4, 9, 4, 3, 2, 2, 1, 4, 4, 3, 9, 1, 2, 1, 2, 2, 4, 2, 9, 4, 4, 1, 1, 4, 2, 9, 2, 4, 2, 1, 4, 3, 1, 3, 4, 1, 1, 2, 3, 2, 1, 3, 1, 1, 1, 3, 1, 1, 3, 9], [3, 2, 1, 4, 2, 2, 4, 4, 1, 3, 3, 4, 1, 1, 2, 2, 4, 3, 3, 2, 3, 3, 2, 2, 1, 3, 1, 3, 1, 1, 2, 9, 4, 1, 3, 3, 4, 1, 3, 4, 4, 1, 4, 1, 2, 4, 2, 3, 3, 4], [3, 3, 9, 2, 4, 1, 2, 4, 1, 4, 1, 4, 1, 3, 3, 1, 2, 9, 2, 2, 3, 3, 4, 1, 4, 4, 1, 4, 2, 3, 3, 1, 2, 3, 2, 2, 3, 4, 3, 3, 1, 4, 4, 1, 4, 1, 4, 1, 9, 3], [2, 4, 1, 3, 4, 2, 4, 1, 1, 3, 4, 2, 2, 3, 4, 4, 2, 2, 1, 1, 4, 4, 3, 1, 4, 1, 3, 4, 4, 9, 2, 4, 2, 2, 1, 9, 1, 1, 1, 1, 1, 1, 3, 4, 2, 3, 1, 4, 2, 2], [3, 3, 1, 3, 1, 1, 3, 4, 4, 4, 1, 9, 2, 1, 2, 4, 1, 4, 2, 2, 1, 1, 2, 2, 1, 9, 1, 2, 2, 3, 2, 3, 4, 1, 1, 9, 4, 2, 1, 1, 2, 1, 4, 4, 3, 4, 3, 1, 1, 2], [4, 2, 4, 4, 2, 4, 2, 2, 3, 4, 2, 3, 3, 3, 2, 2, 2, 2, 3, 2, 1, 2, 4, 4, 4, 2, 3, 2, 2, 2, 3, 1, 3, 1, 3, 4, 4, 1, 4, 2, 4, 2, 4, 2, 3, 1, 4, 1, 3, 3], [1, 1, 1, 1, 1, 1, 1, 9, 4, 3, 3, 1, 3, 3, 2, 1, 3, 2, 4, 1, 4, 9, 1, 2, 2, 2, 4, 3, 3, 1, 1, 1, 3, 3, 4, 4, 4, 2, 9, 3, 4, 4, 3, 4, 3, 9, 9, 3, 4, 2], [3, 4, 4, 4, 1, 4, 3, 1, 4, 1, 2, 4, 2, 2, 4, 1, 3, 2, 4, 4, 3, 1, 1, 1, 1, 2, 4, 3, 4, 3, 3, 9, 4, 1, 4, 4, 4, 2, 2, 1, 3, 3, 2, 4, 1, 3, 3, 1, 1, 4], [2, 3, 3, 3, 2, 2, 2, 4, 3, 3, 3, 2, 1, 2, 3, 1, 1, 4, 4, 2, 4, 3, 1, 4, 2, 1, 2, 4, 2, 1, 4, 3, 1, 4, 4, 1, 3, 3, 1, 1, 4, 3, 3, 3, 4, 2, 2, 4, 4, 4], [2, 3, 3, 4, 1, 3, 4, 4, 3, 1, 4, 3, 2, 1, 1, 3, 3, 4, 2, 4, 4, 1, 4, 1, 2, 2, 3, 1, 2, 2, 2, 1, 2, 1, 3, 3, 3, 3, 2, 1, 3, 2, 1, 4, 3, 4, 4, 3, 2, 9], [1, 3, 2, 3, 4, 3, 2, 1, 1, 3, 4, 3, 2, 3, 9, 4, 2, 3, 1, 1, 3, 3, 3, 1, 3, 1, 2, 3, 4, 1, 3, 3, 3, 9, 1, 2, 2, 1, 1, 2, 1, 2, 4, 4, 1, 2, 1, 4, 1, 1], [4, 4, 3, 1, 1, 3, 3, 2, 2, 4, 4, 4, 1, 2, 4, 1, 1, 2, 3, 2, 2, 4, 1, 1, 3, 1, 3, 2, 4, 1, 3, 1, 2, 2, 4, 4, 1, 2, 1, 3, 2, 9, 2, 2, 4, 4, 3, 2, 2, 4], [1, 3, 2, 4, 1, 4, 1, 4, 2, 9, 4, 4, 4, 4, 2, 9, 4, 4, 1, 3, 3, 4, 3, 9, 3, 4, 1, 9, 3, 4, 4, 3, 2, 4, 4, 4, 3, 4, 9, 2, 4, 4, 4, 2, 9, 4, 3, 1, 3, 1], [4, 3, 3, 3, 2, 1, 2, 2, 4, 3, 3, 1, 2, 4, 4, 1, 4, 4, 4, 4, 2, 1, 1, 3, 4, 4, 3, 9, 1, 3, 3, 2, 4, 1, 1, 2, 2, 4, 3, 3, 3, 2, 2, 4, 2, 1, 4, 3, 3, 2], [1, 4, 3, 4, 3, 3, 3, 1, 4, 1, 2, 2, 4, 4, 2, 3, 1, 1, 2, 3, 2, 3, 1, 4, 4, 1, 1, 1, 4, 1, 3, 3, 4, 4, 3, 1, 3, 1, 4, 2, 1, 1, 1, 2, 2, 4, 4, 3, 3, 4], [2, 1, 1, 3, 2, 1, 4, 4, 2, 3, 1, 2, 2, 2, 1, 2, 2, 3, 1, 4, 2, 4, 3, 2, 3, 3, 2, 3, 3, 3, 4, 4, 2, 3, 4, 1, 3, 2, 3, 4, 3, 4, 3, 2, 1, 1, 1, 3, 1, 1], [4, 4, 3, 4, 3, 3, 4, 2, 1, 1, 2, 1, 2, 2, 3, 4, 1, 1, 1, 2, 3, 3, 1, 1, 9, 1, 3, 1, 2, 9, 4, 1, 4, 3, 3, 2, 4, 1, 3, 4, 2, 2, 3, 1, 4, 3, 4, 1, 4, 3], [2, 1, 4, 4, 3, 1, 4, 4, 2, 4, 4, 3, 3, 1, 1, 3, 2, 2, 4, 1, 3, 4, 4, 1, 4, 2, 4, 1, 1, 4, 2, 2, 1, 3, 2, 1, 2, 2, 1, 2, 2, 4, 4, 4, 3, 3, 3, 9, 4, 9], [4, 3, 4, 2, 4, 2, 2, 3, 3, 3, 1, 2, 3, 4, 2, 4, 1, 2, 2, 3, 2, 3, 9, 2, 3, 1, 3, 4, 3, 3, 3, 4, 4, 4, 1, 2, 4, 3, 2, 9, 3, 3, 3, 4, 1, 3, 4, 2, 3, 2], [2, 4, 1, 2, 3, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 1, 4, 2, 4, 2, 3, 1, 3, 4, 2, 4, 4, 1, 4, 2, 3, 4, 1, 2, 1, 1, 3, 2, 3, 2, 4, 1, 3, 3, 1, 4, 4, 2, 3], [3, 2, 2, 2, 1, 1, 4, 2, 3, 1, 1, 4, 2, 4, 3, 4, 3, 3, 3, 1, 3, 1, 3, 9, 2, 1, 9, 3, 3, 1, 1, 9, 1, 1, 4, 4, 3, 2, 4, 2, 1, 2, 4, 3, 4, 9, 3, 4, 3, 3], [2, 1, 2, 1, 2, 4, 1, 4, 3, 3, 3, 2, 2, 9, 1, 2, 2, 1, 1, 1, 3, 1, 3, 2, 1, 3, 4, 1, 1, 1, 2, 9, 1, 4, 2, 4, 1, 3, 2, 4, 2, 4, 2, 1, 2, 2, 2, 4, 1, 3], [4, 3, 1, 2, 4, 2, 2, 9, 1, 1, 4, 2, 4, 2, 4, 3, 4, 1, 3, 9, 1, 1, 3, 2, 2, 2, 4, 4, 3, 2, 2, 2, 1, 1, 1, 2, 4, 3, 2, 4, 3, 3, 3, 4, 9, 3, 2, 1, 1, 4], [4, 4, 4, 1, 1, 2, 3, 4, 3, 3, 1, 1, 2, 4, 4, 4, 3, 4, 3, 4, 4, 2, 2, 2, 4, 4, 4, 3, 3, 4, 3, 9, 1, 3, 4, 2, 3, 3, 3, 1, 4, 2, 1, 2, 3, 3, 4, 4, 2, 3], [3, 2, 2, 1, 2, 9, 4, 1, 3, 1, 3, 3, 4, 3, 2, 4, 2, 2, 1, 3, 3, 9, 3, 3, 1, 2, 1, 4, 9, 9, 3, 1, 4, 3, 4, 9, 4, 3, 1, 4, 2, 9, 2, 9, 1, 4, 3, 4, 1, 2]], "start_state": [0, 0], "end_state": [48, 48]} \ No newline at end of file diff --git a/boards/narrow_path.json b/boards/narrow_path.json new file mode 100644 index 0000000..e7abeba --- /dev/null +++ b/boards/narrow_path.json @@ -0,0 +1,19 @@ +{ + "type": "Simple2DProblem", + "board": [ + [0, 1, 1, 1, 1], + [0, 0, 0, 0, 0], + [1, 1, 1, 1, 0], + [1, 1, 1, 1, 0], + [1, 1, 1, 1, 1] + ], + "costs": [ + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1] + ], + "start_state": [0, 0], + "end_state": [2, 4] +} diff --git a/boards/no_possible_path.json b/boards/no_possible_path.json new file mode 100644 index 0000000..bccb145 --- /dev/null +++ b/boards/no_possible_path.json @@ -0,0 +1,15 @@ +{ + "type": "Simple2DProblem", + "board": [ + [0, 0, 1], + [1, 1, 1], + [0, 0, 0] + ], + "costs": [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1] + ], + "start_state": [0, 0], + "end_state": [2, 2] +} diff --git a/boards/start_is_goal.json b/boards/start_is_goal.json new file mode 100644 index 0000000..5f4f4b4 --- /dev/null +++ b/boards/start_is_goal.json @@ -0,0 +1,13 @@ +{ + "type": "Simple2DProblem", + "board": [ + [0, 0], + [0, 0] + ], + "costs": [ + [1, 1], + [1, 1] + ], + "start_state": [0, 0], + "end_state": [0, 0] +} diff --git a/boards/tiny0.json b/boards/tiny0.json new file mode 100644 index 0000000..5fbd546 --- /dev/null +++ b/boards/tiny0.json @@ -0,0 +1,17 @@ +{"type": "Simple2DProblem", +"board": [ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 1, 0, 1, 0], + [0, 1, 0, 0, 0] +], +"costs": [ + [4, 4, 3, 1, 0], + [3, 4, 2, 2, 2], + [4, 3, 3, 4, 0], + [4, 4, 3, 3, 0], + [4, 3, 1, 1, 2] +], +"start_state": [0, 0], +"end_state": [4, 4]} \ No newline at end of file diff --git a/boards/tiny1.json b/boards/tiny1.json new file mode 100644 index 0000000..9ec5450 --- /dev/null +++ b/boards/tiny1.json @@ -0,0 +1,17 @@ +{"type": "Simple2DProblem", +"board": [ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 1, 0, 1, 1], + [0, 0, 0, 0, 0] +], +"costs": [ + [4, 8, 8, 8, 8], + [1, 4, 2, 2, 1], + [1, 8, 8, 8, 1], + [1, 4, 3, 3, 1], + [1, 1, 1, 1, 1] + ], +"start_state": [0, 0], +"end_state": [2, 4]} \ No newline at end of file diff --git a/boards/tiny2.json b/boards/tiny2.json new file mode 100644 index 0000000..c4dc8f9 --- /dev/null +++ b/boards/tiny2.json @@ -0,0 +1,17 @@ +{"type": "Simple2DProblem", +"board": [ + [0, 0, 0, 0, 0], + [1, 1, 1, 1, 0], + [1, 0, 0, 1, 0], + [1, 0, 1, 1, 0], + [1, 0, 0, 0, 0] +], +"costs": [ + [4, 1, 2, 2, 3], + [1, 4, 2, 2, 3], + [1, 4, 2, 2, 3], + [1, 4, 3, 3, 1], + [1, 1, 1, 1, 1] + ], +"start_state": [0, 0], +"end_state": [2, 2]} \ No newline at end of file diff --git a/boards/tiny3.json b/boards/tiny3.json new file mode 100644 index 0000000..59e3b30 --- /dev/null +++ b/boards/tiny3.json @@ -0,0 +1,17 @@ +{"type": "Simple2DProblem", +"board": [ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 1, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0] +], +"costs": [ + [4, 1, 2, 2, 3], + [1, 4, 2, 2, 3], + [1, 4, 2, 2, 3], + [1, 4, 3, 3, 1], + [1, 1, 1, 1, 1] + ], +"start_state": [0, 0], +"end_state": [2, 2]} \ No newline at end of file diff --git a/boards/tiny4.json b/boards/tiny4.json new file mode 100644 index 0000000..ad8ecd3 --- /dev/null +++ b/boards/tiny4.json @@ -0,0 +1,17 @@ +{"type": "Simple2DProblem", +"board": [ + [0, 0, 0, 0, 0], + [0, 1, 0, 1, 0], + [0, 1, 0, 1, 0], + [0, 1, 1, 1, 1], + [0, 0, 0, 0, 0] +], +"costs": [ + [3, 2, 4, 4, 1], + [2, 3, 4, 2, 2], + [3, 3, 3, 1, 4], + [2, 4, 2, 2, 1], + [4, 3, 4, 1, 2] +], +"start_state": [0, 0], +"end_state": [4, 4]} \ No newline at end of file diff --git a/pig_lite/.gitignore b/pig_lite/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/pig_lite/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/pig_lite/.idea/.gitignore b/pig_lite/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/pig_lite/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/pig_lite/.idea/inspectionProfiles/Project_Default.xml b/pig_lite/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..879f166 --- /dev/null +++ b/pig_lite/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/pig_lite/.idea/inspectionProfiles/profiles_settings.xml b/pig_lite/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/pig_lite/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/pig_lite/.idea/misc.xml b/pig_lite/.idea/misc.xml new file mode 100644 index 0000000..9de2865 --- /dev/null +++ b/pig_lite/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/pig_lite/.idea/modules.xml b/pig_lite/.idea/modules.xml new file mode 100644 index 0000000..e45f676 --- /dev/null +++ b/pig_lite/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/pig_lite/.idea/pig_lite.iml b/pig_lite/.idea/pig_lite.iml new file mode 100644 index 0000000..8b8c395 --- /dev/null +++ b/pig_lite/.idea/pig_lite.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pig_lite/.idea/vcs.xml b/pig_lite/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/pig_lite/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pig_lite/README.md b/pig_lite/README.md new file mode 100644 index 0000000..ed80324 --- /dev/null +++ b/pig_lite/README.md @@ -0,0 +1,3 @@ +# pig_lite + +This is PIG (=Problem Instance Generator) Lite, a simplified and cleaned up version of the framework previously used for the AI assignments. \ No newline at end of file diff --git a/pig_lite/bayesian_net/__init__.py b/pig_lite/bayesian_net/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pig_lite/bayesian_net/bayesian_net.py b/pig_lite/bayesian_net/bayesian_net.py new file mode 100644 index 0000000..1a8b584 --- /dev/null +++ b/pig_lite/bayesian_net/bayesian_net.py @@ -0,0 +1,154 @@ +import matplotlib +import numpy as np +import networkx as nx +import matplotlib.pyplot as plt + +#matplotlib.use('TkAgg') + + +class BayesianNode: + """ Building stone for BayesianNet class. Represents conditional probability distribution + for a boolean random variable, P(X | parents). """ + def __init__(self, X: str, parents: str, cpt: dict = None): + """ + X: String describing variable name + + parents: String containing parent variable names, separated with a whitespace + + cpt: dict that contains the distribution P(X=true | parent1=v1, parent2=v2...). + Dict should be structured as follows: {(v1, v2, ...): p, ...}, and each key must have + as many values as there are parents. Values (v1, v2, ...) must be True/False. + """ + if not isinstance(X, str) or not isinstance(parents, str): + raise ValueError("Use valid arguments - X and parents have to be strings (but at least one is not)!") + self.rand_var = X + self.parents = parents.split() + self.children = [] + + # in case of 0 or 1 parent, fix tuples first + if cpt and isinstance(cpt, (float, int)): + cpt = {(): cpt} + elif cpt and isinstance(cpt, dict): + if isinstance(list(cpt.keys())[0], bool): + # only one parent + cpt = {(k, ): v for k, v in cpt.items()} + elif cpt: + raise ValueError("Define cpt with a valid data type (dict, or int).") + # check format of cpt dict + if cpt: + for val, p in cpt.items(): + assert isinstance(val, tuple) and len(val) == len(self.parents) + assert all(isinstance(v, bool) for v in val) + assert 0 <= p <= 1 + + self.cpt = cpt + + def __repr__(self): + """ String representation of Bayesian Node. """ + return repr((self.rand_var, ' '.join(["parent(s):"] + self.parents))) + + def cond_probability(self, value: bool, event: dict): + """ + Returns conditional probability P(X=value | event) for an atomic event, + i.e. where each parent needs to be assigned a value. + value: bool (value of this random variable) + event: dict, assigning a value to each parent variable + """ + assert isinstance(value, bool) + if self.cpt: + prob_true = self.cpt[self.get_event_values(event)] + return prob_true if value else 1 - prob_true + + return None + + def get_event_values(self, event: dict): + """ Given an event (dict), returns tuple of values for all parents. """ + return tuple(event[p] for p in self.parents) + + +class BayesianNet: + """ Bayesian Network class for boolean random variables. Consists of BayesianNode-s. """ + def __init__(self, node_specs: list): + """ + Creates BayesianNet with given node_specs. Nodes should be in causal order (parents before children). + node_specs should be list of parameters for BayesianNode class. + """ + self.nodes = [] + self.rand_vars = [] + for spec in node_specs: + self.add_node(spec) + + def add_node(self, node_spec): + """ Creates a BayesianNode and adds it to the net, if the variable does *not*, and the parents do exist. """ + node = BayesianNode(*node_spec) + if node.rand_var in self.rand_vars: + raise ValueError("Variable {} already exists in network, cannot be defined twice!".format(node.rand_var)) + if not all((parent in self.rand_vars) for parent in node.parents): + raise ValueError("Parents do not all exist yet! Make sure to first add all parent nodes.") + self.nodes.append(node) + self.rand_vars.append(node.rand_var) + for parent in node.parents: + self.get_node_for_name(parent).children.append(node) + + def get_node_for_name(self, node_name): + """ Given the name of a random variable, returns the according BayesianNode of this network. """ + for n in self.nodes: + if n.rand_var == node_name: + return n + + raise ValueError("The variable {} does not exist in this network!".format(node_name)) + + def __repr__(self): + """ String representation of this Bayesian Network. """ + return "BayesianNet:\n{0!r}".format(self.nodes) + + def _get_depth(self, rand_var): + """ Given random variable, returns "depth" of node in graph for plotting. """ + node = self.get_node_for_name(rand_var) + if len(node.parents) == 0: + return 0 + + return max([self._get_depth(p) for p in node.parents]) + 1 + + def draw(self, title, save_path=None): + """ Draws the BN with networkx. Requires title for plot. """ + plt.figure(figsize=(14, 8)) + nx_bn = nx.DiGraph() + nx_bn.add_nodes_from(self.rand_vars) + pos = {rand_var: (10, 10) for rand_var in self.rand_vars} + for rand_var in self.rand_vars: + node = self.get_node_for_name(rand_var) + for c in node.children: + nx_bn.add_edge(rand_var, c.rand_var) + pos.update({c.rand_var: (pos[c.rand_var][0], pos[c.rand_var][1] - 3)}) + + depths = {rand_var: self._get_depth(rand_var) for rand_var in self.rand_vars} + _, counts = np.unique(list(depths.values()), return_counts=True) + xs = [list(np.linspace(6, 14, c)) if c > 1 else [10] for c in counts] + pos = {rand_var: (xs[depths[rand_var]].pop(), 10 - depths[rand_var] * 3) for rand_var in self.rand_vars} + + nx.set_node_attributes(nx_bn, pos, 'pos') + nx.draw_networkx(nx_bn, arrows=True, pos=nx.get_node_attributes(nx_bn, "pos"), + node_shape="o", node_color="white", node_size=7000, edgecolors="gray") + plt.title(title) + plt.box(False) + plt.margins(0.3) + plt.tight_layout() + if save_path: + plt.savefig(save_path, dpi=400) + else: + plt.show() + + +if __name__ == '__main__': + T = True + F = False + bn = BayesianNet([ + ('Burglary', '', 0.001), + ('Earthquake', '', {(): 0.002}), + ('Alarm', 'Burglary Earthquake', + {(T, T): 0.95, (T, F): 0.94, (F, T): 0.29, (F, F): 0.001}), + ('JohnCalls', 'Alarm', {T: 0.90, F: 0.05}), + ('MaryCalls', 'Alarm', {T: 0.70, F: 0.01}) + ]) + bn.draw("") diff --git a/pig_lite/datastructures/__init__.py b/pig_lite/datastructures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pig_lite/datastructures/priority_queue.py b/pig_lite/datastructures/priority_queue.py new file mode 100644 index 0000000..b5db414 --- /dev/null +++ b/pig_lite/datastructures/priority_queue.py @@ -0,0 +1,61 @@ +import heapq +from functools import total_ordering + + +# this annotation saves us some implementation work +@total_ordering +class Item(object): + def __init__(self, insertion, priority, value): + self.insertion = insertion + self.priority = priority + self.value = value + + def __lt__(self, other): + # if the decision "self < other" can be done + # based on the priority, do that + if self.priority < other.priority: + return True + elif self.priority == other.priority: + # in case the priorities are equal, we + # fall back on the insertion order, + # which establishes a total ordering + return self.insertion < other.insertion + return False + + def __eq__(self, other): + return self.priority == other.priority and self.insertion == other.insertion + + def __repr__(self): + return '({}, {}, {})'.format(self.priority, self.insertion, self.value) + + +class PriorityQueue(object): + def __init__(self): + self.insertion = 0 + self.heap = [] + + def has_elements(self): + return len(self.heap) > 0 + + def put(self, priority, value): + heapq.heappush(self.heap, Item(self.insertion, priority, value)) + self.insertion += 1 + + def get(self, include_priority=False): + item = heapq.heappop(self.heap) + if include_priority: + return item.priority, item.value + else: + return item.value + + def __iter__(self): + return iter([item.value for item in self.heap]) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return ('PriorityQueue [' + ','.join((str(item.value) for item in self.heap)) + ']') + + def __len__(self): + return len(self.heap) diff --git a/pig_lite/datastructures/queue.py b/pig_lite/datastructures/queue.py new file mode 100644 index 0000000..8b7ea03 --- /dev/null +++ b/pig_lite/datastructures/queue.py @@ -0,0 +1,27 @@ +from collections import deque + + +class Queue(object): + def __init__(self): + self.d = deque() + + def put(self, v): + self.d.append(v) + + def get(self): + return self.d.popleft() + + def has_elements(self): + return len(self.d) > 0 + + def __iter__(self): + return iter(self.d) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return ('Queue [' + ','.join((str(item) for item in self.d)) + ']') + + def __len__(self): + return len(self.d) diff --git a/pig_lite/datastructures/stack.py b/pig_lite/datastructures/stack.py new file mode 100644 index 0000000..8dd970a --- /dev/null +++ b/pig_lite/datastructures/stack.py @@ -0,0 +1,21 @@ +from collections import deque + + +class Stack(object): + def __init__(self): + self.d = deque() + + def put(self, v): + self.d.append(v) + + def get(self): + return self.d.pop() + + def has_elements(self): + return len(self.d) > 0 + + def __iter__(self): + return iter(self.d) + + def __repr__(self): + return ('Stack [' + ','.join((str(item) for item in self.d)) + ']') diff --git a/pig_lite/decision_tree/dt_base.py b/pig_lite/decision_tree/dt_base.py new file mode 100644 index 0000000..c5ba853 --- /dev/null +++ b/pig_lite/decision_tree/dt_base.py @@ -0,0 +1,61 @@ +from pig_lite.decision_tree.dt_node import DecisionTreeNodeBase +import scipy.stats as stats + +def entropy(y: list): + """ + Compute the entropy of a binary label distribution. + + This function calculates the entropy of a binary classification label list `y` as a wrapper + around `scipy.stats.entropy`. It assumes the labels are binary (0 or 1) and computes the + proportion of positive labels (1s) to calculate the entropy. + + Parameters + ---------- + y : list + A list of binary labels (0 or 1). + + Returns + ------- + float + The entropy of the label distribution. If the list is empty, returns 0.0. + + Notes + ----- + - Entropy is calculated using the formula: + H = -p*log2(p) - (1-p)*log2(1-p) + where `p` is the proportion of positive labels (1s). + - If `y` is empty, entropy is defined as 0.0. + + Examples + -------- + >>> entropy([0, 0, 1, 1]) + 1.0 + + >>> entropy([1, 1, 1, 1]) + 0.0 + + >>> entropy([]) + 0.0 + """ + if len(y) == 0: return 0.0 + positive = sum(y) / len(y) + return stats.entropy([positive, 1 - positive], base=2) + +# these two dummy classes are only used so we can import them and load trees from a pickle file before they are implemented by the students +class DecisionTree(): + def __init__(self) -> None: + pass + + def get_height(self, node): + if node is None: + return 0 + return max(self.get_height(node.left_child), self.get_height(node.right_child)) + 1 + + def print(self): + if self.root is not None: + height = self.get_height(self.root) + self.root.print_tree(height) + +class DecisionTreeNode(DecisionTreeNodeBase): + def __init__(self) -> None: + pass diff --git a/pig_lite/decision_tree/dt_node.py b/pig_lite/decision_tree/dt_node.py new file mode 100644 index 0000000..83e976f --- /dev/null +++ b/pig_lite/decision_tree/dt_node.py @@ -0,0 +1,70 @@ +from pig_lite.datastructures.queue import Queue + +class DecisionTreeNodeBase(): + def __init__(self): + self.label = None + self.split_point = None + self.split_feature = None + self.left_child = None + self.right_child = None + + def print_node(self, height, level=1): + node_width = 10 + n_spaces = 2 ** (height - level - 1) * node_width - node_width // 2 + if n_spaces > 0: + text = " " * n_spaces + else: + text = "" + + if self.label is None and self.split_feature is None: + return f"{text} {text}" + + if self.label is not None: + text = f"{text}( {self.label} ){text}" + elif self.split_feature is not None: + text_snippet = f"(x{self.split_feature}:{self.split_point:.2f})" + if len(text_snippet) != node_width: + text_snippet = f" {text_snippet}" + text = f"{text}{text_snippet}{text}" + return text + + def __str__(self): + if self.label is not None: return f"({self.label})" + + str_value = f"{self.split_feature}:{self.split_point:.2f}|{self.left_child}{self.right_child}" + return str_value + + def print_tree(self, height): + visited = set() + frontier = Queue() + + lines = [''] + + previous_level = 1 + frontier.put((self, 1)) + + while frontier.has_elements(): + current, level = frontier.get() + if level > previous_level: + lines.append('') + previous_level = level + lines[-1] += current.print_node(height, level) + if current not in visited: + visited.add(current) + if current.left_child is not None: + frontier.put((current.left_child, level + 1)) + else: + if level < height: frontier.put((DecisionTreeNodeBase(), level + 1)) + if current.right_child is not None: + frontier.put((current.right_child, level + 1)) + else: + if level < height: frontier.put((DecisionTreeNodeBase(), level + 1)) + + for line in lines: + print(line) + return None + + def split(): + raise NotImplementedError() + + \ No newline at end of file diff --git a/pig_lite/decision_tree/training_set.py b/pig_lite/decision_tree/training_set.py new file mode 100644 index 0000000..f27e78c --- /dev/null +++ b/pig_lite/decision_tree/training_set.py @@ -0,0 +1,84 @@ +import json +import numpy as np +import matplotlib.pyplot as plt + +import matplotlib.pyplot as plt +import warnings + +class TrainingSet(): + def __init__(self, X, y): + self.X = X + self.y = y + + def to_json(self): + return json.dumps(dict( + type=self.__class__.__name__, + X=self.X.tolist(), + y=self.y.tolist() + )) + + @staticmethod + def from_json(jsonstring): + data = json.loads(jsonstring) + return TrainingSet.from_dict(data) + + @staticmethod + def from_dict(data): + return TrainingSet( + np.array(data['X']).squeeze(), + np.array(data['y']) + ) + + def plot_node_boundaries(self, node, limit_left, limit_right, limit_top, limit_bottom, max_depth, level=1): + + split_point = node.split_point + limit_left_updated = limit_left + limit_right_updated = limit_right + limit_top_updated = limit_top + limit_bottom_updated = limit_bottom + + if node.split_feature == 0: + if limit_bottom == limit_top: + warnings.warn('limit_bottom equals limit_top; extending by 0.1') + plt.plot([split_point, split_point], [limit_bottom - 0.1, limit_top + 0.1], color="purple", alpha=1 / level) + else: + plt.plot([split_point, split_point], [limit_bottom, limit_top], color="purple", alpha=1 / level) + limit_left_updated = split_point + limit_right_updated = split_point + + else: + if limit_left == limit_right: + warnings.warn('limit_left equals limit_right; extending by 0.1') + plt.plot([limit_left - 0.1, limit_right + 0.1], [split_point, split_point], color="purple", alpha=1 / level) + else: + plt.plot([limit_left, limit_right], [split_point, split_point], color="purple", alpha=1 / level) + limit_top_updated = split_point + limit_bottom_updated = split_point + + if level == max_depth: + return + if node.left_child is not None: self.plot_node_boundaries(node.left_child, limit_left, limit_right_updated, + limit_top_updated, limit_bottom, max_depth, level + 1) + if node.right_child is not None: self.plot_node_boundaries(node.right_child, limit_left_updated, limit_right, limit_top, + limit_bottom_updated, max_depth, level + 1) + + def visualize(self, tree=None, max_height=None): + symbols = [["x", "o"][index] for index in self.y] + for y in set(self.y): + X = self.X[self.y == y, :] + plt.scatter(X[:, 0], X[:, 1], + color=["red", "blue"][y], + marker=symbols[y], + label="class: {}".format(y)) + + if tree is not None: + tree_height = tree.get_height(tree.root) + if max_height is None or max_height > tree_height: + max_height = tree_height + self.plot_node_boundaries(tree.root, + limit_left=min(self.X[:, 0]), + limit_right=max(self.X[:, 0]), + limit_top=max(self.X[:, 1]), + limit_bottom=min(self.X[:, 1]), + max_depth=max_height) # TODO: make parameterizable + diff --git a/pig_lite/environment/__init__.py b/pig_lite/environment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pig_lite/environment/base.py b/pig_lite/environment/base.py new file mode 100644 index 0000000..7acfab1 --- /dev/null +++ b/pig_lite/environment/base.py @@ -0,0 +1,60 @@ +import json +import hashlib +import numpy as np + + +class Environment: + def step(self, action): + raise NotImplementedError() + + def reset(self): + raise NotImplementedError() + + def get_n_actions(self): + raise NotImplementedError() + + def get_n_states(self): + raise NotImplementedError() + + def get_flat_policy(self, policy): + flat_policy = [] + for state in range(self.get_n_states()): + for action in range(self.get_n_actions()): + flat_policy.append((state, action, policy[state, action])) + return flat_policy + + def get_policy_hash(self, outcome): + flat_policy = self.get_flat_policy(outcome.policy) + flat_policy_as_str = ','.join(map(str, flat_policy)) + flat_policy_hash = hashlib.sha256(flat_policy_as_str.encode('UTF-8')).hexdigest() + return flat_policy_hash + + +class Outcome: + def __init__(self, n_episodes, policy, V, Q): + self.n_episodes = n_episodes + self.policy = policy + self.V = V + self.Q = Q + + def get_n_episodes(self): + return self.n_episodes + + def to_json(self): + return json.dumps(dict( + type=self.__class__.__name__, + n_episodes=self.n_episodes, + policy=self.policy.tolist(), + V=self.V.tolist(), + Q=self.Q.tolist(), + )) + + @staticmethod + def from_json(jsonstring): + data = json.loads(jsonstring) + return Outcome( + data['n_episodes'], + np.array(data['policy']), + np.array(data['V']), + np.array(data['Q']) + ) diff --git a/pig_lite/environment/gridworld.py b/pig_lite/environment/gridworld.py new file mode 100644 index 0000000..7d8f13b --- /dev/null +++ b/pig_lite/environment/gridworld.py @@ -0,0 +1,360 @@ +import json +import numpy as np + +from pig_lite.environment.base import Environment + +DELTAS = [ + (-1, 0), + (+1, 0), + (0, -1), + (0, +1) +] +NAMES = [ + 'left', + 'right', + 'up', + 'down' +] + +def sample(rng, elements): + """ Samples an element of `elements` randomly. """ + csp = np.cumsum([elm[0] for elm in elements]) + idx = np.argmax(csp > rng.uniform(0, 1)) + return elements[idx] + + +class Gridworld(Environment): + def __init__(self, seed, dones, rewards, starts): + self.seed = seed + self.rng = np.random.RandomState(seed) + self.dones = dones + self.rewards = rewards + self.starts = starts + + self.__compute_P() + + def reset(self): + """ Resets the environment of this gridworld to a randomly sampled start state. """ + _, self.state = sample(self.rng, self.starts) + return self.state + + def step(self, action): + """ Performs the action on the gridworld, where next state of environment is sampled based on self.P. """ + _, self.state, reward, done = sample(self.rng, self.P[self.state][action]) + return self.state, reward, done + + def get_n_actions(self): + """ Returns the number of actions available in this gridworld. """ + return 4 + + def get_n_states(self): + """ Returns the number of states available in this gridworld. """ + return np.prod(self.dones.shape) + + def get_gamma(self): + """ Returns discount factor gamma for this gridworld. """ + return 0.99 + + def __compute_P(self): + """ Computes and stores the transitions for this gridworld. """ + w, h = self.dones.shape + + def inbounds(i, j): + """ Checks whether coordinates i and j are within the grid. """ + return i >= 0 and j >= 0 and i < w and j < h + + self.P = dict() + for i in range(0, w): + for j in range(0, h): + state = j * w + i + self.P[state] = dict() + + if self.dones[i, j]: + for action in range(self.get_n_actions()): + # make it absorbing + self.P[state][action] = [(1, state, 0, True)] + else: + for action, (dx, dy) in enumerate(DELTAS): + ortho_dir_probs = [ + (0.8, dx, dy), + (0.1, dy, dx), + (0.1, -dy, -dx) + ] + transitions = [] + for p, di, dj in ortho_dir_probs: + ni = i + di + nj = j + dj + if inbounds(ni, nj): + # we move + sprime = nj * w + ni + done = self.dones[ni, nj] + reward = self.rewards[ni, nj] + transitions.append((p, sprime, reward, done)) + else: + # stay in the same state, b/c we bounced + sprime = state + done = self.dones[i, j] + reward = self.rewards[i, j] + transitions.append((p, sprime, reward, done)) + + self.P[state][action] = transitions + + def to_json(self): + """ Converts and stores this gridworld to a JSON file. """ + return json.dumps(dict( + type=self.__class__.__name__, + seed=self.seed, + dones=self.dones.tolist(), + rewards=self.rewards.tolist(), + starts=self.starts.tolist() + )) + + @staticmethod + def from_json(jsonstring): + """ Loads given JSON file, and creates gridworld with information. """ + data = json.loads(jsonstring) + return Gridworld( + data['seed'], + np.array(data['dones']), + np.array(data['rewards']), + np.array(data['starts'], dtype=np.int64), + ) + + @staticmethod + def from_dict(data): + """ Creates gridworld with information in given data-dictionary. """ + return Gridworld( + data['seed'], + np.array(data['dones']), + np.array(data['rewards']), + np.array(data['starts'], dtype=np.int64), + ) + + @staticmethod + def get_random_instance(rng, size): + """ Given random generator and problem size, generates Gridworld instance. """ + dones, rewards, starts = Gridworld.__generate(rng, size) + return Gridworld(rng.randint(0, 2 ** 31), dones, rewards, starts) + + @staticmethod + def __generate(rng, size): + """ Helper function that retrieves dones, rewards, starts for Gridworld instance generation. """ + dones = np.full((size, size), False, dtype=bool) + rewards = np.zeros((size, size), dtype=np.int8) - 1 + + coordinates = [] + for i in range(1, size - 1): + for j in range(1, size - 1): + coordinates.append((i, j)) + indices = np.arange(len(coordinates)) + + chosen = rng.choice(indices, max(1, len(indices) // 10), replace=False) + + for c in chosen: + x, y = coordinates[c] + dones[x, y] = True + rewards[x, y] = -100 + + starts = np.array([[1, 0]]) + dones[-1, -1] = True + rewards[-1, -1] = 100 + + return dones, rewards, starts + + @staticmethod + def get_minimum_problem_size(): + return 3 + + def visualize(self, outcome, coords=None, grid=None): + """ Visualisation function for gridworld; plots environment, policy, Q. """ + policy = None + Q = None + V = None + if outcome is not None: + if outcome.policy is not None: + policy = outcome.policy + + if outcome.V is not None: + V = outcome.V + + if outcome.Q is not None: + Q = outcome.Q + + self._plot_environment_and_policy(policy, V, Q, show_coordinates=coords, show_grid=grid) + + def _plot_environment_and_policy(self, policy=None,V=None, Q=None, show_coordinates=False, + show_grid=False, plot_filename=None, debug_info=False): + """ Function that plots environment and policy. """ + import matplotlib.pyplot as plt + fig, axes = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True) + dones_ax = axes[0, 0] + rewards_ax = axes[0, 1] + V_ax = axes[1, 0] + Q_ax = axes[1, 1] + + dones_ax.set_title('Terminal States and Policy') + dones_ax.imshow(self.dones.T, cmap='gray_r', vmin=0, vmax=4) + + rewards_ax.set_title('Immediate Rewards') + rewards_ax.imshow(self.rewards.T, cmap='RdBu_r', vmin=-25, vmax=25) + + if len(policy) > 0: + self._plot_policy(dones_ax, policy) + + w, h = self.dones.shape + V_array = V.reshape(self.dones.shape).T + V_ax.set_title('State Value Function $V(s)$') + r = max(1e-13, np.max(np.abs(V_array))) + V_ax.imshow(V_array.T, cmap='RdBu_r', vmin=-r, vmax=r) + + if debug_info: + for s in range(len(V)): + sy, sx = divmod(s, w) + V_ax.text(sx, sy, f'{sx},{sy}:{s}', + color='w', fontdict=dict(size=6), + horizontalalignment='center', verticalalignment='center') + + Q_ax.set_title('State Action Value Function $Q(s, a)$') + poly_patches_q_values = self._draw_Q(Q_ax, Q, debug_info) + + def format_coord(x, y): + for poly_patch, q_value in poly_patches_q_values: + if poly_patch.contains_point(Q_ax.transData.transform((x, y))): + return f'x:{x:4.2f} y:{y:4.2f} {q_value}' + return f'x:{x:4.2f} y:{y:4.2f}' + + Q_ax.format_coord = format_coord + + for ax in [dones_ax, rewards_ax, V_ax, Q_ax]: + ax.tick_params( + top=show_coordinates, + left=show_coordinates, + labelleft=show_coordinates, + labeltop=show_coordinates, + right=False, + bottom=False, + labelbottom=False + ) + + # Major ticks + s = self.dones.shape[0] + ax.set_xticks(np.arange(0, s, 1)) + ax.set_yticks(np.arange(0, s, 1)) + + # Minor ticks + ax.set_xticks(np.arange(-.5, s, 1), minor=True) + ax.set_yticks(np.arange(-.5, s, 1), minor=True) + + if show_grid: + for color, ax in zip(['m', 'w', 'w'], [dones_ax, rewards_ax, V_ax]): + # Gridlines based on minor ticks + ax.grid(which='minor', color=color, linestyle='-', linewidth=1) + + plt.tight_layout() + if plot_filename is not None: + plt.savefig(plot_filename) + plt.close(fig) + else: + plt.show() + + def _plot_policy(self, ax, policy): + """ Function that plots policy. """ + w, h = self.dones.shape + xs = np.arange(w) + ys = np.arange(h) + xx, yy = np.meshgrid(xs, ys) + + # we need a quiver for each of the four action + quivers = list() + for a in range(self.get_n_actions()): + quivers.append(list()) + + # we parse the textual description of the lake + for s in range(self.get_n_states()): + y, x = divmod(s, w) + if self.dones[x, y]: + for a in range(self.get_n_actions()): + quivers[a].append((0., 0.)) + else: + for a in range(self.get_n_actions()): + wdx, wdy = DELTAS[a] + corrected = np.array([wdx, -wdy]) + quivers[a].append(corrected * policy[s, a]) + + # plot each quiver + for quiver in quivers: + q = np.array(quiver) + ax.quiver(xx, yy, q[:, 0], q[:, 1], units='xy', scale=1.5) + + def _draw_Q(self, ax, Q, debug_info): + """ Function that draws Q. """ + pattern = np.zeros(self.dones.shape) + ax.imshow(pattern, cmap='gray_r') + import matplotlib.pyplot as plt + from matplotlib.cm import ScalarMappable + from matplotlib.colors import Normalize + from matplotlib.patches import Rectangle, Polygon + w, h = self.dones.shape + + r = max(1e-13, np.max(np.abs(Q))) + norm = Normalize(vmin=-r, vmax=r) + cmap = plt.get_cmap('RdBu_r') + sm = ScalarMappable(norm, cmap) + + hover_polygons = [] + for state in range(len(Q)): + qs = Q[state] + # print('qs', qs) + y, x = divmod(state, w) + if self.dones[x, y]: + continue + y += 0.5 + x += 0.5 + + dx = 1 + dy = 1 + + ulx = (x - 1) * dx + uly = (y - 1) * dy + + rect = Rectangle( + xy=(ulx, uly), + width=dx, + height=dy, + edgecolor='k', + facecolor='none' + ) + ax.add_artist(rect) + + mx = (x - 1) * dx + dx / 2. + my = (y - 1) * dy + dy / 2. + + ul = ulx, uly + ur = ulx + dx, uly + ll = ulx, uly + dy + lr = ulx + dx, uly + dy + m = mx, my + + up = [ul, m, ur] + left = [ul, m, ll] + right = [ur, m, lr] + down = [ll, m, lr] + action_polys = [left, right, up, down] + for a, poly in enumerate(action_polys): + poly_patch = Polygon( + poly, + edgecolor='k', + linewidth=0.1, + facecolor=sm.to_rgba(qs[a]) + ) + if debug_info: + mmx = np.mean([x for x, y in poly]) + mmy = np.mean([y for x, y in poly]) + sss = '\n'.join(map(str, self.P[state][a])) + ax.text(mmx, mmy, f'{NAMES[a][0]}:{sss}', + fontdict=dict(size=5), horizontalalignment='center', + verticalalignment='center') + + hover_polygons.append((poly_patch, f'{NAMES[a]}:{qs[a]:4.2f}')) + ax.add_artist(poly_patch) + return hover_polygons diff --git a/pig_lite/game/__init__.py b/pig_lite/game/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pig_lite/game/base.py b/pig_lite/game/base.py new file mode 100644 index 0000000..a4e0a80 --- /dev/null +++ b/pig_lite/game/base.py @@ -0,0 +1,87 @@ +import hashlib + +class Node(object): + def __init__(self, parent, state, action, player, depth): + self.parent = parent + self.state = state + self.action = action + self.player = player + self.depth = depth + + def key(self): + # if state is composed of other stuff (dict, set, ...) + # make it a tuple containing hashable datatypes + # (this is supposed to be overridden by subclasses) + return tuple(self.state) + (self.player, ) + + def __hash__(self): + return hash(self.key()) + + def __eq__(self, other): + if type(self) == type(other): + return self.key() == other.key() + raise ValueError('cannot simply compare two different node types') + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return 'Node(id:{}, parent:{}, state:{}, action:{}, player:{}, depth:{})'.format( + id(self), + id(self.parent), + self.state, + self.action, + self.player, + self.depth + ) + + def get_move_sequence(self): + current = self + reverse_sequence = [] + while current.parent is not None: + reverse_sequence.append((current.player, current.action)) + current = current.parent + return list(reversed(reverse_sequence)) + + def get_move_sequence_hash(self): + move_sequence = self.get_move_sequence() + move_sequence_as_str = ';'.join(map(str, move_sequence)) + move_sequence_hash = hashlib.sha256(move_sequence_as_str.encode('UTF-8')).hexdigest() + return move_sequence_hash + +class Game(object): + def get_number_of_expanded_nodes(self): + raise NotImplementedError() + + def get_start_node(self): + raise NotImplementedError() + + def winner(self, node): + raise NotImplementedError() + + def successors(self, node): + raise NotImplementedError() + + def get_max_player(self): + raise NotImplementedError() + + def to_json(self): + raise NotImplementedError() + + def get_move_sequence(self, end: Node): + if end is None: + return list() + return end.get_move_sequence() + + def get_move_sequence_hash(self, end: Node): + if end is None: + return '' + return end.get_move_sequence_hash() + + @staticmethod + def from_json(jsonstring): + raise NotImplementedError() + + @staticmethod + def get_minimum_problem_size(): + raise NotImplementedError() diff --git a/pig_lite/game/tictactoe.py b/pig_lite/game/tictactoe.py new file mode 100644 index 0000000..1e49d34 --- /dev/null +++ b/pig_lite/game/tictactoe.py @@ -0,0 +1,371 @@ +import json +import numpy as np + +from copy import deepcopy +from pig_lite.game.base import Node, Game + + +class TTTNode(Node): + def key(self): + return tuple(self.state.flatten().tolist() + [self.player]) + + def __repr__(self): + return '"TTTNode(\nid:{}\nparent:{}\nboard:\n{}\nplayer:\n{}\naction:\n{}\ndepth:{})"'.format( + id(self), + id(self.parent), + # this needs to be printed transposed, so it fits together with + # how matplotlib's 'imshow' renders images + self.state.T, + self.player, + self.action, + self.depth + ) + + def pretty_print(self): + import matplotlib.pyplot as plt + from matplotlib.colors import ListedColormap + cm = ListedColormap(['tab:blue', 'lightgray', 'tab:orange']) + print('State of the board:') + plt.figure(figsize=(2, 2)) + plt.imshow(self.state.T, cmap=cm) + plt.axis('off') + plt.show() + print('Performed moves: {}'.format(self.depth)) + + +class TicTacToe(Game): + def __init__(self, rng=None, depth=None): + self.n_expands = 0 + self.play_randomly(rng, depth) + + def play_randomly(self, rng, depth): + """ Initialises self.start_node to be either empty board, or board at given depth after random playing. """ + empty_board = np.zeros((3, 3), dtype=int) + start_from_empty = TTTNode(None, empty_board, None, 1, 0) + if rng is None or depth is None or depth == 0: + self.start_node = start_from_empty + else: + # proceed playing randomly until either 'depth' is reached, + # or the node is a terminal node + nodes = [] + successors = [start_from_empty] + while True: + index = rng.randint(0, len(successors)) + current = successors[index] + + if current.depth == depth: + break + + nodes.append(current) + terminal, winner = self.outcome(current) + if terminal: + break + successors = self.successors(current) + + for node in successors: + nodes.append(node) + + self.start_node = TTTNode(None, current.state, None, current.player, 0) + + def get_start_node(self): + """ Returns start node of this Game. """ + return self.start_node + + def outcome(self, node): + """ Returns tuple stating whether game is finished or not, and winner (or None otherwise). """ + board = node.state + for player in [-1, 1]: + # checks rows and columns + for i in range(3): + if (board[i, :] == player).all() or (board[:, i] == player).all(): + return True, player + + # checks diagonals + if (np.diag(board) == player).all() or (np.diag(np.rot90(board)) == player).all(): + return True, player + + # if board is full, and none of the conditions above are true, + # nobody has won --- it's a draw + if (board != 0).all(): + return True, None + + # else, continue + return False, None + + def get_max_player(self): + """ Returns identifier of MAX player used in this game. """ + return 1 + + def successor(self, node, action): + """ Performs given action at given game node, and returns successor TTT node. """ + board = node.state + player = node.player + + next_board = board.copy() + next_board[action] = player + + if player == 1: + next_player = -1 + else: + next_player = 1 + + return TTTNode( + node, + next_board, + action, + next_player, + node.depth + 1 + ) + + def get_number_of_expanded_nodes(self): + return self.n_expands + + def successors(self, node): + """ Given a game node, returns all possible successor nodes based on all actions that can be performed. """ + self.n_expands += 1 + terminal, winner = self.outcome(node) + + if terminal: + return [] + else: + successor_nodes = [] + # iterate through all possible coordinates (==actions) + for action in zip(*np.nonzero(node.state == 0)): + successor_nodes.append(self.successor(node, action)) + return successor_nodes + + def to_json(self): + """ Converts and stores this TTT game to a JSON file. """ + return json.dumps(dict( + type=self.__class__.__name__, + start_state=self.start_node.state.tolist(), + start_player=self.start_node.player + )) + + @staticmethod + def from_json(jsonstring): + """ Loads given JSON file, and creates game with information. """ + data = json.loads(jsonstring) + + ttt = TicTacToe() + ttt.start_node = TTTNode( + None, + np.array(data['start_state'], dtype=int), + None, + data['start_player'], + 0 + ) + return ttt + + @staticmethod + def from_dict(data): + """ Creates game with information in given data-dictionary. """ + ttt = TicTacToe() + ttt.start_node = TTTNode( + None, + np.array(data['start_state'], dtype=int), + None, + data['start_player'], + 0 + ) + return ttt + + @staticmethod + def get_minimum_problem_size(): + return 0 + + def visualize(self, move_sequence, show_possible=False, tree_name=''): + game = deepcopy(self) + nodes = [] + current = game.get_start_node() + nodes.append(current) + for player, move in move_sequence: + if show_possible: + successors = game.successors(current) + nodes.extend(successors) + current = None + for succ in successors: + if succ.action == move: + current = succ + break + else: + current = game.successor(current, move) + nodes.append(current) + + try: + self.networkx_plot_game_tree(tree_name, nodes) + except ImportError: + print('#' * 30) + print('#' * 30) + print('starting position') + print(self.get_start_node()) + print('#' * 30) + print('#' * 30) + print('-' * 30) + print('sequence of nodes') + for node in nodes: + print('-' * 30) + print(node) + terminal, winner = game.outcome(node) + print('terminal {}, winner {}'.format(terminal, winner)) + + def networkx_plot_game_tree(self, title, nodes, highlight=None): + # TODO: this needs some serious refactoring + # use visitors for styling, for example, instead of cumbersome dicts + import networkx as nx + import matplotlib.pyplot as plt + from networkx.drawing.nx_pydot import graphviz_layout + from matplotlib.offsetbox import OffsetImage, AnnotationBbox, HPacker, VPacker, TextArea + + fig, tree_ax = plt.subplots() + tree_ax.set_title(title) + G = nx.DiGraph(ordering='out') + nodes_extra = dict() + edges_extra = dict() + + def sort_key(node): + if node.action is None: + return (-1, -1) + return node.action + + for node in sorted(nodes, key=sort_key): + G.add_node(id(node), search_node=node) + terminal, winner = self.outcome(node) + nodes_extra[id(node)] = dict( + board=node.state, + player=node.player, + depth=node.depth, + terminal=terminal, + winner=winner + ) + + for node in nodes: + if node.parent is not None: + edge = id(node.parent), id(node) + G.add_edge(*edge, parent_node=node.parent) + edges_extra[edge] = dict( + label='{}'.format(node.action), + parent_player=node.parent.player + ) + + node_size = 1000 + positions = graphviz_layout(G, prog='dot') + + from matplotlib.colors import Normalize, LinearSegmentedColormap + + blue_orange = LinearSegmentedColormap.from_list( + 'blue_orange', + ['tab:blue', 'lightgray', 'tab:orange'] + ) + + inf = float('Inf') + x_range = [inf, -inf] + y_range = [inf, -inf] + for id_node, pos in positions.items(): + x, y = pos + x_range = [min(x, x_range[0]), max(x, x_range[1])] + y_range = [min(y, y_range[0]), max(y, y_range[1])] + + player = nodes_extra[id_node]['player'] + text_player = 'p:{}'.format(player) + text_depth = 'd:{}'.format(nodes_extra[id_node]['depth']) + color_player = 'tab:blue' if player == -1 else 'tab:orange' + + frameon = False + bboxprops = None + if nodes_extra[id_node]['terminal']: + winner = nodes_extra[id_node]['winner'] + frameon = True + if winner is None: + edgecolor = 'tab:purple' + else: + edgecolor = 'tab:blue' if winner == -1 else 'tab:orange' + bboxprops = dict( + facecolor='none', + edgecolor=edgecolor + ) + color_player = 'k' + text_player = 'w:{}'.format(winner) + if winner is None: + text_player = '' + + # needs to be transposed b/c image coordinates etc ... + board = nodes_extra[id_node]['board'].T + textbox_player = TextArea(text_player, textprops=dict(size=6, color=color_player)) + textbox_depth = TextArea(text_depth, textprops=dict(size=6)) + + textbox_children = [textbox_player, textbox_depth] + + if highlight is not None: + if id_node in highlight: + if nodes_extra[id_node]['terminal']: + frameon = True + if nodes_extra[id_node]['winner'] is None: + edgecolor = 'tab:purple' + else: + edgecolor = 'tab:blue' if winner == -1 else 'tab:orange' + + bboxprops = dict( + facecolor='none', + edgecolor=edgecolor + ) + + if len(highlight[id_node]) > 0: + for key, value in highlight[id_node].items(): + textbox_children.append( + TextArea('{}:{}'.format(key, value), textprops=dict(size=6)) + ) + + imagebox = OffsetImage(board, zoom=5, cmap=blue_orange, norm=Normalize(vmin=-1, vmax=1)) + packed = HPacker( + align='center', + children=[ + imagebox, + VPacker( + align='center', + children=textbox_children, + sep=0.1, pad=0.1 + ) + ], + sep=0.1, pad=0.1 + ) + + ab = AnnotationBbox(packed, pos, xycoords='data', frameon=frameon, bboxprops=bboxprops) + tree_ax.add_artist(ab) + + def min_dist(a, b): + if a == b: + return [a - 1, b + 1] + else: + return [a - 0.9 * abs(a), b + 0.1 * abs(b)] + + x_range = min_dist(*x_range) + y_range = min_dist(*y_range) + tree_ax.set_xlim(x_range) + tree_ax.set_ylim(y_range) + + orange_edges = [] + blue_edges = [] + + for edge, extra in edges_extra.items(): + if extra['parent_player'] == -1: + blue_edges.append(edge) + else: + orange_edges.append(edge) + + for color, edgelist in [('tab:orange', orange_edges), ('tab:blue', blue_edges)]: + nx.draw_networkx_edges( + G, positions, + edgelist=edgelist, + edge_color=color, + arrowstyle='-|>', + arrowsize=10, + node_size=node_size, + ax=tree_ax + ) + edge_labels = {edge_id: edge['label'] for edge_id, edge in edges_extra.items()} + nx.draw_networkx_edge_labels(G, positions, edge_labels, ax=tree_ax, font_size=6) + + tree_ax.axis('off') + plt.tight_layout() + plt.show() \ No newline at end of file diff --git a/pig_lite/instance_generation/__init__.py b/pig_lite/instance_generation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pig_lite/instance_generation/enc.py b/pig_lite/instance_generation/enc.py new file mode 100644 index 0000000..5b9c7f0 --- /dev/null +++ b/pig_lite/instance_generation/enc.py @@ -0,0 +1,5 @@ +# this is the common encoding for different level tiles +WALL = 1 +SPACE = 0 +EXPOSED = -1 +UNDETERMINED = -2 diff --git a/pig_lite/instance_generation/problem_factory.py b/pig_lite/instance_generation/problem_factory.py new file mode 100644 index 0000000..6bed8e2 --- /dev/null +++ b/pig_lite/instance_generation/problem_factory.py @@ -0,0 +1,96 @@ +import json + +from pig_lite.problem.simple_2d import Simple2DProblem, MazeLevel, TerrainLevel, RoomLevel +from pig_lite.environment.gridworld import Gridworld +from pig_lite.game.tictactoe import TicTacToe +from pig_lite.decision_tree.training_set import TrainingSet + +# this is the common encoding for different level tiles +encoding = { + 'WALL': 1, + 'SPACE': 0, + 'EXPOSED': -1, + 'UNDETERMINED': -2 +} + +class ProblemFactory(): + def __init__(self) -> None: + pass + + @staticmethod + def generate_problem(problem_type, problem_size, rng): + if problem_type == 'maze': + level = MazeLevel(rng, size=problem_size) + return Simple2DProblem(level.get_field(), + level.get_costs(), + level.get_start(), + level.get_end()) + elif problem_type == 'terrain': + level = TerrainLevel(rng, size=problem_size) + return Simple2DProblem(level.get_field(), + level.get_costs(), + level.get_start(), + level.get_end()) + elif problem_type == 'rooms': + level = RoomLevel(rng, size=problem_size) + return Simple2DProblem(level.get_field(), + level.get_costs(), + level.get_start(), + level.get_end()) + elif problem_type == 'tictactoe': + return TicTacToe(rng, depth=problem_size) + elif problem_type == 'gridworld': + return Gridworld.get_random_instance(rng, size=problem_size) + elif problem_type =='trainset': + raise NotImplementedError(f'problem_type {problem_type} is not implemented yet') + else: + raise ValueError(f'unknown problem_type {problem_type}') + + + @staticmethod + def create_problem_from_json(json_path): + with open(json_path, 'r') as file: + data = json.load(file) + problem_type = data['type'] + + if problem_type == 'Simple2DProblem': + problem = Simple2DProblem.from_dict(data) + return problem + elif problem_type == 'TicTacToe': + problem = TicTacToe.from_dict(data) + return problem + elif problem_type == 'Gridworld': + problem = Gridworld.from_dict(data) + return problem + elif problem_type == 'TrainingSet': + problem = TrainingSet.from_dict(data) + return problem + else: + raise ValueError(f"Unknown problem type: {problem_type}") + + + @staticmethod + def create_problem_from_dict(data, problem_type='Simple2DProblem'): + import numpy as np + if problem_type == 'Simple2DProblem': + if not ('board' in data.keys() and 'costs' in data.keys() + and 'start_state' in data.keys() and 'end_state' in data.keys()): + raise ValueError('data dict must contain: "board", "costs", "start_state" and "end_state"') + if np.array(data['board']).shape != np.array(data['costs']).shape: + raise ValueError('data["board"] and data["costs"] must have same shape') + problem = Simple2DProblem.from_dict(data) + return problem + if problem_type == 'TicTacToe': + if not ('start_state' in data.keys() and 'start_player' in data.keys()): + raise ValueError('data dict must contain: "start_state", "start_player"') + problem = TicTacToe.from_dict(data) + return problem + if problem_type == 'Gridworld': + if not ('seed' in data.keys() and 'dones' in data.keys() + and 'rewards' in data.keys() and 'starts' in data.keys()): + raise ValueError('data dict must contain: "seed", "dones", "rewards", "starts"') + problem = Gridworld.from_dict(data) + return problem + else: + raise NotImplementedError(f'problem_type {problem_type} is not implemented yet') + diff --git a/pig_lite/problem/base.py b/pig_lite/problem/base.py new file mode 100644 index 0000000..72bde76 --- /dev/null +++ b/pig_lite/problem/base.py @@ -0,0 +1,92 @@ +import hashlib + +class Node(object): + def __init__(self, parent, state, action, cost, depth): + self.parent = parent + self.state = state + self.action = action + self.cost = cost + self.depth = depth + + def key(self): + # if state is composed of other stuff (dict, set, ...) + # make it a tuple containing hashable datatypes + # (this is supposed to be overridden by subclasses) + return self.state + + def __hash__(self): + return hash(self.key()) + + def __eq__(self, other): + if type(self) == type(other): + return self.key() == other.key() + raise ValueError('cannot simply compare two different node types') + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return 'Node(id:{}, parent:{}, state:{}, action:{}, cost:{}, depth:{})'.format( + id(self), + id(self.parent), + self.state, + self.action, + self.cost, + self.depth + ) + + def get_action_sequence(self): + current = self + reverse_sequence = [] + while current.parent is not None: + reverse_sequence.append(current.action) + current = current.parent + return list(reversed(reverse_sequence)) + + def get_action_sequence_hash(self): + action_sequence = self.get_action_sequence() + action_sequence_as_str = ','.join(map(str, action_sequence)) + action_sequence_hash = hashlib.sha256(action_sequence_as_str.encode('UTF-8')).hexdigest() # should solution node return hashcode? + return action_sequence_hash + + def pretty_print(self): + print(f"state {self.state} was reached following the sequence {self.get_action_sequence()} (cost: {self.cost}, depth: {self.depth})") + + +class Problem(object): + def get_number_of_expanded_nodes(self): + raise NotImplementedError() + + def get_start_node(self): + raise NotImplementedError() + + def get_end_node(self): + raise NotImplementedError() + + def is_end(self, node): + raise NotImplementedError() + + def action_cost(self, state, action): + raise NotImplementedError() + + def successors(self, node): + raise NotImplementedError() + + def to_json(self): + raise NotImplementedError() + + def visualize(self, **kwargs): + raise NotImplementedError() + + def get_action_sequence(self, end: Node): + if end is None: + return list() + return end.get_action_sequence() + + @staticmethod + def from_json(jsonstring): + raise NotImplementedError() + + @staticmethod + def get_minimum_problem_size(): + raise NotImplementedError() diff --git a/pig_lite/problem/simple_2d.py b/pig_lite/problem/simple_2d.py new file mode 100644 index 0000000..101467b --- /dev/null +++ b/pig_lite/problem/simple_2d.py @@ -0,0 +1,529 @@ +from pig_lite.problem.base import Problem, Node +from pig_lite.instance_generation import enc +import json +import numpy as np +from collections import OrderedDict +import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable +from matplotlib.colors import TABLEAU_COLORS, XKCD_COLORS + +class BaseLevel(): + def __init__(self, rng, size) -> None: + self.rng = rng + self.size = size + self.field = None + self.costs = None + self.start = None + self.end = None + + self.initialize_level() + + def initialize_level(self): + raise NotImplementedError() + + def get_field(self): + return self.field + + def get_costs(self): + return self.costs + + def get_start(self): + return self.start + + def get_end(self): + return self.end + + +class MazeLevel(BaseLevel): + # this method generates a random maze according to prim's randomized + # algorithm + # http://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim.27s_algorithm + + def __init__(self, rng, size): + super().__init__(rng, size) + + + def initialize_level(self): + + self.field = np.full((self.size, self.size), enc.WALL, dtype=np.int8) + self.costs = self.rng.randint(1, 5, self.field.shape, dtype=np.int8) + + self.start = (0, 0) + + self.deltas = [ + (0, 1), + (0, -1), + (1, 0), + (-1, 0) + ] + self.random_walk() + end = np.where(self.field == enc.SPACE) + self.end = (int(end[0][-1]), int(end[1][-1])) + + self.replace_walls_with_high_cost_tiles() + + def replace_walls_with_high_cost_tiles(self): + # select only coordinates of walls + walls = np.where(self.field == enc.WALL) + + n_walls = len(walls[0]) + + # replace about a tenth of the walls... + to_replace = self.rng.randint(0, n_walls, n_walls // 9) + + # ... with space, but very *costly* space (it's trap!) + for ri in to_replace: + x, y = walls[0][ri], walls[1][ri] + self.field[x, y] = enc.SPACE + self.costs[x, y] = 9 + + def random_walk(self): + frontier = list() + + sx, sy = self.start + self.field[sx, sy] = enc.SPACE + frontier.extend(self.get_walls(self.start)) + + while len(frontier) > 0: + current, opposing = frontier[self.rng.randint(len(frontier))] + + cx, cy = current + ox, oy = opposing + if self.field[ox, oy] == enc.WALL: + self.field[cx, cy] = enc.SPACE + self.field[ox, oy] = enc.SPACE + frontier.extend(self.get_walls(opposing)) + else: + frontier.remove((current, opposing)) + + def in_bounds(self, position): + x, y = position + return x >= 0 and y >= 0 and x < self.size and y < self.size + + def get_walls(self, position): + walls = [] + px, py = position + for dx, dy in self.deltas: + cx = px + dx + cy = py + dy + current = (cx, cy) + + ox = px + 2 * dx + oy = py + 2 * dy + opposing = (ox, oy) + + if (self.in_bounds(current) and self.field[cx, cy] == enc.WALL and self.in_bounds(opposing)): + walls.append((current, opposing)) + return walls + + +# this is code taken from +# https://github.com/dandrino/terrain-erosion-3-ways/blob/master/util.py +# Copyright (c) 2018 Daniel Andrino +# (project is MIT licensed) +def fbm(shape, p, lower=-np.inf, upper=np.inf): + freqs = tuple(np.fft.fftfreq(n, d=1.0 / n) for n in shape) + freq_radial = np.hypot(*np.meshgrid(*freqs)) + envelope = (np.power(freq_radial, p, where=freq_radial != 0) * + (freq_radial > lower) * (freq_radial < upper)) + envelope[0][0] = 0.0 + phase_noise = np.exp(2j * np.pi * np.random.rand(*shape)) + return np.real(np.fft.ifft2(np.fft.fft2(phase_noise) * envelope)) + + +class TerrainLevel(BaseLevel): + def __init__(self, rng, size): + super().__init__(rng, size) + + def initialize_level(self): + + self.field = np.full((self.size, self.size), enc.SPACE, dtype=np.int8) + + self.costs = fbm(self.field.shape, -2) + self.costs -= self.costs.min() + self.costs /= self.costs.max() + self.costs *= 9 + self.costs += 1 + self.costs = self.costs.astype(int) + + self.start = (0, 0) + self.end = (self.size - 1, self.size - 1) + + x = 0 + y = self.size - 1 + for i in range(0, self.size): + self.field[x, y] = enc.WALL + x += 1 + y -= 1 + + self.replace_one_or_more_walls() + + def replace_one_or_more_walls(self): + # select only coordinates of walls + walls = np.where(self.field == enc.WALL) + n_walls = len(walls[0]) + n_replace = self.rng.randint(1, max(2, n_walls // 5)) + to_replace = self.rng.randint(0, n_walls, n_replace) + + for ri in to_replace: + x, y = walls[0][ri], walls[1][ri] + self.field[x, y] = enc.SPACE + + +class RoomLevel(BaseLevel): + def __init__(self, rng, size): + super().__init__(rng, size) + + def initialize_level(self): + self.field = np.full((self.size, self.size), enc.SPACE, dtype=np.int8) + self.costs = np.ones_like(self.field, dtype=np.float32) + + k = 1 + self.subdivide(self.field.view(), self.costs.view(), k, 0, 0) + + # such a *crutch*! + # this 'repairs' dead ends. horrible stuff. + for x in range(1, self.size - 1): + for y in range(1, self.size - 1): + s = 0 + s += self.field[x - 1, y] + s += self.field[x + 1, y] + s += self.field[x, y - 1] + s += self.field[x, y + 1] + if self.field[x, y] == enc.SPACE and s >= 3: + self.field[x - 1, y] = enc.SPACE + self.field[x + 1, y] = enc.SPACE + self.field[x, y - 1] = enc.SPACE + self.field[x, y + 1] = enc.SPACE + + spaces = np.where(self.field == enc.SPACE) + n_spaces = len(spaces[0]) + + n_danger = self.rng.randint(3, 7) + dangers = self.rng.choice(range(n_spaces), n_danger, replace=False) + for di in dangers: + rx, ry = np.unravel_index(di, (self.size, self.size)) + const = max(1., self.rng.randint(self.size // 5, self.size // 2)) + for x in range(self.size): + for y in range(self.size): + distance = np.sqrt((rx - x) ** 2 + (ry - y) ** 2) + self.costs[x, y] = self.costs[x, y] + (1. / (const + distance)) + + self.costs = self.costs - self.costs.min() + self.costs = self.costs / self.costs.max() + self.costs = self.costs * 9 + self.costs = self.costs + 1 + self.costs = self.costs.astype(int) + + start_choice = 0 + end_choice = -1 + + self.start = (int(spaces[0][start_choice]), int(spaces[1][start_choice])) + self.end = (int(spaces[0][end_choice]), int(spaces[1][end_choice])) + + if self.start == self.end: + raise RuntimeError('should never happen') + + def subdivide(self, current, costs, k, d, previous_door): + w, h = current.shape + random_stop = self.rng.randint(0, 10) == 0 and d > 2 + if w <= 2 * k + 1 or h <= 2 * k + 1 or random_stop: + return + + split = previous_door + while split == previous_door: + split = self.rng.randint(k, w - k) + current[split, :] = enc.WALL + door = self.rng.randint(k, h - k) + current[split, door] = enc.SPACE + + self.subdivide( + current[:split, :].T, + costs[:split, :].T, + k, + d + 1, + door + ) + self.subdivide( + current[split + 1:, :].T, + costs[split + 1:, :].T, + k, + d + 1, + door + ) + + +class Simple2DProblem(Problem): + """ + the states are the positions on the board that the agent can walk on + """ + + ACTIONS_DELTA = OrderedDict([ + ('R', (+1, 0)), + ('U', (0, -1)), + ('D', (0, +1)), + ('L', (-1, 0)), + ]) + + def __init__(self, board, costs, start, end): + self.board = board + self.costs = costs + self.start_state = start + self.end_state = end + self.n_expands = 0 + + def get_start_node(self): + return Node(None, self.start_state, None, 0, 0) + + def get_end_node(self): + return Node(None, self.end_state, None, 0, 0) + + def is_end(self, node): + return node.state == self.end_state + + def action_cost(self, state, action): + # for the MazeProblem, the cost of any action + # is stored at the coordinates of the successor state, + # and represents the cost of 'stepping onto' this + # position on the board + sx, sy = self.__delta_state(state, action) + return self.costs[sx, sy] + + def successor(self, node, action): + # determine the next state + successor_state = self.__delta_state(node.state, action) + if successor_state is None: + return None + + # determine what it would cost to take this action in this state + cost = self.action_cost(node.state, action) + + # add the next state to the list of successor nodes + return Node( + node, + successor_state, + action, + node.cost + cost, + node.depth + 1 + ) + + def get_number_of_expanded_nodes(self): + return self.n_expands + + def reset(self): + self.n_expands = 0 + + def successors(self, node): + self.n_expands += 1 + successor_nodes = [] + for action in self.ACTIONS_DELTA.keys(): + succ = self.successor(node, action) + if succ is not None and succ != node: + successor_nodes.append(succ) + return successor_nodes + + def to_json(self): + return json.dumps(dict( + type=self.__class__.__name__, + board=self.board.tolist(), + costs=self.costs.tolist(), + start_state=self.start_state, + end_state=self.end_state + )) + + @staticmethod + def draw_nodes(fig, ax, name, node_collection, color, marker): + states = np.array([node.state for node in node_collection]) + if len(states) > 0: + ax.scatter(states[:, 0], states[:, 1], color=color, label=name, marker=marker) + + @staticmethod + def plot_nodes(fig, ax, nodes): + if len(nodes) > 0: + if len(nodes[0]) == 3: + for (name, marker, node_collection), color in zip(nodes, TABLEAU_COLORS): + if len(node_collection) > 0: + Simple2DProblem.draw_nodes(fig, ax, name, node_collection, color, marker) + else: + for name, marker, node_collection, color in nodes: + if len(node_collection) > 0: + Simple2DProblem.draw_nodes(fig, ax, name, node_collection, color, marker) + + ax.legend( + bbox_to_anchor=(0.5, -0.03), + loc='upper center', + ) + + def plot_sequences(self, fig, ax, sequences): + start_node = self.get_start_node() + for (name, action_sequence), color in zip(sequences, XKCD_COLORS): + self.draw_path(fig, ax, name, start_node, action_sequence, color) + + ax.legend( + bbox_to_anchor=(0.5, -0.03), + loc='upper center', + ) + + + def draw_path(self, fig, ax, name, start_node, action_sequence, color): + current = start_node + xs = [current.state[0]] + ys = [current.state[1]] + us = [0] + vs = [0] + + length = len(action_sequence) + cost = 0 + costs = [0] * length + for i, action in enumerate(action_sequence): + costs[i] = current.cost + xs.append(current.state[0]) + ys.append(current.state[1]) + current = self.successor(current, action) + dx, dy = self.ACTIONS_DELTA[action] + us.append(dx) + vs.append(-dy) + cost = current.cost + + quiv = ax.quiver( + xs, ys, us, vs, + color=color, + label='{} l:{} c:{}'.format(name, length, cost), + scale_units='xy', + units='xy', + scale=1, + headwidth=1, + headlength=1, + linewidth=1, + picker=5 + ) + return quiv + + def plot_field_and_costs_aux(self, fig, show_coordinates, show_grid, + field_ax=None, costs_ax=None): + + if field_ax is None: + ax = field_ax = plt.subplot(121) + else: + ax = field_ax + + ax.set_title('The field') + im = ax.imshow(self.board.T, cmap='gray_r') + + divider = make_axes_locatable(ax) + cax = divider.append_axes('right', size='5%', pad=0) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks([0, 1]) + cbar.set_ticklabels([0, 1]) + + if costs_ax is None: + ax = costs_ax = plt.subplot(122, sharex=ax, sharey=ax) + else: + ax = costs_ax + + ax.set_title('The costs (for stepping on a tile)') + im = ax.imshow(self.costs.T, cmap='viridis') + divider = make_axes_locatable(ax) + cax = divider.append_axes('right', size='5%', pad=0) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + ticks = np.arange(self.costs.min(), self.costs.max() + 1) + cbar.set_ticks(ticks) + cbar.set_ticklabels(ticks) + + for ax in [field_ax, costs_ax]: + ax.tick_params( + top=show_coordinates, + left=show_coordinates, + labelleft=show_coordinates, + labeltop=show_coordinates, + right=False, + bottom=False, + labelbottom=False + ) + + # Major ticks + s = self.board.shape[0] + ax.set_xticks(np.arange(0, s, 1)) + ax.set_yticks(np.arange(0, s, 1)) + + # Minor ticks + ax.set_xticks(np.arange(-.5, s, 1), minor=True) + ax.set_yticks(np.arange(-.5, s, 1), minor=True) + + if show_grid: + for color, ax in zip(['m', 'w'], [field_ax, costs_ax]): + # Gridlines based on minor ticks + ax.grid(which='minor', color=color, linestyle='-', linewidth=1) + + return field_ax, costs_ax + + def visualize(self, sequences=None, show_coordinates=False, show_grid=False, plot_filename=None): + + nodes = [ + ('start', 'o', [self.get_start_node()]), + ('end', 'o', [self.get_end_node()]) + ] + + fig = plt.figure(figsize=(10, 7)) + field_ax, costs_ax = self.plot_field_and_costs_aux(fig, show_coordinates, show_grid) + if sequences is not None and len(sequences) > 0: + self.plot_sequences(fig, field_ax, sequences) + self.plot_sequences(fig, costs_ax, sequences) + + if nodes is not None and len(nodes) > 0: + Simple2DProblem.plot_nodes(fig, field_ax, nodes) + + plt.tight_layout() + if plot_filename is not None: + plt.savefig(plot_filename) + plt.close(fig) + else: + plt.show() + + + @staticmethod + def from_json(jsonstring): + data = json.loads(jsonstring) + return Simple2DProblem( + np.array(data['board']), + np.array(data['costs']), + tuple(data['start_state']), + tuple(data['end_state']) + ) + + @staticmethod + def from_dict(data): + return Simple2DProblem( + np.array(data['board']), + np.array(data['costs']), + tuple(data['start_state']), + tuple(data['end_state']) + ) + + def __delta_state(self, state, action): + # the old state's coordinates + x, y = state + + # the deltas for each coordinates + dx, dy = self.ACTIONS_DELTA[action] + + # compute the coordinates of the next state + sx = x + dx + sy = y + dy + + if self.__on_board(sx, sy) and self.__walkable(sx, sy): + # (sx, sy) is a *valid* state if it is on the board + # and there is no wall where we want to go + return sx, sy + else: + # EIEIEIEIEI. up until assignment 1, this returned None :/ + # this had no consequences on the correctness of the algorithms, + # but the explanations, and the self-edges were wrong + return x, y + + def __on_board(self, x, y): + size = len(self.board) # all boards are quadratic + return x >= 0 and x < size and y >= 0 and y < size + + def __walkable(self, x, y): + return self.board[x, y] != enc.WALL diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..af8ecd4 --- /dev/null +++ b/shell.nix @@ -0,0 +1,15 @@ +{ + pkgs ? import { }, +}: + +pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + python3Packages.notebook + python3Packages.numpy + python3Packages.matplotlib + graphviz + python3Packages.networkx + python3Packages.pydot + ]; +} diff --git a/uninformed_search.ipynb b/uninformed_search.ipynb new file mode 100644 index 0000000..8a881be --- /dev/null +++ b/uninformed_search.ipynb @@ -0,0 +1,1797 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Artificial Intelligence UE\n", + "## Exercises 1 - Uninformed Search\n", + "\n", + "In this series of exercises you can implement a few different **uninformed** search algorithms: \n", + "- Breadth First Search\n", + "- Uniform Cost Search\n", + "- Depth First Search\n", + "- Depth Limited Depth First Search\n", + "- Iterative Deepening Search\n", + "\n", + "The algorithms have been explained in the lecture (VO) and we gave you some additional information in the exercise (UE). Please refer to the lecture slides (VO) for the pseudo algorithms and the exercise slides (UE) for additional hints. \n", + "Before implementing the algorithms make sure to check out the introductory notebook \"introducing_pig.ipynb\" and read the following instructions carefully.\n", + "\n", + "
\n", + "\n", + "

Practical hints:

\n", + "
    \n", + "
  • Replace the placeholders # YOUR CODE HERE, raise NotImplementedError() with your code.
  • \n", + "
  • Do not rename any of the already existing variables (this might lead to some tests failing / not working).
  • \n", + "
  • solve() should return the found solution node or None if no solution is found. You do not need to store the path, the function node.get_action_sequence() can be used to retrieve it later via backtracking.
  • \n", + "
  • Use a set() to store already visited nodes (when needed).
  • \n", + "
  • Use the imported data structures Queue, Stack, and PriorityQueue as the fringe / frontier (choose the right datatype depending on the algorithm)
  • \n", + "
\n", + "
\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "e8c3c2892151630ce3b9132174833fd7", + "grade": false, + "grade_id": "cell-c5c2d34ef5fe1e7d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "from pig_lite.problem.base import Problem\n", + "from pig_lite.datastructures.queue import Queue\n", + "from pig_lite.datastructures.stack import Stack\n", + "from pig_lite.datastructures.priority_queue import PriorityQueue\n", + "from pig_lite.instance_generation.problem_factory import ProblemFactory" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Hint: Here we load and visualize a problem instance that is used for some basic tests, please do not change it. You can use the printed output after each algorithm (i.e., position of the end node, action sequence) to compare whether your algorithm behaves as expected).\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "b9b312c546887a10a22564e421213312", + "grade": false, + "grade_id": "cell-6f2cca86b47722a9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "factory = ProblemFactory()\n", + "maze = factory.create_problem_from_json(json_path='boards/tiny0.json')\n", + "maze.visualize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Now it's your turn to implement 5 different uninformed search algorithms - all spots that need your attention are marked with # YOUR CODE HERE!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing BFS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "716c2661a31500af4a81eb11f7c180ca", + "grade": false, + "grade_id": "cell-d2df9b0e3d90cf00", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + } + }, + "outputs": [], + "source": [ + "class BFS(object):\n", + "\n", + " def __init__(self) -> None:\n", + " # YOUR CODE HERE: initialize self.visited and self.fringe here or in the solve function with the correct datatypes\n", + " self.visited = None\n", + " self.fringe = None\n", + "\n", + " def solve(self, problem: Problem): \n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()\n", + " return None\n", + " \n", + "bfs_search = BFS()\n", + "maze.reset() # resets maze for hidden tests\n", + "bfs_solution = bfs_search.solve(maze)\n", + "\n", + "if bfs_solution is not None:\n", + " bfs_solution.pretty_print()\n", + " maze.visualize(sequences=[('bfs', bfs_solution.get_action_sequence())])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic Checks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "bf5d1779e9b62e8a9be1d3cd10c6a820", + "grade": true, + "grade_id": "cell-ca9a92a1cfa1444d", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(bfs_solution is not None), \"your algorithm did not return a solution\"\n", + "assert(bfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "\n", + "assert(bfs_solution.cost == 20), \"the solution found by your algorithm did not return the expected cost\"\n", + "assert(bfs_solution.depth == 8), \"the solution found by your algorithm does not have the expected length\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check visited set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "170ce4b6b16402c27f4505b590329d0c", + "grade": true, + "grade_id": "cell-ced878c4bd3f8b22", + "locked": true, + "points": 0.5, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(bfs_search.visited is not None), \"it seems you did not correctly initialize the visited set\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check fringe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "432e7c6ad5496a0cf9d4adb22873838d", + "grade": true, + "grade_id": "cell-97963b0eac5a8e46", + "locked": true, + "points": 0.6, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(bfs_search.fringe is not None), \"it seems you did not correctly initialize the fringe\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check expaned nodes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "9416b34fff06cfd2dd7611e489a7d53b", + "grade": true, + "grade_id": "cell-25fc2adbfe6e02be", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_expanded_nodes = maze.get_number_of_expanded_nodes()\n", + "assert(bfs_expanded_nodes > 0), \"it seems your algorithm did not expand any nodes\"\n", + "assert(bfs_expanded_nodes == 18), \"it seems your algorithm did not expand the correct number of nodes\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check different mazes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "tiny0 = factory.create_problem_from_json(json_path='boards/tiny0.json')\n", + "bfs_solution = bfs_search.solve(tiny0)\n", + "assert(bfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == 'c283a9803562a0053fc1ea0c30d421e0b4a7a9f599c699d74477cbeeffec23bc'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "tiny1 = factory.create_problem_from_json(json_path='boards/tiny1.json')\n", + "bfs_solution = bfs_search.solve(tiny1)\n", + "assert(bfs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == '66ec8af4739b256cec553c3d2c2cbacbc1fd4c853859c633e44996eed1f6021c'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "tiny2 = factory.create_problem_from_json(json_path='boards/tiny2.json')\n", + "bfs_solution = bfs_search.solve(tiny2)\n", + "assert(bfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == '5bda40f5b72290920507aa1d23329fb5a3445346372a30bd35cc85f743c439ac'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "tiny3 = factory.create_problem_from_json(json_path='boards/tiny3.json')\n", + "bfs_solution = bfs_search.solve(tiny3)\n", + "assert(bfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == 'e1c95ac72da74163d0c237b69f99dc766c1bbe5e4270fd2934b721d5eab9de31'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "tiny4 = factory.create_problem_from_json(json_path='boards/tiny4.json')\n", + "bfs_solution = bfs_search.solve(tiny4)\n", + "assert(bfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == '0123d362bf2df8f84e7c41197827be005159724c07774ef32d9f15373a440091'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "cyclic_map = factory.create_problem_from_json(json_path='boards/cyclic_map.json')\n", + "bfs_solution = bfs_search.solve(cyclic_map)\n", + "assert(bfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == 'e1c95ac72da74163d0c237b69f99dc766c1bbe5e4270fd2934b721d5eab9de31'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "narrow_path = factory.create_problem_from_json(json_path='boards/narrow_path.json')\n", + "bfs_solution = bfs_search.solve(narrow_path)\n", + "assert(bfs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == '2ee4599132a71cf20e4feae6aff58b9ff77ea9486fb0e12225ddd8896ccb9843'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "no_possible_path = factory.create_problem_from_json(json_path='boards/no_possible_path.json')\n", + "bfs_solution = bfs_search.solve(no_possible_path)\n", + "assert(bfs_solution is None), \"your algorithm magically found a solution in an impossible maze\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "start_is_goal = factory.create_problem_from_json(json_path='boards/start_is_goal.json')\n", + "bfs_solution = bfs_search.solve(start_is_goal)\n", + "assert(bfs_solution.state == (0, 0)), \"your algorithm did not return the expected solution\"\n", + "assert(bfs_solution.get_action_sequence_hash() == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this test is supposed to test whether the visited set is used somewhat correctly (otherwise it will timeout)\n", + "\n", + "bfs_search = BFS()\n", + "large = factory.create_problem_from_json(json_path='boards/large.json')\n", + "bfs_solution = bfs_search.solve(large)\n", + "assert(bfs_solution)\n", + "assert(bfs_solution.state == (48, 48)), \"your algorithm did not return the expected solution\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing UCS\n", + "\n", + "- implement Uniform Cost Search (UCS), a variant of Dijkstra's Graph Search" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "57efee82377a65469cc75a8c797111ab", + "grade": false, + "grade_id": "cell-0bc80e814a5b5029", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + } + }, + "outputs": [], + "source": [ + "class UCS(object):\n", + "\n", + " def __init__(self) -> None:\n", + " # YOUR CODE HERE: initialize self.visited and self.fringe here or in the solve function with the correct datatypes\n", + " self.visited = None\n", + " self.fringe = None\n", + "\n", + " def solve(self, problem: Problem):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()\n", + " return None\n", + "\n", + "ucs_search = UCS()\n", + "maze.reset() # resets maze for hidden tests\n", + "ucs_solution = ucs_search.solve(maze)\n", + "\n", + "if ucs_solution is not None:\n", + " ucs_solution.pretty_print()\n", + " maze.visualize(sequences=[('ucs', ucs_solution.get_action_sequence())])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic checks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "e5f06248b3e49c5e4347509304ce684c", + "grade": true, + "grade_id": "cell-aac452910d38e14e", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(ucs_solution is not None), \"your algorithm did not return a solution\"\n", + "assert(ucs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "\n", + "assert(ucs_solution.depth == 8), \"the solution found by your algorithm does not have the expected length\"\n", + "\n", + "ucs_expanded_nodes = maze.get_number_of_expanded_nodes()\n", + "assert(ucs_expanded_nodes == 12), \"it seems your algorithm did not expand the correct number of nodes\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check visited set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "1eb3167d55f3292f93f4985fcfc514f2", + "grade": true, + "grade_id": "cell-f7d28a2a7702c9bd", + "locked": true, + "points": 0.5, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(ucs_search.visited is not None), \"it seems you did not correctly initialize the visited set\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check fringe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "8122ec4f35430855efc6c1e0acb75beb", + "grade": true, + "grade_id": "cell-7502444279f2a79a", + "locked": true, + "points": 0.8, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(ucs_search.fringe is not None), \"it seems you did not correctly initialize the fringe\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Checking cost" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "ddafb0736b258f1b71dadfaf6824b67c", + "grade": true, + "grade_id": "cell-d395b6d5f9d370b1", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "bfs_search = BFS()\n", + "maze.reset()\n", + "bfs_solution = bfs_search.solve(maze)\n", + "\n", + "assert(ucs_solution.cost < bfs_solution.cost), \"the solution cost for UCS should be lower than the one for BFS\"\n", + "assert(ucs_solution.cost == 12), \"the solution found by your algorithm did not return the expected cost\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check different mazes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "tiny0 = factory.create_problem_from_json(json_path='boards/tiny0.json')\n", + "ucs_solution = ucs_search.solve(tiny0)\n", + "assert(ucs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == '6bdb8d2c34f7d512c6c555c27ec66385b0fd13c5401db6a83cdc68000541dbb5'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "tiny1 = factory.create_problem_from_json(json_path='boards/tiny1.json')\n", + "ucs_solution = ucs_search.solve(tiny1)\n", + "assert(ucs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == '473818e05eb92df10661a91a41e2fefd9d3d0b49466df773091a94791b0ac2a5'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "tiny2 = factory.create_problem_from_json(json_path='boards/tiny2.json')\n", + "ucs_solution = ucs_search.solve(tiny2)\n", + "assert(ucs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == '5bda40f5b72290920507aa1d23329fb5a3445346372a30bd35cc85f743c439ac'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "tiny3 = factory.create_problem_from_json(json_path='boards/tiny3.json')\n", + "ucs_solution = ucs_search.solve(tiny3)\n", + "assert(ucs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == 'e1c95ac72da74163d0c237b69f99dc766c1bbe5e4270fd2934b721d5eab9de31'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "tiny4 = factory.create_problem_from_json(json_path='boards/tiny4.json')\n", + "ucs_solution = ucs_search.solve(tiny4)\n", + "assert(ucs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == '0123d362bf2df8f84e7c41197827be005159724c07774ef32d9f15373a440091'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "cyclic_map = factory.create_problem_from_json(json_path='boards/cyclic_map.json')\n", + "ucs_solution = ucs_search.solve(cyclic_map)\n", + "assert(ucs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == 'e1c95ac72da74163d0c237b69f99dc766c1bbe5e4270fd2934b721d5eab9de31'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "narrow_path = factory.create_problem_from_json(json_path='boards/narrow_path.json')\n", + "ucs_solution = ucs_search.solve(narrow_path)\n", + "assert(ucs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ucs_solution.get_action_sequence_hash() == '2ee4599132a71cf20e4feae6aff58b9ff77ea9486fb0e12225ddd8896ccb9843'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "no_possible_path = factory.create_problem_from_json(json_path='boards/no_possible_path.json')\n", + "ucs_solution = ucs_search.solve(no_possible_path)\n", + "assert(ucs_solution is None), \"your algorithm did not handle unreachable goal state correctly\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ucs_search = UCS()\n", + "start_is_goal = factory.create_problem_from_json(json_path='boards/start_is_goal.json')\n", + "ucs_solution = ucs_search.solve(start_is_goal)\n", + "assert(ucs_solution.state == (0, 0)), \"your algorithm did not return the expected solution when starting from the goal state\"\n", + "assert(ucs_solution.get_action_sequence_hash() == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this test is supposed to test whether the visited set is used somewhat correctly (otherwise it will timeout)\n", + "\n", + "ucs_search = UCS()\n", + "large = factory.create_problem_from_json(json_path='boards/large.json')\n", + "ucs_solution = ucs_search.solve(large)\n", + "assert(ucs_solution)\n", + "assert(ucs_solution.state == (48, 48)), \"your algorithm did not return the expected solution\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing DFS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "6c1e9ac7733fbca45f4b842d89e4416f", + "grade": false, + "grade_id": "cell-e94fcb80013152e4", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + } + }, + "outputs": [], + "source": [ + "class DFS(object):\n", + "\n", + " def __init__(self) -> None:\n", + " # YOUR CODE HERE: initialize self.visited and self.fringe here or in the solve function with the correct datatypes\n", + " self.visited = None\n", + " self.fringe = None\n", + "\n", + " def solve(self, problem: Problem):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()\n", + " return None\n", + "\n", + "dfs_search = DFS()\n", + "maze.reset() # resets maze for hidden tests\n", + "dfs_solution = dfs_search.solve(maze)\n", + "\n", + "if dfs_solution is not None:\n", + " dfs_solution.pretty_print()\n", + " maze.visualize(sequences=[('dfs', dfs_solution.get_action_sequence())])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic checks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "13ec840f2f77f176e68155b1f34fcd9d", + "grade": true, + "grade_id": "cell-7729bdb99cbcb357", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(dfs_solution is not None), \"your algorithm did not return a solution\"\n", + "assert(dfs_solution is not None or isinstance(dfs_solution, type(maze.get_start_node()))), \"your solution is not of the right type\"\n", + "assert(dfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "\n", + "assert(dfs_solution.cost == 24), \"the solution found by your algorithm did not return the expected cost\"\n", + "assert(dfs_solution.depth == 12), \"the solution found by your algorithm does not have the expected length\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check visited set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "8c6f1ddd46e0cc7c3830bb347c612fc5", + "grade": true, + "grade_id": "cell-ea2de578de0929dd", + "locked": true, + "points": 0.5, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(dfs_search.visited is not None), \"it seems you did not correctly initialize the visited set\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check fringe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "31e1861c96a7c40bdca43bd26f943874", + "grade": true, + "grade_id": "cell-45ac331b0d77b101", + "locked": true, + "points": 0.8, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(dfs_search.fringe is not None), \"it seems you did not correctly initialize the fringe\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check different mazes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "00f5c7aa425bc6f808edea2478337c48", + "grade": true, + "grade_id": "cell-fbef9ebfc64d0579", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "tiny0 = factory.create_problem_from_json(json_path='boards/tiny0.json')\n", + "dfs_solution = dfs_search.solve(tiny0)\n", + "assert(dfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == 'e1fa32922bd28adb1dbb8a08b56547137b98d1105229a033162ae0edb274f418'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "fb8984e9d558951d18e22f60972f98ce", + "grade": true, + "grade_id": "cell-b7dffcb6abd93552", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "tiny1 = factory.create_problem_from_json(json_path='boards/tiny1.json')\n", + "dfs_solution = dfs_search.solve(tiny1)\n", + "assert(dfs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == '50ad40a2921e4009cab8f9bdb1c7a00bf36f69fbe7a20b83aeb93227334ee601'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "cc94a80ee24db1db3fb7bcf2f5fc910b", + "grade": true, + "grade_id": "cell-d10f2fec91098425", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "tiny2 = factory.create_problem_from_json(json_path='boards/tiny2.json')\n", + "dfs_solution = dfs_search.solve(tiny2)\n", + "assert(dfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == '5bda40f5b72290920507aa1d23329fb5a3445346372a30bd35cc85f743c439ac'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "4cc6902eda0bdb8335c102f497a4ace0", + "grade": true, + "grade_id": "cell-1b277be9230355d0", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "tiny3 = factory.create_problem_from_json(json_path='boards/tiny3.json')\n", + "dfs_solution = dfs_search.solve(tiny3)\n", + "assert(dfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == '5bda40f5b72290920507aa1d23329fb5a3445346372a30bd35cc85f743c439ac'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "09d1faa9749bb1e2cf83b33c1fb528f2", + "grade": true, + "grade_id": "cell-c4f7dbe4d3a2ca3d", + "locked": true, + "points": 0.2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "tiny4 = factory.create_problem_from_json(json_path='boards/tiny4.json')\n", + "dfs_solution = dfs_search.solve(tiny4)\n", + "assert(dfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == '0123d362bf2df8f84e7c41197827be005159724c07774ef32d9f15373a440091'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "cyclic_map = factory.create_problem_from_json(json_path='boards/cyclic_map.json')\n", + "dfs_solution = dfs_search.solve(cyclic_map)\n", + "assert(dfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == '61ade70d1b7727f7e21ea8d65cbc2afc5239a5cc2a0c056593d39b43edcc82df'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "narrow_path = factory.create_problem_from_json(json_path='boards/narrow_path.json')\n", + "dfs_solution = dfs_search.solve(narrow_path)\n", + "assert(dfs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == '2ee4599132a71cf20e4feae6aff58b9ff77ea9486fb0e12225ddd8896ccb9843'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "no_possible_path = factory.create_problem_from_json(json_path='boards/no_possible_path.json')\n", + "dfs_solution = dfs_search.solve(no_possible_path)\n", + "assert(dfs_solution is None), \"your algorithm magically found a solution in an impossible maze\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dfs_search = DFS()\n", + "start_is_goal = factory.create_problem_from_json(json_path='boards/start_is_goal.json')\n", + "dfs_solution = dfs_search.solve(start_is_goal)\n", + "assert(dfs_solution.state == (0, 0)), \"your algorithm did not return the expected solution\"\n", + "assert(dfs_solution.get_action_sequence_hash() == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "e9c1cb9ad2c23a47501c54fddb3d4a28", + "grade": true, + "grade_id": "cell-f24c21dd867ccc44", + "locked": true, + "points": 0.5, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this test is supposed to test whether the visited set is used somewhat correctly (otherwise it will timeout)\n", + "\n", + "dfs_search = DFS()\n", + "large = factory.create_problem_from_json(json_path='boards/large.json')\n", + "dfs_solution = dfs_search.solve(large)\n", + "assert(dfs_solution)\n", + "assert(dfs_solution.state == (48, 48)), \"your algorithm did not return the expected solution\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing DLDFS\n", + "\n", + "Hints:\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "da284236de56495b7e580ad070a76dc8", + "grade": false, + "grade_id": "cell-3e0fae5d39e252d7", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + } + }, + "outputs": [], + "source": [ + "class DLDFS_Recursive(object):\n", + " def __init__(self, max_depth):\n", + " self.max_depth = max_depth\n", + "\n", + " def solve_aux(self, problem, current):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()\n", + " return None\n", + "\n", + " def solve(self, problem: Problem):\n", + " return self.solve_aux(problem, problem.get_start_node())\n", + "\n", + "\n", + "dldfs_search = DLDFS_Recursive(12)\n", + "maze.reset() # resets maze for hidden tests\n", + "dldfs_solution = dldfs_search.solve(maze)\n", + "\n", + "if dldfs_solution is not None:\n", + " dldfs_solution.pretty_print()\n", + " maze.visualize(sequences=[('dldfs', dldfs_solution.get_action_sequence())])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic Checks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "8f0da3ceb956f1584b6b0fde776cc8c3", + "grade": true, + "grade_id": "cell-07d2f9c64ab2c152", + "locked": true, + "points": 2, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(dldfs_solution is not None), \"your algorithm did not return a solution\"\n", + "assert(dldfs_solution is not None or isinstance(dldfs_solution, type(maze.get_start_node()))), \"your solution is not of the right type\"\n", + "assert(dldfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "\n", + "assert(dldfs_solution.cost == 36), \"the solution found by your algorithm did not return the expected cost\"\n", + "assert(dldfs_solution.depth == 12), \"the solution found by your algorithm does not have the expected length\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check different mazes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# we need more depth for some of the problem instances\n", + "max_dldfs_depth = 25" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "807edb2d77a6dd6e592db96a14c2df80", + "grade": true, + "grade_id": "cell-5fafe913c0c969c2", + "locked": true, + "points": 0.4, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "tiny0 = factory.create_problem_from_json(json_path='boards/tiny0.json')\n", + "dldfs_solution = dldfs_search.solve(tiny0)\n", + "assert(dldfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == '5eb9f93f575001cbddca893c699d418e47eff2b53f0c9a1a2072c3b9ad643dc7'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "046f9d9fc7c3aa47f7d1864795046cf6", + "grade": true, + "grade_id": "cell-462d4b407fabc045", + "locked": true, + "points": 0.4, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "tiny1 = factory.create_problem_from_json(json_path='boards/tiny1.json')\n", + "dldfs_solution = dldfs_search.solve(tiny1)\n", + "assert(dldfs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == '7f10f7179c1d448ed33e8b8d0bb5372266cea15bd9ec0ffbdd11118438754d99'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "d9ca3e75bfa55423bc3d947434ba26f9", + "grade": true, + "grade_id": "cell-30634f5520d90c17", + "locked": true, + "points": 0.4, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "tiny2 = factory.create_problem_from_json(json_path='boards/tiny2.json')\n", + "dldfs_solution = dldfs_search.solve(tiny2)\n", + "assert(dldfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == 'ebb80a630c129a7234ed6c9bcde740930a01622f4adde8b304db362ec5a24725'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "db1f664f956e3158981b58e5a41ae86c", + "grade": true, + "grade_id": "cell-4d12d065035af707", + "locked": true, + "points": 0.4, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "tiny3 = factory.create_problem_from_json(json_path='boards/tiny3.json')\n", + "dldfs_solution = dldfs_search.solve(tiny3)\n", + "assert(dldfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == '8ec892c800ce37ea54e9ba4dab28681de6956e481d2ae0e032b1e11c59e4a718'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "28b14dd36a9a8cfb1552d94655a67b79", + "grade": true, + "grade_id": "cell-9320c5aa64bda863", + "locked": true, + "points": 0.4, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "tiny4 = factory.create_problem_from_json(json_path='boards/tiny4.json')\n", + "dldfs_solution = dldfs_search.solve(tiny4)\n", + "assert(dldfs_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == 'e097ad8539f0d2f7abe3160c1a47c24e1b19bdb9b0c40e0dcd7e9ecb3307c7bb'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=2)\n", + "cyclic_map = factory.create_problem_from_json(json_path='boards/cyclic_map.json')\n", + "dldfs_solution = dldfs_search.solve(cyclic_map)\n", + "assert(dldfs_solution is None), \"your algorithm should not find a solution at max_depth=2\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "cyclic_map = factory.create_problem_from_json(json_path='boards/cyclic_map.json')\n", + "dldfs_solution = dldfs_search.solve(cyclic_map)\n", + "assert(dldfs_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == '7ea076a692e54d03a54b160dbceb28175b083f29615149dda6a21a8c6347b28a'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "narrow_path = factory.create_problem_from_json(json_path='boards/narrow_path.json')\n", + "dldfs_solution = dldfs_search.solve(narrow_path)\n", + "assert(dldfs_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == 'ffb856b0937bb3fb160f44c61f57d391e0edfa9c6671bbfa773fcdba7c317b13'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "no_possible_path = factory.create_problem_from_json(json_path='boards/no_possible_path.json')\n", + "dldfs_solution = dldfs_search.solve(no_possible_path)\n", + "assert(dldfs_solution is None), \"your algorithm magically found a solution in an impossible maze\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "dldfs_search = DLDFS_Recursive(max_depth=max_dldfs_depth)\n", + "start_is_goal = factory.create_problem_from_json(json_path='boards/start_is_goal.json')\n", + "dldfs_solution = dldfs_search.solve(start_is_goal)\n", + "assert(dldfs_solution.state == (0, 0)), \"your algorithm did not return the expected solution\"\n", + "assert(dldfs_solution.get_action_sequence_hash() == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing IDS\n", + "\n", + "Hint:\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "8f21b2a9464561f48f79410a2f8a1467", + "grade": false, + "grade_id": "cell-b3614f9983cf1dce", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + } + }, + "outputs": [], + "source": [ + "class IDS(object):\n", + " def solve(self, problem: Problem):\n", + " node = None\n", + " depth = 0\n", + "\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()\n", + " return node\n", + " \n", + "ids_search = IDS()\n", + "maze.reset() # resets maze for hidden tests\n", + "ids_solution = ids_search.solve(maze)\n", + "\n", + "if ids_solution is not None:\n", + " ids_solution.pretty_print()\n", + " maze.visualize(sequences=[('ids', ids_solution.get_action_sequence())])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "05b85a83571ad65322ac4af47727e218", + "grade": true, + "grade_id": "cell-36d9e04df571467f", + "locked": true, + "points": 0.5, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "assert(ids_solution is not None), \"your algorithm did not return a solution\"\n", + "assert(ids_solution is not None or isinstance(ids_solution, type(maze.get_start_node()))), \"your solution is not of the right type\"\n", + "assert(ids_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "\n", + "assert(ids_solution.cost == 20), \"the solution found by your algorithm did not return the expected cost\"\n", + "assert(ids_solution.depth == 8), \"the solution found by your algorithm does not have the expected length\"\n", + "\n", + "ids_expanded_nodes = maze.get_number_of_expanded_nodes()\n", + "bfs_search = BFS()\n", + "maze.reset() # resets maze for hidden tests\n", + "bfs_solution = bfs_search.solve(maze)\n", + "bfs_expanded_nodes = maze.get_number_of_expanded_nodes()\n", + "assert(ids_expanded_nodes > bfs_expanded_nodes), \"the solution found by your algorithm is expected to have more expanded nodes than your solution for BFS.\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check different mazes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "d2957d5766c737d2afc1045b5ccaf34f", + "grade": true, + "grade_id": "cell-951813681d37fac0", + "locked": true, + "points": 0.1, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "tiny0 = factory.create_problem_from_json(json_path='boards/tiny0.json')\n", + "ids_solution = ids_search.solve(tiny0)\n", + "assert(ids_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == 'c283a9803562a0053fc1ea0c30d421e0b4a7a9f599c699d74477cbeeffec23bc'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "458ff494791981e923559334759eb463", + "grade": true, + "grade_id": "cell-90bd1d9cace41b93", + "locked": true, + "points": 0.1, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "tiny1 = factory.create_problem_from_json(json_path='boards/tiny1.json')\n", + "ids_solution = ids_search.solve(tiny1)\n", + "assert(ids_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == '66ec8af4739b256cec553c3d2c2cbacbc1fd4c853859c633e44996eed1f6021c'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "269cd4b03aa9fa4ccd4b09cf9982420b", + "grade": true, + "grade_id": "cell-f38380f9ab502704", + "locked": true, + "points": 0.1, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "tiny2 = factory.create_problem_from_json(json_path='boards/tiny2.json')\n", + "ids_solution = ids_search.solve(tiny2)\n", + "assert(ids_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == '5bda40f5b72290920507aa1d23329fb5a3445346372a30bd35cc85f743c439ac'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "5962b78731fc9cc9fad3645abc3d69ab", + "grade": true, + "grade_id": "cell-58d667cc9d701e52", + "locked": true, + "points": 0.1, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "tiny3 = factory.create_problem_from_json(json_path='boards/tiny3.json')\n", + "ids_solution = ids_search.solve(tiny3)\n", + "assert(ids_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == 'e1c95ac72da74163d0c237b69f99dc766c1bbe5e4270fd2934b721d5eab9de31'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "c4d100fafaf5c914e3329ac98c1ecec0", + "grade": true, + "grade_id": "cell-768a694fd1d7c870", + "locked": true, + "points": 0.1, + "schema_version": 3, + "solution": false, + "task": false + } + }, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "tiny4 = factory.create_problem_from_json(json_path='boards/tiny4.json')\n", + "ids_solution = ids_search.solve(tiny4)\n", + "assert(ids_solution.state == (4, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == '0123d362bf2df8f84e7c41197827be005159724c07774ef32d9f15373a440091'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "cyclic_map = factory.create_problem_from_json(json_path='boards/cyclic_map.json')\n", + "ids_solution = ids_search.solve(cyclic_map)\n", + "assert(ids_solution.state == (2, 2)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == 'e1c95ac72da74163d0c237b69f99dc766c1bbe5e4270fd2934b721d5eab9de31'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "narrow_path = factory.create_problem_from_json(json_path='boards/narrow_path.json')\n", + "ids_solution = ids_search.solve(narrow_path)\n", + "assert(ids_solution.state == (2, 4)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == '2ee4599132a71cf20e4feae6aff58b9ff77ea9486fb0e12225ddd8896ccb9843'), \"your algorithm did not return the expected solution path\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this is a testing cell, do not edit or delete\n", + "\n", + "ids_search = IDS()\n", + "start_is_goal = factory.create_problem_from_json(json_path='boards/start_is_goal.json')\n", + "ids_solution = ids_search.solve(start_is_goal)\n", + "assert(ids_solution.state == (0, 0)), \"your algorithm did not return the expected solution\"\n", + "assert(ids_solution.get_action_sequence_hash() == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), \"your algorithm did not return the expected solution path\"" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}