Professional Documents
Culture Documents
University of Science
Faculty of Information Technology
LAB 1:
N-Puzzle with A* and UCS
Course Introduction to AI
Class 22CLC02
Teacher Nguyễn Ngọc Thảo
Hồ Thị Thanh Tuyến
HCMC, 2024
Mục lục
1 Check list 2
2 Source Code 2
2.1 Library Usages: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2 Methods Description: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2.1 Class PuzzleState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2.2 A* with Inversion Distance Heuristic . . . . . . . . . . . . . . . . . . . . . . . 3
2.2.3 UCS - Uniform Cost Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 How to use: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4 References 11
VNUHCM-US-FIT 1 22127357
1 Check list
1. A* with Inversion Distance Heuristic Implementation.
2. UCS Implementation.
2 Source Code
2.1 Library Usages:
− streamlit: Used to make the GUI for the program, allowing users to interact with the appli-
cation through buttons, input fields, and text displays.
− numpy: Used for representing the puzzle states as arrays, performing operations on these ar-
rays. It records the start time before solving the puzzle and calculates the elapsed time after
solving it.
− tracemalloc: Used to measure the total memory a test used. It starts tracing memory allo-
cations before solving the puzzle and retrieves memory statistics afterward.
− queue: from this library, "Priority Queue"is imported and is used in the UCS and A* al-
gorithms for managing the frontier (states to be explored). It ensures that states with lower
costs are explored first, facilitating efficient pathfinding.
− Attributes:
• state: Represents the current configuration of the puzzle. It’s a 1D array that holds the
values of each space in the puzzle.
• parent: Represents the previous state from which the current state was derived. This
attribute helps trace back the solution path.
• operator: Indicates the action (move) that was applied to transition from the parent
state to the current state.
• depth: Represents the depth of the current state in the search tree. It indicates the
number of moves taken to reach this state from the initial state.
VNUHCM-US-FIT 2 22127357
• cost: Represents the cost associated with reaching the current state. In the context of
UCS, it represents the actual cost (number of moves). In A*, it represents the cumula-
tive cost of reaching the state.
• heuristic: Represents the heuristic value associated with the state. In A*, this at-
tribute stores the Inversion Distance heuristic value.
− Methods:
• __init__: Constructor method used for initializing the PuzzleState object with the pro-
vided attributes.
• __lt__: Comparison method used for comparing two PuzzleState objects based on their
combined cost and heuristic values. This method is crucial for prioritizing states in the
priority queue used by UCS and A* algorithms.
• is_goal: Method to check if the current state matches the goal state.
• create_PuzzleState: Static method used for creating a PuzzleState object. It provides
an alternative way to create PuzzleState objects without explicitly calling the construc-
tor.
− How It Works:
(a) When a PuzzleState is generated or expanded during the search process, it’s encapsu-
lated within a PuzzleState object.
(b) The attributes of the PuzzleState object store crucial information about the state, in-
cluding its configuration, parent state, action taken, depth, cost, and heuristic value (as
already mentioned above Attributes).
(c) The comparison method (__lt__) allows PuzzleState objects to be compared based
on their combined cost and heuristic values. This comparison is essential for prioritizing
states in the frontier during UCS and A* search.
(d) The PuzzleState objects are utilized by search algorithms (UCS and A*) to explore
the search space, trace the solution path, and estimate the cost of reaching the goal
state.
(e) By encapsulating states within PuzzleState objects, the codebase becomes modular
and easier to manage, facilitating the implementation of various search algorithms and
heuristic functions.
VNUHCM-US-FIT 3 22127357
def ALGORITHMS ( puzzle , goal ) :
path = []
frontier = PQ ()
explored = set ()
start = PuzzleState ( puzzle , None , None , 0 , 0 , ID ( puzzle ) ) # THIS IS
WHERE THE 2 ALGORITHMS DIFFER
frontier . put ( start ) # Add the initial state to the frontier
while not frontier . empty () :
state = frontier . get () # Get the state with the lowest cost
if state . is_goal ( goal ) :
while state . parent is not None :
path . append ( state . state )
state = state . parent
return path [:: -1] # Return the path in reverse order
explored . add ( tuple ( state . state ) ) # Add the state to the explored set
for neighbor in expand_state ( state ) : # Expand the state ( for A * , the
ID is updated in the expand_state function according to the action taken
)
if tuple ( neighbor . state ) not in explored :
frontier . put ( neighbor ) # Add the neighbor to the frontier ,
if it is not already in the explored set
return None
− How it works:
• It takes two parameters: puzzle (the initial state of the puzzle) and goal (the goal
state of the puzzle).
• It initializes an empty list path to store the path from the initial state to the goal state.
• It initializes a PriorityQueue frontier to store the states to be explored, where states
with lower costs are given higher priority.
• The empty set explored() is used to keep track of explored states.
• It creates a start state object representing the initial state of the puzzle.
• It puts the start state into the frontier.
• It then enters a loop that continues until the frontier is empty:
+ It gets the state with the lowest cost from the frontier.
+ If the current state is the goal state, it reconstructs the path from the goal state to
the initial state and returns it.
Otherwise, it adds the current state to the explored set and expands it to generate
its neighboring states.
+ It puts the unexplored neighboring states into the frontier.
+ If the loop terminates without finding a solution, it returns None.
− With the major divergence in format at the creation of the object start = PuzzleState(puzzle,
None, None, 0, 0, ID(puzzle)) as A* has the heuristic value attribute (calculated at first
using the ID() method) instead of just being 0 for UCS.
(a) ID(state): This function calculates the inversion distance heuristic for a given state of
the puzzle.
VNUHCM-US-FIT 4 22127357
• It takes a single parameter state, which represents the current state of the puzzle.
• It initializes a variable idx to keep track of the index of the current tile.
• It iterates over the elements of the state array to calculate the number of inversions
for horizontal moves and vertical moves:
+ For horizontal moves, it counts the number of inversions by comparing each tile
with the tiles to its left.
+ For vertical moves, it calculates the number of inversions based on the relative
positions of the tiles in the puzzle grid.
• It then returns the sum of the number of vertical moves and horizontal moves, which
represents the Inversion Distance Heuristic.
def ID ( state ) :
idx = 0 # Index of the current tile
# Calculate the number of inversions
for i in range ( len ( state ) ) :
if state [ i ] != 0:
for j in range ( i ) :
if state [ i ] < state [ j ]:
idx += 1
VNUHCM-US-FIT 5 22127357
tic value according to the change in the number of inversions caused by the action, pro-
viding a more accurate estimate of the remaining cost to reach the goal state.
• It takes two parameters: state (the current state of the puzzle) and action (the ac-
tion to be taken: Up, Down, Left, or Right).
• It calculates the initial heuristic value using the ID() function.
• It determines the position of the blank tile after the action is taken and compares it
with the positions of other tiles affected by the action to adjust the heuristic value
accordingly:
+ A variable count is used to keep track of how many inversions are affected by
the action.
+ Updates the count variable based on the comparisons made during action han-
dling.
+ If count is negative, it means there are more inversions after the action, so it
adjusts the heuristic value accordingly.
+ If count is positive or zero, it means there are fewer or the same number of in-
versions after the action, so it adjusts the heuristic value accordingly.
def IDfromMove ( state , action ) :
heuristic = ID ( state )
size = int ( np . sqrt ( len ( state ) ) ) - 1 # -1 because the index starts at
0
count = 0
if action == " Up " :
idx = size + 1
num = state [ size ] # The element that is moved
if num > state [ idx ]:
count += 1
else :
count -= 1
idx += size
if num > state [ idx ]:
count += 1
else :
count -= 1
idx += size
if num > state [ idx ]:
count += 1
else :
count -= 1
elif action == " Down " :
idx = size + 1
num = state [ size ]
if num < state [ idx ]:
count += 1
else :
count -= 1
idx -= size
if num < state [ idx ]:
count += 1
else :
count -= 1
idx -= size
if num < state [ idx ]:
VNUHCM-US-FIT 6 22127357
count += 1
else :
count -= 1
. . . . . . . . . . . . . . . . . . . . . . # For full , please refers to the source code
if count < 0:
heuristic += abs ( count ) /3 + 1
else :
heuristic -= abs ( count ) / 3
return heuristic
# How the heuristic is updated :
# Upon moving a tile , the number of inversions that are affected by
the move is calculated
# Using the lookup table , the number of inversions that are affected
by the move is calculated
# If the number of inversions that are affected by the move is
negative , the heuristic is increased by the number of inversions that
are affected by the move divided by 3 and 1
# If the number of inversions that are affected by the move is
positive , the heuristic is decreased by the number of inversions that
are affected by the move divided by 3
− Other than the above, there are also numerous methods (Ex: the actions on the blank space,
applying the actions by checking if it’s possible, etc) with expand_state(state) being the
methods that separate how the cost of states are chosen for UCS and A*.
• Select the size of the puzzle: Ranging from 3x3->6x6 - N=15->N=35 using the incremental
box +/-.
• Shuffle Puzzle: Shuffle the puzzle randomly using np.random.permutation() for test cases.
(NOTICE: there are no "unsolvable checker")
• Solve Puzzle: Solve the desired randomized puzzle. The resulting path is printed on screen
with a Step Counter (starting with the initial state as Step 0).
VNUHCM-US-FIT 7 22127357
3 Time and Space Measurement
Before we get into the full information board, let go over the test cases and thhe performance of
each algorithms
Test 1
Steps:
• A*: 21 Steps
• UCS: 21 Steps
− Test 2:
Test 2
Steps:
• A*: 11 Steps
• UCS: 11 Steps
− Test 3:
Test 3
Steps:
VNUHCM-US-FIT 8 22127357
• A*: 21 Steps
• UCS: 21 Steps
− Results:
Results recorded
9! = 362, 880
As can be seen with the results recorded, in three of the cases (or just the Average), A* over-
all performed better than UCS, this is due to the added benefit of Heuristic value.
− Test 1:
Test 1
VNUHCM-US-FIT 9 22127357
Steps:
• A*: 14 Steps
• UCS: INTRACTABLE
− Test 2:
Test 2
Steps:
• A*: 19 Steps
• UCS: INTRACTABLE
− Test 3:
Test3
Steps:
• A*: 26 Steps
• UCS: INTRACTABLE
− Results
VNUHCM-US-FIT 10 22127357
Results recorded
UCS having no information about the goal state or how close the current state is to the goal. It
will have to explore numerous states, effectively performing a brute-force search. Given the large
number of possible states and the lack of heuristic guidance, UCS might take an impractical amount
of time to find the solution.
4 References
− N-Puzzle:
− UCS:
+ Hossam-Elbahrawy - Search.py
− A*:
VNUHCM-US-FIT 11 22127357