bluends
bluends

Reputation: 195

Dont know how to fix A* pathfinding algorithm

I'm trying the A* outline from GeeksforGeeks. I followed most of the steps in the grey box until I hit a roadblock on dii and diii. Here is the section of the pathfinding:

def pathfind(grid):
    sx, sy = 0, 0
    # find start point and end point cood
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            if grid[y][x] == "S":
                sx = x
                sy = y
            elif grid[y][x] == "E":
                ex = x
                ey = y

    opensq = []
    closedsq = []
    successor = []

    #add starting point to closed
    opensq.append([sx, sy, gcost(sx, sy, sx, sy), hcost(sx, sy, ex, ey)])
    grid[sy][sx] = "O"

    while opensq:
        # find the node with lowest fcost
        q = opensq[0]
        if len(opensq) == 1:
            pass
        else:
            for sq in range(len(opensq)):
                if sq == len(opensq) - 1:
                    pass
                else:
                    if q[2] + q[3] < opensq[sq + 1][2] + opensq[sq + 1][3]:
                        pass
                    elif q[2] + q[3] == opensq[sq + 1][2] + opensq[sq + 1][3]:
                        # if f is same, check hcost
                        if q[3] < opensq[sq + 1][3]:
                            pass
                        elif q[3] == opensq[sq + 1][3]:
                            pass
                        else:
                            q = opensq[sq + 1]
                    else:
                        q = opensq[sq + 1]
        opensq.pop(opensq.index(q))
        # pick successors to q
        successors = []
        successors.append([q[0] + 1, q[1], gcost(q[0] + 1, q[1], sx, sy), hcost(q[0] + 1, q[1], ex, ey)])
        successors.append([q[0] - 1, q[1], gcost(q[0] - 1, q[1], sx, sy), hcost(q[0] - 1, q[1], ex, ey)])
        successors.append([q[0], q[1] + 1, gcost(q[0], q[1] + 1, sx, sy), hcost(q[0], q[1] + 1, ex, ey)])
        successors.append([q[0], q[1] - 1, gcost(q[0], q[1] - 1, sx, sy), hcost(q[0], q[1] - 1, ex, ey)])
        for s in successors:
        #     if successor is the goal, stop search
            if s[0] == ex and s[1] == ey:
                pass
#            if a node with the same position as
#            successor is in the OPEN list which has a
#            lower f than successor, skip this successor
            for sq in opensq:
                if sq[2] + sq[3] < s[2] + s[3]:
                    successors.pop(successors.index(s))
            # if a node with the same position as
            # successor  is in the CLOSED list which has
            # a lower f than successor, skip this successor
            # otherwise, add  the node to the open list
            for sq in closedsq:
                if sq[2] + sq[3] < s[2] + s[3]:
                    successors.pop(successors.index(s))
        for s in successors:
            opensq.append(s)
            grid[s[1]][s[0]] = "O"
        closedsq.append(q)
        grid[q[1]][q[0]] = "C"

sx and sy being start point and ex ey being endpoint. I used letters to determine if a node is open or closed or start or end, with their first letter. This error pops up when I run it:

Traceback (most recent call last):
  File "D:/Bruh/Desktop/Codes and Scripts/AI/A_Pathfinding/Code.py", line 287, in <module>
    main()
  File "D:/Bruh/Desktop/Codes and Scripts/AI/A_Pathfinding/Code.py", line 274, in main
    pathfind(grid)
  File "D:/Bruh/Desktop/Codes and Scripts/AI/A_Pathfinding/Code.py", line 98, in pathfind
    successors.pop(successors.index(s))
ValueError: [5, 12, 3, 14] is not in list

here is the whole script, I used pygame for the visualization but just focus on the pathfind method, it currently kinda works but 1. the closed nodes become open nodes again after each loop for some reason and 2. it will get stuck in a loop if I skip the successor when it faces a wall. https://pastebin.com/yZfcPkq8

edit: finally finished the code! I'll put it up there, the only problem being that..I dont know hwo to show the path.. https://pastebin.com/EYSxFzpe

Upvotes: 0

Views: 230

Answers (1)

Gorisanson
Gorisanson

Reputation: 2312

The error occurs in the following part:

for s in successors:
    if s[0] == ex and s[1] == ey:
        pass

    for sq in opensq:
        if sq[2] + sq[3] < s[2] + s[3]:
            successors.pop(successors.index(s))  # line 1

    for sq in closedsq:
        if sq[2] + sq[3] < s[2] + s[3]:
            successors.pop(successors.index(s))  # line 2

On "line 1" and "line 2", successors.index(s) can be called after the s is already popped in the previous cycle in the for loop. Then, the error occurs.

And, more significantly, your code does not perform A* search algorithm properly. You should only check "a node with the same position as successor" as you commented on your code. You can try the following code instead of the part mentioned above to fix the problems.

# Iterate over a copy of the list,
# since we are popping elements in the original list.
for s in list(successors):
    if s[0] == ex and s[1] == ey:
        pass

    for sq in opensq + closedsq:
        if sq[0] == s[0] and sq[1] == s[1] and sq[2] + sq[3] <= s[2] + s[3]:
            successors.pop(successors.index(s))
            break

And still, the code above is not that neat. You can see that sq[3] == s[3] always holds on the if statement above since they are hcost for the same position. So, you can just compare sq[2] to s[2], i.e. gcost. (In other words, since sq[3] == s[3] holds, you can just do sq[2] <= s[2] instead of sq[2] + sq[3] <= s[2] + s[3] on the if statement above.) I think the A* search algorithm described in the grey box on GeeksforGeeks is not that neat.


gcost is "the movement cost to move from the starting point to a given square on the grid, following the path generated to get there." So you should fix your code:

  • Remove gcost function

  • Fix the lines gcost used to

opensq.append([sx, sy, 0, hcost(sx, sy, ex, ey)])
successors.append([q[0] + 1, q[1], q[2] + 1, hcost(q[0] + 1, q[1], ex, ey)])
successors.append([q[0] - 1, q[1], q[2] + 1, hcost(q[0] - 1, q[1], ex, ey)])
successors.append([q[0], q[1] + 1, q[2] + 1, hcost(q[0], q[1] + 1, ex, ey)])
successors.append([q[0], q[1] - 1, q[2] + 1, hcost(q[0], q[1] - 1, ex, ey)])

With the fixes above, it seems your code works. But I think you can still improve your code. One thing we can imporve is separating the node and the gcost of the node. Now opensq in your code is saving the node coordinate and gcost together, and a node can be added to opensq more than once if the calculated gcost's are different.

You can also refer A* pseudocode on Wikipedia. I think it is more neat and clean than on GeeksForGeeks which you already referred.

For the closed list, you can refer the "Remark" after the pseudocode on Wikipedia:

Remark: In this pseudocode, if a node is reached by one path, removed from openSet, and subsequently reached by a cheaper path, it will be added to openSet again. This is essential to guarantee that the path returned is optimal if the heuristic function is admissible but not consistent. If the heuristic is consistent, when a node is removed from openSet the path to it is guaranteed to be optimal so the test ‘tentative_gScore < gScore[neighbor]’ will always fail if the node is reached again.

The closed list on GeeksForGeeks is really closed only if the heuristic function is consistent.

Upvotes: 2

Related Questions