24

I'm implementing a-star algorithm with Manhattan distance to solve the 8-puzzle (in C). It seems to work very well and passes a lot of unit tests but it fails to find the shortest path in one case (it finds 27 steps instead of 25).

When I change the heuristic function to Hamming distance it finds in 25 steps. Also finds in 25 steps when I make the Manhattan distance function to return a half of the actual cost.

That's why I believe the problem lies somewhere in Manhattan distance function and it is over estimating the cost (hence inadmissible). I thought maybe something else is going wrong in the C program so I wrote a little Python script to test and verify the output of the Manhattan distance function only and they both produce the exact same result.

I'm really confused because the heuristic function seems to be the only point of failure and it seems to be correct at the same time.

8-puzzle start goal

You can try this solver and put the tile order like "2,6,1,0,7,8,3,5,4" Choose the algorithm Manhattan distance and it finds in 25 steps. Now change it to Manhattan distance + linear conflict and it finds 27 steps.

But my Manhattan distance (without linear conflict) finds in 27 steps.

Here's my general algorithm:

manhattan_distance = 0
iterate over all tiles
if the tile is not the blank tile:
find the coordinates of this tile on the goal board
manhattan_distance += abs(x - goal_x) + abs(y - goal_y)

I think if there was something very badly wrong with some important part it wouldn't pass all 25+ previous tests so this might be some sort of edge case.

Here's commented Manhattan distance function in C:

int ManhattanDistance(Puzzle p, State b){
   State goal = getFinalState(p);
   int size = getSize(b);
   int distance = 0;
   if (getSize(goal) == size){ // both states are the same size
      int i, j;
      for(i=0; i<size; i++){
         for(j=0; j<size; j++){ // iterate over all tiles
            int a = getStateValue(b, i, j); // what is the number on this tile?
            if (a != 'B'){ // if it's not the blank tile
               int final_cordinates[2];
               getTileCoords(goal, a, final_cordinates); // find the coordinates on the other board
               int final_i = final_cordinates[0];
               int final_j = final_cordinates[1];
               distance +=  abs(i - final_i) + abs(j - final_j);
            }
         }
      }
   }
   return distance;
}

Please help me.

EDIT: As discussed in comments, the code provided for opening nodes can be found here

amit
  • 175,853
  • 27
  • 231
  • 333
Babak
  • 582
  • 4
  • 14
  • 1
    Your manhattan distance function seems fine to me..[well, at least from just reading it...] are you sure it is the issue? maybe your A* implementation doesn't re-open closed nodes when finding shorter path to them? that could explain why this bug doesn't always occur. – amit Oct 24 '11 at 13:13
  • also note, that if `size(goal) != size(b)`, you return 0. This should not happen anyway, but if you already check for this situation, you might want to return `infinity` instead [since you cannot get to the target with non-matching board sizes] – amit Oct 24 '11 at 13:21
  • It might be useful to compare a trace of your implementation on this specific instance to another implementation (e.g. http://code.google.com/p/a-star-algorithm-implementation/). Seeing where they diverge could help you find your bug. – Nate Kohl Oct 24 '11 at 13:37
  • I agree with amit, your function seems fine to me. The problem is probably somewhere else in your code. Try finding the smallest case that gives you unexpected results and debug that (i.e. compare expected intermediate results with actual ones). – svick Oct 24 '11 at 13:41
  • 1
    @amit I have checked those if blocks and the program never reaches those since cost of moving from one state to another state is always 1 (as opposed to say, length of roads between cities) so it never needs to re-open a closed node because the further you go the cost will increase by 1 each step so it is impossible to find a node that you have seen before that costs less than the current move. – Babak Oct 24 '11 at 13:55
  • @ThomasK: This is not true, look at [this example](http://shrib.com/WNETuCxj). Consider a branch where `h=1` for all nodes leading to a vertex `v`, with 50 nodes [still admissible!] and a 2nd branch, with one node, with `h=100`, also leading to `v`, which leads to a solution within 200 steps. all edges have same cost [1]. without reopening `v` when encountering it, A* will fail. [this is of course much more simplified example, but the principle can still apply for this case as well]. – amit Oct 24 '11 at 14:17
  • After quickly looking at your A* code (`AStar.java`), it appears you're using the `f` function (distance plus cost) to determine if you found a better path to a given node, while you should be using the `g` function (path cost) for that. Try changing that, and see if you get better results. – Sander De Dycker Oct 24 '11 at 14:30
  • Case in point (and I think @amit is on the same track) : For the 27 step solution, if you perform the first step, and then solve it again, you find a 26 step solution, BUT the first step in that solution, is to undo the previous step. So, the first two steps cancel each other out, leaving a 25 step solution. – Sander De Dycker Oct 24 '11 at 14:48
  • @SanderDeDycker sorry what AStar.java code? My solution is in C and not posted above. You raise a good point however I'm using the g function as well. I'm not really following the solution steps cancelling out each other. If we start from the exact same starting position and if we can reach the goal in 25 steps any admissible a-star algorithm that finds the goal in more steps is not working correctly. Am I right? – Babak Oct 24 '11 at 16:17
  • @amit I can't really understand your example. Could you please post a simple ms paint diagram? when you reopen nodes you only consider the g cost of the node (since h doesn't change) that means if you come across a node that you have seen before (since it is in closed) you must have seen it before now. That means its g cost is less than the cost of moving from your current position to that node since you have moved forward. – Babak Oct 24 '11 at 16:23
  • @ThomasK: note that for 2 vertices v1,v2 such that v1->v2, g(v1) can be higher then g(v2). so, you might have developed v2, from a different branch, before developing v1, but once you develop v1, you see that the path to v2 through v1 is better then the old one. Do you want me to post that as an answer? – amit Oct 24 '11 at 16:31
  • @amit if you prefer, please look at [this code](http://pastebin.com/BCjGLMtK). – Babak Oct 24 '11 at 16:50
  • @Thomas K : the code available on the website you linked to (http://www.brian-borowski.com/Software/Puzzle/), which I assumed was your code, since it is having the issue you're describing. Apparently that's not true, but then I'm confused about why it's having the same issue ? – Sander De Dycker Oct 24 '11 at 16:51
  • In any case, it will likely help if you post your A* code. Because everything points in that direction. – Sander De Dycker Oct 24 '11 at 16:55
  • One way of debugging such problems, where you know the right answer, is to add tests and printfs to the code so that you can find out where the program first drops the right answer (or rather an ancestor of the right answer) from the set of answers it is following - or rather, in the case of A* - why the path of nodes leading up to the right answer had not been fully examined at the time the program settled on the wrong answer. – mcdowella Oct 24 '11 at 18:24
  • @SanderDeDycker ah sorry about the confusion that's not my code that's just a sample and I think that one is working correctly since it finds the path in 27 steps only when used with manhattan+linear conflict (it's probably inadmissible) but when used with manhattan distance alone (as in my solution) it finds in 25 steps where my code finds in 27 steps. – Babak Oct 24 '11 at 19:11
  • @mcdowella It's making me nuts really. It's very hard to debug since the printed output is the result of examining hundreds of nodes and something could've gone anywhere in there. It's even more frustrating because it passes 25+ similar test cases before that error happens. – Babak Oct 24 '11 at 19:15
  • When I bury myself in output I don't understand I usually try and be more selective about what I print out. Here is a question with a short answer - when your A* program finishes, is there an unexpanded node that leads to the correct answer? If so, why is it unexpanded? What minimum length does your heuristic function assign to it? – mcdowella Oct 24 '11 at 19:49
  • The link to pastebin doesn't work anymore :( – EaterOfCode Apr 03 '13 at 20:44

1 Answers1

6

The problem seems to be not in your heuristic function, but in the algorithm itself. From your description of the problem, and the fact that it occures only on some specific cases, I believe it has to do with the re-opening of a closed vertice, once you find a better path to it.

While reading the code you have provided [in comments], I think I understood where the problem lays, in line 20:

if(getG(current) + 1 < getG(children[i])){

This is wrong! You are checking if g(current) + 1 < g(children[i]), you actually want to check for: f(current) + 1 + h(children[i]) < g(children[i]), since you want to check this value with the heuristic function of children[i], and not of current!
Note that it is identical as to set f(children[i]) = min{f(children[i]),f(current)+1}, and then adding h(children[i]) to get the g value.

amit
  • 175,853
  • 27
  • 231
  • 333
  • 1
    Thanks for examining that code amit. I'm not following your logic here at all. f(current) = g(current) + h(current) why would you add both h(current) and h(children[i]) together and expect it to be less than g(children[i])? as you know each "h" value represents the approximate cost from that node to the goal. Also look at the algorithm in [wikipedia page](http://en.wikipedia.org/wiki/A*_search_algorithm) and that portion belongs to "tentative_g_score" in that code which is equal to g_score[x] + dist_between(x,y) I don't understand what "h" is doing here? – Babak Oct 24 '11 at 19:06