Skip to content

Commit bab84cd

Browse files
committed
fixes
1 parent d6f6b6a commit bab84cd

1 file changed

Lines changed: 78 additions & 72 deletions

File tree

src/num_methods/simulated_annealing.md

Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,115 +9,116 @@ tags:
99

1010
## The problem
1111

12-
We are given a function $E(s)$, which calculates the potential of the state $s$. We are tasked with finding the state $s_{best}$ at which $E(s)$ is minimized. **SA** is suited for problems where the states are discrete and $E(s)$ has multiple local minima. We'll take the example of the [Travelling Salesman Problem (TSP)](https://en.wikipedia.org/wiki/Travelling_salesman_problem).
12+
We are given a function $E(s)$, which calculates the energy of the state $s$. We are tasked with finding the state $s_{best}$ at which $E(s)$ is minimized. **SA** is suited for problems where the states are discrete and $E(s)$ has multiple local minima. We'll take the example of the [Travelling Salesman Problem (TSP)](https://en.wikipedia.org/wiki/Travelling_salesman_problem).
1313

1414
### Travelling Salesman Problem (TSP)
1515

16-
You are given a set of nodes in 2 dimensional space. Each node is characterised by its $x$ and $y$ coordinates. Your task is to find the an ordering of the nodes, which will minimise the distance to be travelled when visiting these nodes in that order.
16+
You are given a set of nodes in 2 dimensional space. Each node is characterised by its $x$ and $y$ coordinates. Your task is to find an ordering of the nodes, which will minimise the distance to be travelled when visiting these nodes in that order.
1717

18-
### State
19-
20-
State space is the collection of all possible values that can be taken by the independent variables.
21-
A state is a unique point in the state space of the problem. In the case of TSP, all possible paths that we can take to visit all the nodes is the state space, and any single one of these paths can be considered as a state.
22-
23-
### Neighbouring state
18+
## Motivation
19+
Annealing is a metallurgical process , wherein a material is heated up and allowed to cool, in order to allow the atoms inside to rearrange themselves in an arrangement with minimal internal energy, which in turn causes the material to have different properties. The state is the arrangement of atoms and the internal energy is the function being minimised. We can think of the original state of the atoms, as a local minima for its internal energy. To make the material rearrange its atoms, we need to motivate it to go across a region where its internal energy is not minimised in order to reach the global minima. This motivation is given by heating the material to a higher temperature.
2420

25-
It is a state in the state space which is close to the previous state. This usually means that we can obtain the neighbouring state from the original state using a simple transform. In the case of the Travelling Salesman Problem, a neighbouring state is obtained by randomly choosing 2 nodes, and swapping their positions in the current state.
21+
Simulated annealing, literally, simulates this process. We start off with some random state (material) and set a high temperature (heat it up). Now, the algorithm is ready to accept states which have a higher energy than the current state, as it is motivated by the high temperature. This prevents the algorithm from getting stuck inside local minimas and move towards the global minima. As time progresses, the algorithm cools down and refuses the states with higher energy and moves into the closest minima it has found.
2622

2723
### The energy function E(s)
2824

2925
$E(s)$ is the function which needs to be minimised (or maximised). It maps every state to a real number. In the case of TSP, $E(s)$ returns the distance of travelling one full circle in the order of nodes in the state.
3026

31-
## The approach
27+
### State
28+
29+
The state space is the domain of the energy function, E(s), and a state is any element which belongs to the state space. In the case of TSP, all possible paths that we can take to visit all the nodes is the state space, and any single one of these paths can be considered as a state.
3230

33-
We start of with a random state $s$. In every step, we choose a neighbouring state $s_{next}$ of the current state $s$. If $E(s_{next}) < E(s)$, then we update $s = s_{next}$. Otherwise, we use a probability acceptance function $P(E(s),E(s_{next}),T)$ which decides whether we should move to $s_{next}$ or stay at $s$. T here is the temperature, which is initially set to a high value and decays slowly with every step. The higher the temperature, the more likely it is to move to $s_{next}$.
34-
At the same time we also keep a track of the best state $s_{best}$ across all iterations. Proceeding till convergence or time runs out.
31+
### Neighbouring state
3532

36-
### How does this work?
33+
It is a state in the state space which is close to the previous state. This usually means that we can obtain the neighbouring state from the original state using a simple transform. In the case of the Travelling Salesman Problem, a neighbouring state is obtained by randomly choosing 2 nodes, and swapping their positions in the current state.
3734

38-
This algorithm is called simulated annealing because we are simulating the process of annealing, wherein a material is heated up and allowed to cool, in order to allow the atoms inside to rearrange themselves in an arrangement with minimal internal energy, which in turn causes the material to have different properties. The state is the arrangement of atoms and the internal energy is the function being minimised. We can think of the original state of the atoms, as a local minima for its internal energy. To make the material rearrange its atoms, we need to motivate it to go across a region where its internal energy is not minimised in order to reach the global minima. This motivation is given by heating the material to a higher temperature.
35+
## Algorithm
3936

40-
Simulated annealing, literally simulates this process. We start off with some random state (material) and set a high temperature (heat it up). Now, the algorithm is ready to accept states which have a higher energy than the current state, as it is motivated by the high value of $T$. This prevents the algorithm from getting stuck inside local minimas and move towards the global minima. As time progresses, the algorithm cools down and refuses the states with higher energy and moves into the closest minima it has found.
37+
We start with a random state $s$. In every step, we choose a neighbouring state $s_{next}$ of the current state $s$. If $E(s_{next}) < E(s)$, then we update $s = s_{next}$. Otherwise, we use a probability acceptance function $P(E(s),E(s_{next}),T)$ which decides whether we should move to $s_{next}$ or stay at $s$. T here is the temperature, which is initially set to a high value and decays slowly with every step. The higher the temperature, the more likely it is to move to $s_{next}$.
38+
At the same time we also keep a track of the best state $s_{best}$ across all iterations. Proceeding till convergence or time runs out.
4139

4240

4341
<center>
4442
<img src="https://upload.wikimedia.org/wikipedia/commons/d/d5/Hill_Climbing_with_Simulated_Annealing.gif" width="800px">
4543
<br>
4644
<i>A visual representation of simulated annealing, searching for the maxima of this function with multiple local maxima.</i>.
4745
<br>
48-
<i>This <a href="https://upload.wikimedia.org/wikipedia/commons/d/d5/Hill_Climbing_with_Simulated_Annealing.gif">gif</a> by [Kingpin13](https://commons.wikimedia.org/wiki/User:Kingpin13) is distributed under <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.en">CC0 1.0</a></i> license.
49-
</center>
46+
<i>This <a href="https://commons.wikimedia.org/wiki/File:Hill_Climbing_with_Simulated_Annealing.gif">animation</a> by [Kingpin13](https://commons.wikimedia.org/wiki/User:Kingpin13) is distributed under <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.en">CC0 1.0</a></i> license.
47+
48+
### Temperature($T$) and decay($u$)
49+
50+
The temperature of the system quantifies the willingness of the algorithm to accept a state with a higher energy. The decay is a constant which quantifies the "cooling rate" of the algorithm. A slow cooling rate (larger $u$) is known to give better results.
5051

5152
## Probability Acceptance Function(PAF)
5253

5354
$P(E,E_{next},T) =
5455
\begin{cases}
55-
\text{True} &\quad\text{if } \exp{\frac{-(E_{next}-E)}{T}} \ge random(0,1) \\
56+
\text{True} &\quad\text{if } \mathcal{U}_{[0,1]} \le \exp(-\frac{E_{next}-E}{T}) \\
5657
\text{False} &\quad\text{otherwise}\\
5758
\end{cases}$
5859

59-
This function takes in the current state, the next state and the Temperature , returning a boolean value, which tells our search whether it should move to $s_{next}$ or stay at $s$. Note that for $E_{next} < E$ , this function will always return True.
60+
Here, $\mathcal{U}_{[0,1]}$ is a continuous uniform random value on $[0,1]$. This function takes in the current state, the next state and the temperature, returning a boolean value, which tells our search whether it should move to $s_{next}$ or stay at $s$. Note that for $E_{next} < E$ , this function will always return True, otherwise it can still make the move with probability $\exp(-\frac{E_{next}-E}{T})$, which corresponds to the [Gibbs measure](https://en.wikipedia.org/wiki/Gibbs_measure).
6061

6162
```cpp
62-
bool P(double E,double E_next,double T){
63-
double prob = (double)rand()/RAND_MAX; // Generate a random number between 0 and 1
64-
if(exp(-(E_next-E)/T) > prob) return true;
65-
else return false;
63+
bool P(double E,double E_next,double T,mt19937 rng){
64+
double prob = exp(-(E_next-E)/T);
65+
if(prob > 1) return true;
66+
else{
67+
bernoulli_distribution d(prob);
68+
return d(rng);
69+
}
6670
}
6771
```
6872
## Code Template
6973
7074
```cpp
71-
class state{
75+
class state {
7276
public:
73-
state(){
77+
state() {
7478
// Generate the initial state
7579
}
76-
state(state& s){
77-
// Implement the copy constructor
78-
}
79-
state next(){
80-
state s_next = state(*this);
81-
82-
// Modify s_next to the neighbouring state
83-
80+
state next() {
81+
state s_next;
82+
// Modify s_next to a random neighboring state
8483
return s_next;
8584
}
86-
double E(){
87-
// Implement the cost function here
85+
double E() {
86+
// Implement the energy function here
8887
};
8988
};
9089
91-
pair<double,state> simAnneal(){
90+
91+
pair<double, state> simAnneal() {
9292
state s = state();
93-
state best = state(s);
94-
double T = 1000; // Initial temperature
95-
double u = 0.99; // decay rate
93+
state best = s;
94+
double T = 10000; // Initial temperature
95+
double u = 0.995; // decay rate
9696
double E = s.E();
9797
double E_next;
9898
double E_best = E;
99-
while (T > 1){
99+
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
100+
while (T > 1) {
100101
state next = s.next();
101102
E_next = next.E();
102-
if(P(E,E_next,T)){
103+
if (P(E, E_next, T, rng)) {
103104
s = next;
104-
if(E_next < E_best){
105+
if (E_next < E_best) {
105106
best = s;
106107
E_best = E_next;
107108
}
108109
}
109110
E = E_next;
110111
T *= u;
111112
}
112-
return {E_best,best};
113+
return {E_best, best};
113114
}
114115
115116
```
116117
## How to use:
117-
Fill in the state class functions as appropriate. If you are trying to find a global maxima and not a minima, ensure that the $E()$ function returns negative of the function you are maximising and finally print out $-E_{best}$. Set the below parameters as per your need.
118+
Fill in the state class functions as appropriate. If you are trying to find a global maxima and not a minima, ensure that the $E()$ function returns negative of the function you are maximizing and print $-E_{best}$ in the end. Set the below parameters as per your need.
118119

119120
### Parameters
120-
- $T$ : Temperature. Set it to a higher value if you want the search to run for a longer time.
121+
- $T$ : Initial temperature. Set it to a higher value if you want the search to run for a longer time.
121122
- $u$ : Decay. Decides the rate of cooling. A slower cooling rate (larger value of u) usually gives better results, at the cost of running for a longer time. Ensure $u < 1$.
122123

123124
The number of iterations the loop will run for is given by the expression
@@ -131,47 +132,47 @@ $T = u^{-N}$
131132
### Example implementation for TSP
132133
```cpp
133134

134-
class state{
135+
class state {
135136
public:
136-
vector<pair<int,int>> points;
137-
138-
state(){
139-
{% raw %}points = {{0,0},{2,2},{0,2},{2,0},{0,1},{1,2},{2,1},{1,0}};{% endraw %}
140-
}
141-
state(state& s){
142-
points = s.points;
137+
vector<pair<int, int>> points;
138+
std::mt19937 mt{ static_cast<std::mt19937::result_type>(
139+
std::chrono::steady_clock::now().time_since_epoch().count()
140+
) };
141+
state() {
142+
points = {%raw%} {{0,0},{2,2},{0,2},{2,0},{0,1},{1,2},{2,1},{1,0}} {%endraw%};
143143
}
144-
state next(){
145-
state s_next = state(*this);
146-
int a = (rand()%points.size());
147-
int b = (rand()%points.size());
144+
state next() {
145+
state s_next;
146+
s_next.points = points;
147+
uniform_int_distribution<> choose(0, points.size()-1);
148+
int a = choose(mt);
149+
int b = choose(mt);
148150
s_next.points[a].swap(s_next.points[b]);
149151
return s_next;
150152
}
151153

152-
double euclidean(pair<int,int> a, pair<int,int> b){
153-
return pow(pow((a.first-b.first),2)+pow((a.second-b.second),2),0.5);
154+
double euclidean(pair<int, int> a, pair<int, int> b) {
155+
return hypot(a.first - b.first, a.second - b.second);
154156
}
155-
double E(){
157+
158+
double E() {
156159
double dist = 0;
157160
bool first = true;
158161
int n = points.size();
159-
for(int i = 0;i < n; i++){
160-
dist += euclidean(points[i],points[(i+1)%n]);
161-
}
162+
for (int i = 0;i < n; i++)
163+
dist += euclidean(points[i], points[(i+1)%n]);
162164
return dist;
163165
};
164166
};
165167

166-
167-
int main(){
168-
pair<int,state> res;
168+
int main() {
169+
pair<double, state> res;
169170
res = simAnneal();
170171
double E_best = res.first;
171172
state best = res.second;
172173
cout << "Lenght of shortest path found : " << E_best << "\n";
173174
cout << "Order of points in shortest path : \n";
174-
for(auto x: best.points){
175+
for(auto x: best.points) {
175176
cout << x.first << " " << x.second << "\n";
176177
}
177178
}
@@ -180,14 +181,19 @@ int main(){
180181
## Further modifications to the algorithm:
181182

182183
- Add a time based exit condition to the while loop to prevent TLE
184+
- The decay implemented above is an exponential decay. You can always replace this with a decay function as per your needs.
183185
- The Probability acceptance function given above, prefers accepting states which are lower in energy because of the $E_{next} - E$ factor in the numerator of the exponent. You can simply remove this factor, to make the PAF independent of the difference in energies.
184186
- The effect of the difference in energies, $E_{next} - E$, on the PAF can be increased/decreased by increasing/decreasing the base of the exponent as shown below:
185187
```cpp
186-
bool P(double E,double E_next,double T){
187-
double e = 2; // set e to any real number greater than 1
188-
double prob = (double)rand()/RAND_MAX; // Generate a random number between 0 and 1
189-
if(pow(e,-(E_next-E)/T) > prob) return true;
190-
else return false;
188+
bool P(double E, double E_next, double T, mt19937 rng) {
189+
e = 2 // set e to any real number greater than 1
190+
double prob = exp(-(E_next-E)/T);
191+
if (prob > 1)
192+
return true;
193+
else {
194+
bernoulli_distribution d(prob);
195+
return d(rng);
196+
}
191197
}
192198
```
193199

0 commit comments

Comments
 (0)