-
Notifications
You must be signed in to change notification settings - Fork 0
/
heuristics.py
127 lines (106 loc) · 3.79 KB
/
heuristics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from pocket_cube.cube import Cube
from pocket_cube.cube import Move
from utils import get_neighbors
from tests import test_list
import numpy as np
from typing import Callable
# neighbours of a certain square considering rotations as moves
square_neighbours = [[1,3,4,5], [0,2,4,5], [1,4,3,5], [0,2,4,5], [0,1,2,3], [0,1,2,3]]
def is_admissible(astar: Callable[[Cube], tuple[list[Move], int]],
bfs: Callable[[Cube], tuple[list[Move], int]],
heuristic: Callable[[Cube], int]) -> bool:
for _ in range(0, 100):
for test in test_list:
cube = Cube(test)
(path_astar, _) = astar(cube, heuristic)
(path_bfs, _) = bfs(cube)
if len(path_astar) < len(path_bfs):
return False
return True
def hamming(cube: Cube) -> int:
"""
Returns the number of pieces that are not in the correct position.
Not admisible.
Args:
cube (Cube): The cube to evaluate.
Returns:
int: The number of pieces that are not in the correct position.
"""
return np.sum(cube.state != cube.goal_state)
def blocked_hamming(cube: Cube) -> int:
"""
Returns the number of faces that are not in the correct position, multiplied by 4.
Args:
cube (Cube): The cube to evaluate.
Returns:
int: The number of faces that are not in the correct position, multiplied by 4.
"""
res: int = 0
for i in range(6):
for j in range(4):
if cube.state[i * 4 + j] != cube.goal_state[i * 4 + j]:
res += 1
break
return res * 4
def __distance_to_correct_face(cube: Cube, square: int):
"""
Returns the distance from a square to the correct face.
Args:
cube (Cube): The cube to evaluate.
square (int): The square to evaluate.
Returns:
int: The distance from a square to the correct face.
"""
color: int = cube.state[square]
face: int = square // 4
if color == face:
return 0
elif color in square_neighbours[face]:
return 1
else:
return 2
def manhattan(cube: Cube) -> int:
"""
Returns the sum of the distances from each square to the correct face.
Args:
cube (Cube): The cube to evaluate.
Returns:
int: The sum of the distances from each square to the correct face.
"""
max_distance: int = 0
for face in range(6):
for i in range(4):
max_distance += __distance_to_correct_face(cube, face * 4 + i)
return max_distance / 8
def build_database(max_depth: int = 7) -> dict[str, int]:
"""
Builds a database of the distance to the solved state for each state with a depth lower than max_depth.
Args:
max_depth (int, optional): The maximum depth to search. Defaults to 7.
Returns:
dict[str, int]: The database.
"""
database: dict[str, int] = {}
cube = Cube()
cube.state = cube.goal_state
frontier: list[tuple[Cube, int]] = [(cube, 0)]
while frontier:
(cube, depth) = frontier.pop()
if cube.hash() not in database or database[cube.hash()] > depth:
database[cube.hash()] = depth
if depth < max_depth:
for (neighbor, _) in get_neighbors(cube):
frontier.append((neighbor, depth + 1))
return database
def database_heuristic(cube: Cube, database: dict[str, int], default_heuristic: Callable[[Cube], int]) -> int:
"""
Returns a heuristic function that uses the databse heuristic if the entry exists, and the default heuristic otherwise.
Args:
default_heuristic (Callable[[Cube], int]): The default heuristic.
Returns:
int: The heuristic value.
"""
if cube.hash() in database:
return database[cube.hash()]
else:
return default_heuristic(cube)