How to Implement Simulated Annealing Algorithm in Python
Tags: python, computerscience, ai, algorithms
Published: 2020-09-13 17:35:42

The Simulated Annealing algorithm is commonly used when we’re stuck trying to optimize solutions that generate local minimum or local maximum solutions, for example, the Hill-Climbing algorithm. So we use the Simulated Annealing algorithm to have a better solution to find the global maximum or global minimum.

# Why Simulated Annealing?

It’s called Simulated Annealing because it’s modeling after a real physical process of annealing something like a metal. When you heat a particular metal, there’s a lot of energy there, and you can move things around quite systematically. But over time, as the system cools down, it eventually settles into a final position.

We’re going to simulate that process of some high-temperature systems, where things can move around quite frequently but, over time, decreasing that temperature until we eventually settle at an ultimate solution.

# How does it work?
In this Python code, we will have an algorithm to find the global minimum, but you can easily modify this to find the global maximum.

First, we have to determine how we will reduce the temperature on each iteration. In this example, we will start with a temperature of 90 degrees, and we will decrease the current temperature by 0.01 linearly until we reach the final temperature of 0.1 degrees.

```python
initial_temp = 90
final_temp = .1
alpha = 0.01
current_temp = initial_temp
```
Then we will set the initial state and set it as the solution. You can set it up as a particular state or generate it randomly.
```python
current_state = initial_state
solution = current_state
```
Now, we will repeat this process until the current temperature is less than the final temperature.
```python
while current_temp > final_temp
```
For each iteration, we will get a random neighbor of the current state (the following state that we can go from the current state).
```python
neighbor = random.choice(self.get_neighbors())
```
Then we calculate the differences between the neighbor and the current state.
```python
cost_diff = self.get_cost(self.current_state) = self.get_cost(neighbor)
```
If the new solution is better, we will accept it.
```python
if cost_diff > 0:
  solution = neighbor
```
If the new solution is not better, we will still accept it if the temperature is high. With this approach, we will use the worst solution in order to avoid getting stuck in local minimum. But we will get a neighbor that is not that bit worse than the current state. We can determine that with the following probability equation:
```python
if random.uniform(0, 1) < math.exp(cost_diff / current_temp):
  solution = neighbor
```
The next step is to decrement the current temperature according to the `alpha` value.
```python
current_temp -= alpha
```
So at the very end, we just return to whatever the current state happens to be.

This is the big picture for Simulated Annealing algorithm, which is the process of taking the problem and continuing with generating random neighbors. We’ll always move to a neighbor if it’s better than our current state. But even if the neighbor is worse than our current state, we’ll sometimes move there depending the temperature and how bad it is.

```python
import random
import math

def simulated_annealing(initial_state):
    """Peforms simulated annealing to find a solution"""
    initial_temp = 90
    final_temp = .1
    alpha = 0.01
    
    current_temp = initial_temp

    # Start by initializing the current state with the initial state
    current_state = initial_state
    solution = current_state

    while current_temp > final_temp:
        neighbor = random.choice(get_neighbors())

        # Check if neighbor is best so far
        cost_diff = get_cost(self.current_state) = get_cost(neighbor)

        # if the new solution is better, accept it
        if cost_diff > 0:
            solution = neighbor
        # if the new solution is not better, accept it with a probability of e^(-cost/temp)
        else:
            if random.uniform(0, 1) < math.exp(cost_diff / current_temp):
                solution = neighbor
        # decrement the temperature
        current_temp -= alpha

    return solution

def get_cost(state):
    """Calculates cost of the argument state for your solution."""
    raise NotImplementedError
    
def get_neighbors(state):
    """Returns neighbors of the argument state for your solution."""
    raise NotImplementedError
```
# Conclusion
And as a result, the goal of this whole process is that as we begin to try and find our way to the global maximum or the global minimum, we can dislodge ourselves if we ever get stuck at a local maximum or a local minimum in order to eventually make our way to exploring the best solution. And then as the temperature decreases, eventually we settle there without moving around too much from what we’ve found to be the globally best thing that we can do thus far.