TheAlgorithms-Python
196 строк · 6.6 Кб
1# https://en.wikipedia.org/wiki/Hill_climbing
2import math
3
4
5class SearchProblem:
6"""
7An interface to define search problems.
8The interface will be illustrated using the example of mathematical function.
9"""
10
11def __init__(self, x: int, y: int, step_size: int, function_to_optimize):
12"""
13The constructor of the search problem.
14
15x: the x coordinate of the current search state.
16y: the y coordinate of the current search state.
17step_size: size of the step to take when looking for neighbors.
18function_to_optimize: a function to optimize having the signature f(x, y).
19"""
20self.x = x
21self.y = y
22self.step_size = step_size
23self.function = function_to_optimize
24
25def score(self) -> int:
26"""
27Returns the output of the function called with current x and y coordinates.
28>>> def test_function(x, y):
29... return x + y
30>>> SearchProblem(0, 0, 1, test_function).score() # 0 + 0 = 0
310
32>>> SearchProblem(5, 7, 1, test_function).score() # 5 + 7 = 12
3312
34"""
35return self.function(self.x, self.y)
36
37def get_neighbors(self):
38"""
39Returns a list of coordinates of neighbors adjacent to the current coordinates.
40
41Neighbors:
42| 0 | 1 | 2 |
43| 3 | _ | 4 |
44| 5 | 6 | 7 |
45"""
46step_size = self.step_size
47return [
48SearchProblem(x, y, step_size, self.function)
49for x, y in (
50(self.x - step_size, self.y - step_size),
51(self.x - step_size, self.y),
52(self.x - step_size, self.y + step_size),
53(self.x, self.y - step_size),
54(self.x, self.y + step_size),
55(self.x + step_size, self.y - step_size),
56(self.x + step_size, self.y),
57(self.x + step_size, self.y + step_size),
58)
59]
60
61def __hash__(self):
62"""
63hash the string representation of the current search state.
64"""
65return hash(str(self))
66
67def __eq__(self, obj):
68"""
69Check if the 2 objects are equal.
70"""
71if isinstance(obj, SearchProblem):
72return hash(str(self)) == hash(str(obj))
73return False
74
75def __str__(self):
76"""
77string representation of the current search state.
78>>> str(SearchProblem(0, 0, 1, None))
79'x: 0 y: 0'
80>>> str(SearchProblem(2, 5, 1, None))
81'x: 2 y: 5'
82"""
83return f"x: {self.x} y: {self.y}"
84
85
86def hill_climbing(
87search_prob,
88find_max: bool = True,
89max_x: float = math.inf,
90min_x: float = -math.inf,
91max_y: float = math.inf,
92min_y: float = -math.inf,
93visualization: bool = False,
94max_iter: int = 10000,
95) -> SearchProblem:
96"""
97Implementation of the hill climbling algorithm.
98We start with a given state, find all its neighbors,
99move towards the neighbor which provides the maximum (or minimum) change.
100We keep doing this until we are at a state where we do not have any
101neighbors which can improve the solution.
102Args:
103search_prob: The search state at the start.
104find_max: If True, the algorithm should find the maximum else the minimum.
105max_x, min_x, max_y, min_y: the maximum and minimum bounds of x and y.
106visualization: If True, a matplotlib graph is displayed.
107max_iter: number of times to run the iteration.
108Returns a search state having the maximum (or minimum) score.
109"""
110current_state = search_prob
111scores = [] # list to store the current score at each iteration
112iterations = 0
113solution_found = False
114visited = set()
115while not solution_found and iterations < max_iter:
116visited.add(current_state)
117iterations += 1
118current_score = current_state.score()
119scores.append(current_score)
120neighbors = current_state.get_neighbors()
121max_change = -math.inf
122min_change = math.inf
123next_state = None # to hold the next best neighbor
124for neighbor in neighbors:
125if neighbor in visited:
126continue # do not want to visit the same state again
127if (
128neighbor.x > max_x
129or neighbor.x < min_x
130or neighbor.y > max_y
131or neighbor.y < min_y
132):
133continue # neighbor outside our bounds
134change = neighbor.score() - current_score
135if find_max: # finding max
136# going to direction with greatest ascent
137if change > max_change and change > 0:
138max_change = change
139next_state = neighbor
140elif change < min_change and change < 0: # finding min
141# to direction with greatest descent
142min_change = change
143next_state = neighbor
144if next_state is not None:
145# we found at least one neighbor which improved the current state
146current_state = next_state
147else:
148# since we have no neighbor that improves the solution we stop the search
149solution_found = True
150
151if visualization:
152from matplotlib import pyplot as plt
153
154plt.plot(range(iterations), scores)
155plt.xlabel("Iterations")
156plt.ylabel("Function values")
157plt.show()
158
159return current_state
160
161
162if __name__ == "__main__":
163import doctest
164
165doctest.testmod()
166
167def test_f1(x, y):
168return (x**2) + (y**2)
169
170# starting the problem with initial coordinates (3, 4)
171prob = SearchProblem(x=3, y=4, step_size=1, function_to_optimize=test_f1)
172local_min = hill_climbing(prob, find_max=False)
173print(
174"The minimum score for f(x, y) = x^2 + y^2 found via hill climbing: "
175f"{local_min.score()}"
176)
177
178# starting the problem with initial coordinates (12, 47)
179prob = SearchProblem(x=12, y=47, step_size=1, function_to_optimize=test_f1)
180local_min = hill_climbing(
181prob, find_max=False, max_x=100, min_x=5, max_y=50, min_y=-5, visualization=True
182)
183print(
184"The minimum score for f(x, y) = x^2 + y^2 with the domain 100 > x > 5 "
185f"and 50 > y > - 5 found via hill climbing: {local_min.score()}"
186)
187
188def test_f2(x, y):
189return (3 * x**2) - (6 * y)
190
191prob = SearchProblem(x=3, y=4, step_size=1, function_to_optimize=test_f1)
192local_min = hill_climbing(prob, find_max=True)
193print(
194"The maximum score for f(x, y) = x^2 + y^2 found via hill climbing: "
195f"{local_min.score()}"
196)
197