1

I'm trying to solve the 15-Puzzle problem using IDA* algorithm and Manhattan heuristic. I already implemented the algorithm from the pseudocode in this Wikipedia page (link).

Here's my code so far :

def IDA(initial_state, goal_state):
    initial_node = Node(initial_state)
    goal_node = Node(goal_state)
    
    threshold = manhattan_heuristic(initial_state, goal_state)
    path = [initial_node]

    while 1:
        tmp = search(path, goal_state, 0, threshold)
        if tmp == True:
            return path, threshold
        elif tmp == float('inf'):
            return False
        else:
            threshold = tmp


def search(path, goal_state, g, threshold):
    node = path[-1]
    f = g + manhattan_heuristic(node.state, goal_state)

    if f > threshold:
        return f

    if np.array_equal(node.state, goal_state):
        return True

    minimum = float('inf')  
    for n in node.nextnodes():
        if n not in path:
            path.append(n)
            tmp = search(path, goal_state, g + 1, threshold)
            if tmp == True:
                return True
            if tmp < minimum:
                minimum = tmp
            path.pop()

    return minimum


def manhattan_heuristic(state1, state2):
    size = range(1, len(state1) ** 2)
    distances = [count_distance(num, state1, state2) for num in size]

    return sum(distances)


def count_distance(number, state1, state2):
    position1 = np.where(state1 == number)
    position2 = np.where(state2 == number)

    return manhattan_distance(position1, position2)


def manhattan_distance(a, b):
    return abs(b[0] - a[0]) + abs(b[1] - a[1])


class Node():
    def __init__(self, state):
        self.state = state

    def nextnodes(self):
        zero = np.where(self.state == 0)
        
        y,x = zero
        y = int(y)
        x = int(x)
        
        up = (y - 1, x) 
        down = (y + 1, x)
        right = (y, x + 1)
        left = (y, x - 1)

        arr = []
        for direction in (up, down, right, left):
            if len(self.state) - 1 >= direction[0] >= 0 and len(self.state) - 1 >= direction[1] >= 0:
                tmp = np.copy(self.state)
                tmp[direction[0], direction[1]], tmp[zero] = tmp[zero], tmp[direction[0], direction[1]]
                arr.append(Node(tmp))

        return arr

I'm testing this code with a 3x3 Puzzle and here's the infinite loop! Due to the recursion I have some trouble testing my code...

I think the error might be here : tmp = search(path, goal_state, g + 1, threshold) (in the search function). I'm adding only one to the g cost value. It should be correct though, because I can only move a tile 1 place away.

Here's how to call the IDA() function:

initial_state = np.array([8 7 3],[4 1 2],[0 5 6])
goal_state = np.array([1 2 3],[8 0 4],[7 6 5])
IDA(initial_state, goal_state)

Can someone help me on this ?

AcK
  • 2,063
  • 2
  • 20
  • 27
cuzureau
  • 330
  • 2
  • 17
  • Your sample input is for `8-puzzle` problem, right? – biqarboy Feb 19 '21 at 22:57
  • I get result with the threshold value `24`. It did not enter in any infinite loop, it is just taking time to converge! You can check the performance of different algorithms in solving `8-puzzle` from [here](https://github.com/biqar/puzzle-solver/blob/master/src/8-puzzle/README.md). – biqarboy Feb 19 '21 at 23:06

1 Answers1

1

There are couple of issues in your IDA* implementation. First, what is the purpose of the variable path? I found two purposes of path in your code:

  1. Use as a flag/map to keep the board-states that is already been visited.
  2. Use as a stack to manage recursion states.

But, it is not possible to do both of them by using a single data structure. So, the first modification that your code requires:

  • Fix-1: Pass current node as a parameter to the search method.
  • Fix-2: flag should be a data structure that can perform a not in query efficiently.

Now, fix-1 is easy as we can just pass the current visiting node as the parameter in the search method. For fix-2, we need to change the type of flag from list to set as:

  • list's average case complexity for x in s is: O(n)
  • set's
    • Average case complexity for x in s is: O(1)
    • Worst case complexity for x in s is: O(n)

You can check more details about performance for testing memberships: list vs sets for more details.

Now, to keep the Node information into a set, you need to implement __eq__ and __hash__ in your Node class. In the following, I have attached the modified code.

import timeit
import numpy as np

def IDA(initial_state, goal_state):
    initial_node = Node(initial_state)
    goal_node = Node(goal_state)
    
    threshold = manhattan_heuristic(initial_state, goal_state)
    
    #print("heuristic threshold: {}".format(threshold))
    
    loop_counter = 0

    while 1:
        path = set([initial_node])
        
        tmp = search(initial_node, goal_state, 0, threshold, path)
        
        #print("tmp: {}".format(tmp))
        if tmp == True:
            return True, threshold
        elif tmp == float('inf'):
            return False, float('inf')
        else:
            threshold = tmp


def search(node, goal_state, g, threshold, path):
    #print("node-state: {}".format(node.state))
    f = g + manhattan_heuristic(node.state, goal_state)

    if f > threshold:
        return f

    if np.array_equal(node.state, goal_state):
        return True

    minimum = float('inf')  
    for n in node.nextnodes():
        if n not in path:
            path.add(n)
            tmp = search(n, goal_state, g + 1, threshold, path)
            if tmp == True:
                return True
            if tmp < minimum:
                minimum = tmp

    return minimum


def manhattan_heuristic(state1, state2):
    size = range(1, len(state1) ** 2)
    distances = [count_distance(num, state1, state2) for num in size]

    return sum(distances)


def count_distance(number, state1, state2):
    position1 = np.where(state1 == number)
    position2 = np.where(state2 == number)

    return manhattan_distance(position1, position2)


def manhattan_distance(a, b):
    return abs(b[0] - a[0]) + abs(b[1] - a[1])


class Node():
    def __init__(self, state):
        self.state = state
    
    def __repr__(self):
        return np.array_str(self.state.flatten())

    def __hash__(self):
        return hash(self.__repr__())
        
    def __eq__(self, other):
        return self.__hash__() == other.__hash__()

    def nextnodes(self):
        zero = np.where(self.state == 0)
        
        y,x = zero
        y = int(y)
        x = int(x)
        
        up = (y - 1, x) 
        down = (y + 1, x)
        right = (y, x + 1)
        left = (y, x - 1)

        arr = []
        for direction in (up, down, right, left):
            if len(self.state) - 1 >= direction[0] >= 0 and len(self.state) - 1 >= direction[1] >= 0:
                tmp = np.copy(self.state)
                tmp[direction[0], direction[1]], tmp[zero] = tmp[zero], tmp[direction[0], direction[1]]
                arr.append(Node(tmp))

        return arr


initial_state = np.array([[8, 7, 3],[4, 1, 2],[0, 5, 6]])
goal_state = np.array([[1, 2, 3],[8, 0, 4],[7, 6, 5]])

start = timeit.default_timer()
is_found, th = IDA(initial_state, goal_state)
stop = timeit.default_timer()

print('Time: {} seconds'.format(stop - start))

if is_found is True:
    print("Solution found with heuristic-upperbound: {}".format(th))
else:
    print("Solution not found!")

Node: Please double check your Node.nextnodes() and manhattan_heuristic() methods as I did not pay much attention in those areas. You can check this GitHub repository for other algorithmic implementations (i.e., A*, IDS, DLS) to solve this problem.

References:

  1. Python Wiki: Time Complexity
  2. TechnoBeans: Performance for testing memberships: list vs tuples vs sets
  3. GitHub: Puzzle Solver (by using problem solving techniques)
biqarboy
  • 852
  • 6
  • 15
  • Thanks a lot for your answer biqrboy and sorry for the delay! I tried using a set() but it didn't speed up significatively my algorithm. I check my `nextnodes()` as you suggested, and you were right : I could optimize here. But still, no significative speed up. Since I modified a lot of the code I posted a new question here : https://codereview.stackexchange.com/questions/257267/how-can-i-speed-up-my-ida-algorithm-for-n-puzzle – cuzureau Mar 16 '21 at 20:52