HW2: State Space Search
This short assignment will give you some experience in using state space search and developing a heuristic for Algorithm A*.
1 The water jug puzzle
Classic two water jugs problem is that you given two jugs J1 and J2 with capacities C1 and C2, initially filled with W1 and W2. Can you end up with exactly G1 liters in J1 and G2 liters in J2? You're allowed the following actions: dump the contents of either jug onto the floor, or pour the contents of one jug into the other until either the jug from which you are pouring is empty or the one you are filling is full. We looked at a simple way to solve this in Python using AIMA's search.py module, both as a conventional python program (wj.py) and as a jupyter notebook (wj.ipynb).
In a variation of the puzzle, you are allowed the following six actions: dump the contents of either jug onto the floor, pour the contents of one jug into the other until either the jug from which you are pouring is empty or the one you are filling is full, and filling either jug that is not yet full from a faucet until the it is full. In our variation, the cost of each action is 1 plus the amount of water it uses (if any) from the faucet. For example, the action of emptying J1 costs 1, and toping off J1 from the faucet if it has capacity five liters but only contains two liters of water costs 4. See this video from the film Die Hard 3 for an example.
Your goal is to create a version to solve this version of the problem by modifying the version for the simple case. You will at least have to change the problem's methods for:
- computing the actions that are possible in a state;
- computing the next state that results in applying a (legal) action to a given state;
- computing the cost of an action taken in a given state; and
- returning the h value for a given state, which should be an admissible function, i.e., one that never over-estimates the distance to a goal state. It should not be a trivial one, i.e., returning 0 for a goal state and 1 for a non-goal state. Initially, you may want to use that trivial heuristic while you are getting the rest of the code working.
We also ask that you add a new method to the problem class, reachable_states(), that takes no arguments (other than self, of course) and returns a set of all of the states that are reachable from the problem's initial state. This not as hard as it might sound. Here's a description of a good approach.
Your function should start be defining two variables, seen and fringe, both of which will be bound to a python set. The seen variable will be used to hold all of the states known so far to be reachable and is initially empty. The variable fringe is a set of all of the nodes that we have generated as reachable, but that we've not expanded to find out what successor nodes they have. The algorithm then loops until fringe is empty, at which time it returns the set seen. In the loop, if fringe is not empty, remove one of its members with the pop() method, add that node to seen with the add() method, and compute all of its successors, adding them to the set fringe with the add() method.
Note that this is similar a simple problem space search algorithm, but it's not concerned with finding a particular goal nor does care about finding paths or path costs. It just generates all possible legal states. You would not want to do this for problems with a large number of states, but for this problem, it's not too bad.
2. preliminaries
Start by cloning the hw2 assignment and take a brief look at it. It includes the included search.py and utils.py files. You should review these to look at the Problem class and its methods, in particular, and the search algorithms to fully understand what you are doing. Note that this code only works in Python3. To invoke python3 on gl, just use the command python3 rather than python. If you want to install a version of Python 3 on your own computer, you can do this without removing a version of Python2, if you have one already installed. You should be able to find and download the latest version of Python for your operating system here.
3. What to do
Accept the HW2 assignment, which will create a repository for you on GitHub, which you should clone on the computer you will use to do the homework, e.g., your laptop or gl. This repository is self-contained in that it already includes the aima-python packages search.py and utils.py, so you do not need any additional files from aima-python. It also includes the simple water jug puzzle files (wj.py, wjtest.py, wj.ipnb) which you can study and experiment with.
We've created a stub file for wj2.py which is the only program file you need to complete. To complete this stub, you must finish the generic WJ2 class by completing the relevant methods as indicated by the comments.
4. Testing your code
Once you've written your
code you can execute the python file wj2test.py, which will try your solution for several test cases. You can see the results that our model solution generates for this test file in test_output1.txt and test_output2.txt While your answers might differ from the ones we've given, they should be similar. Our model solution uses use an admissible heuristic, so the lengths of the solutions found are the shortest ones possible. Do check your answers to see if they make sense.
When we evaluate your homework we will test it with additional inputs, of course. 5. some questions to answer
Complete your copy of the file questions.md by answering each of the questions.
6. What to hand in
After writing and testing your wj2.py and completing questions.md, you should commit those files and push them back to GitHub.
7. Background reading
|