Skip to content

Commit

Permalink
Ch 4 - Add simulated annealing diagram
Browse files Browse the repository at this point in the history
  • Loading branch information
Rishav159 authored and redblobgames committed Jun 11, 2017
1 parent 9ddc4bb commit f44dc39
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 137 deletions.
20 changes: 1 addition & 19 deletions 4-Beyond-Classical-Search/c_hillClimbing.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
var colors = {
hill: 'hsl(217,30%,70%)',
maxima: 'hsl(110,100%,75%)',
unvisitedMaxima: 'hsl(110,100%,50%)',
GMRA: 'hsl(110,25%,60%)'
};

class HillDiagram {
constructor(hill, svg, h, w) {
this.padding = 20;
Expand Down Expand Up @@ -265,7 +258,7 @@ $(document).ready(function() {
svgRects.transition()
.duration(200)
.style('opacity', 1)
.style('class', (d) => {
.attr('class', (d) => {
if (d.maxima) {
return 'hill-maxima';
} else {
Expand All @@ -275,17 +268,6 @@ $(document).ready(function() {
return 'hill';
}
}
})
.style('fill', (d) => {
if (d.maxima) {
return colors.maxima;
} else {
if (d.isGMRA) {
return colors.GMRA;
} else {
return colors.hill;
}
}
});
}

Expand Down
197 changes: 117 additions & 80 deletions 4-Beyond-Classical-Search/c_simulatedAnnealing.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,119 @@
$(document).ready(function(){
$.ajax({
url : "simulatedAnnealing.js",
dataType: "text",
success : function (data) {
$("#simulatedAnnealingCode").html(data);
}
});

var annealingCanvas;
var text,line,background;
var w,h;
var two;
var sa;

var x = 0; // Current Index
var f; // The objective function

var DELAY = 1 * 60;
var POINTS = 30; // Number of points of the objective function
var INITIAL_TEMP = 50;
var K = 1; // Boltzmann constant

function init(){
annealingCanvas = document.getElementById("annealingCanvas");
annealingCanvas.addEventListener("click", handleClick, false);
w = annealingCanvas.offsetWidth;
h = 300;
two = new Two({ width: w, height: h }).appendTo(annealingCanvas);
sa = new SimulatedAnnealing(x,K,INITIAL_TEMP);
text = two.makeText("Temperature: "+INITIAL_TEMP,w/2 ,10,'normal');
line = two.makeLine(x,0,x,h);
line.stroke = 'orangered';
line.linewidth = 5;
setupScene();
}

init();

two.bind('update', function(frameCount){
if(frameCount % DELAY == 0){
x = sa.anneal(f);
// Translate the point according to the new chosen point
line.translation.set(x*w/POINTS,h/2);
y = Math.round(f[x]*100)/100;
text.value = "Temperature: "+sa.T + " ("+x+" , "+y+")";
}
}).play();

function handleClick(){
// When ever the canvas is clicked
// recalculate and redraw the background
// and reinitialize the sa object
setupScene();
x = 0;
sa = new SimulatedAnnealing(x,K,INITIAL_TEMP);
}

function setupScene(){
// If background already exists,
// then clear it
if(background != null)
two.remove(background);
f = new Array(POINTS);
background = new Array(POINTS-1);
f[0] = 0;
for(var i = 1; i < f.length; i++){
// f[i] ranges between 0 and 3*h/4
f[i] = Math.random() * 3*h/4;
sx = (i-1) * w/POINTS;
sy = h - f[i-1];
fx = i * w/POINTS;
fy = h - f[i];
// Draw lines connecting all f[i]
background[i-1] = two.makeLine(sx,sy,fx,fy);
background[i-1].linewidth = 2;
background[i-1].stroke = '#090A3B';
}
two.update();
}
$(document).ready(function() {

//Wrapper class for the diagram
class SimulatedAnnealingDiagram extends HillWorld {
constructor(selector, h, w, sliderSelector) {
super(selector, h, w);
this.sliderElement = $(sliderSelector);
}

init(delay, k) {
this.delay = delay;
this.initial = this.hillClimber.getCurrentState();
this.simulatedAnnealing = new SimulatedAnnealing(this.hill, this.initial, k);
this.colorScale = d3.scaleLinear().domain([0, 50]).range([70, 30]).clamp(true);
this.showAllStates();
this.bindListeners();
this.paintGlobalMaxima();
this.showTemperature(100);
this.sliderElement.val(100);
this.startAnnealing();
}

showAllStates() {
this.hillDiagram.svgRects.transition()
.duration(200)
.style('opacity', 1);
}

paintGlobalMaxima() {
this.hillDiagram.svgRects.classed('hill-maxima', (d) => d.maxima);
}

teleportRobot(currentState) {
let robotLocation = currentState;
let robotStateValue = this.hill.getStates()[currentState];
this.hillClimberDiagram.robot
.attr('x', this.hillClimberDiagram.xScale(robotLocation) - this.hillClimberDiagram.xOffset)
.attr('y', this.h - this.hillClimberDiagram.yScale(robotStateValue) - this.hillClimberDiagram.yOffset);
}

updateAnnealRegion(currentState) {
this.hillDiagram.svgRects.classed('hill-anneal-current-state', d => d.state == currentState);
}

showTemperature(t) {
this.svgTemp = this.hillDiagram.svg.append('text')
.attr('x', this.w - 180)
.attr('y', this.h - 470)
//Displaying temperature with 2 digit precision
.text(`Temperature : ${Math.round(t*100)/100}`);
}

updateTemperature(t) {
this.svgTemp.text(`Temperature : ${Math.round(t*100)/100}`);
}

nextMove(temp) {
let nextNode = this.simulatedAnnealing.anneal(temp);
if (nextNode.temp <= 0) {
this.updateTemperature(0);
return false;
} else {
let nextState = nextNode.state;
this.hillClimber.changeState(nextState);
this.teleportRobot(nextState);
this.updateAnnealRegion(nextState);
return true;
}
}

startAnnealing() {
//Stop annealing if already running.
this.stopAnnealing();
this.annealIntervalFunction = setInterval(() => {
if (!this.nextMove(this.sliderElement.val())) {
this.stopAnnealing();
}
}, this.delay);
}

stopAnnealing() {
clearInterval(this.annealIntervalFunction, this.delay);
}

destroy() {
this.stopAnnealing();
}

bindListeners() {
this.sliderElement.mouseup(() => {
if (this.sliderElement.val() > 0) {
this.startAnnealing();
}
});
this.sliderElement.mousemove(() => {
this.updateTemperature(this.sliderElement.val())
})
}
}

var simulatedAnnealingDiagram;

function init() {
simulatedAnnealingDiagram = new SimulatedAnnealingDiagram('#annealingCanvas', 500, 1000, '#tempSelector');

let delay = 20;

let k = 0.3;
simulatedAnnealingDiagram.init(delay, k);
};

function restart() {
simulatedAnnealingDiagram.destroy();
init();
}

init();
$('#annealingRestart').click(restart);
});
36 changes: 26 additions & 10 deletions 4-Beyond-Classical-Search/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@

<script src="https://cdnjs.cloudflare.com/ajax/libs/two.js/0.6.0/two.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
<!-- <script type="text/javascript" src="./geneticAlgorithm.js"></script>
<script type="text/javascript" src="./simulatedAnnealing.js"></script> -->
<!-- <script type="text/javascript" src="./geneticAlgorithm.js"></script> -->
<script type="text/javascript" src="./simulatedAnnealing.js"></script>
<script type="text/javascript" src="./hillClimbing.js"></script>
<!-- <script type="text/javascript" src="./andOrGraphSearch.js"></script>
<script type="text/javascript" src="./lrtaAgent.js"></script>
<script type="text/javascript" src="./onlineDfsAgent.js"></script> -->

<!-- <script type="text/javascript" src="./c_geneticAlgorithm.js"></script>
<script type="text/javascript" src="./c_simulatedAnnealing.js"></script> -->
<!-- <script type="text/javascript" src="./c_geneticAlgorithm.js"></script> -->
<script type="text/javascript" src="./c_simulatedAnnealing.js"></script>
<script type="text/javascript" src="./c_hillClimbing.js"></script>
<!-- <script type="text/javascript" src="./c_andOrGraphSearch.js"></script>
<script type="text/javascript" src="./c_lrtaAgent.js"></script>
Expand All @@ -36,7 +36,7 @@ <h2>Optimization Problem</h2>
represented by x-axis and elevation(objective function value) represented by y-axis. The best state is hence the state with the highest objective value</p>
<p>The given diagram is a state-space representation of an objective function. You can click anywhere inside the box to reveal the elevation there. You are allowed <i>25 moves</i> to <b>find the highest peak</b> before the hill is revealed.</p>
<p><span class='inline-legend maxima'></span> represents found global maximas and <span class='inline-legend unvisitedmaxima'></span> represents unfound global maximas</p>
<p>Try to come up with a strategy that can be used for al kinds of hills.</p>
<p>Try to come up with a strategy that can be used for all kinds of hills.</p>
<div class='row'>
<div class="col-md-2">
<div class='btn btn-primary restart-button' id='hillClimbRestart'>Restart</div>
Expand All @@ -51,7 +51,7 @@ <h2 id='hillMoves'></h2>
<h2>Hill Climbing Search</h2>
<p>In hill climbing search, the current node is replaced by the best neighbor. In this case, the objective function is represented by elevation, neighbors of a state are the states to the left and right of it and the best neigbor is the neigbor state
with the highest elevation.</p>
<p>The <span class='inline-legend maxima'></span> represents global maximas and <span class='inline-legend grma'></span> represents the states from where the hill climbing search can reach a global maxima.</p>
<p>The <span class='inline-legend maxima'></span> represents global maximas and <span class='inline-legend gmra'></span> represents the states from where the hill climbing search can reach a global maxima.</p>
<p>Click on different states to start Hill Climbing Search from there and see which direction it prefers and when does it stop.</p>
<p>Use the restart button to try it on different kinds of hills.</p>
<div class='row'>
Expand All @@ -63,11 +63,27 @@ <h2>Hill Climbing Search</h2>
</div>

<h2>Simulated Annealing</h2>
<p>Click on the screen to restart the simulation.</p>
<p>The orange line represents the current position.</p>
<div class="canvas" id="annealingCanvas" height="300px"></div>
<p>Simulated Annealing is a combination of Hill Climbing and Random Walk to gain more efficiency and completeness. In this procedure, instead of always moving to the best neighbor, a random neighbor is chosen. If the new state has better objective
value than the current state, it is always chosen. If not, the algorithm accept the new state with a probability less than one. The probability of choosing a <b>bad state</b> depends on :
<ol>
<li>The difference in objective value of the new state and the current state</li>
<li>The temperature</li>
</ol>
The temperature T denotes how likely it is for a bad state to be accepted. The procedure starts with a high temperature and decreases it with time. The probability of accepting a bad move decreases exponentially with temperature.</p>
<p>The <span class='inline-legend maxima'></span> represents global maximas and <span class='inline-legend anneal-region'></span> represents current state.</p>
<p>Notice how the algorithm shakes violently between states when the temperature is high and then stabilizes to higher states as the temperature decreases.</p>
<p>Try to set the temperature from the slider to different values and notice how the algorithm behaves.</p>
<div class='row'>
<div class="col-md-2">
<div class='btn btn-primary restart-button' id='annealingRestart'>Restart</div>
</div>
<div class="col-md-8">
<b>Temperature</b>
<input style="margin-top:1.5%" type="range" id="tempSelector" name="tempInput" step="0.1" min="0" max="100" value="100">
</div>
</div>
<div id="annealingCanvas"></div>

<pre id="simulatedAnnealingCode"></pre>

<h2>Genetic Algorithm</h2>
<p>Little critters change the color of their fur to match the background to camouflage themselves from predators.</p>
Expand Down
11 changes: 10 additions & 1 deletion 4-Beyond-Classical-Search/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@
width: 10px;
height: 10px;
}
.anneal-region {
background-color: hsl(303, 100%, 50%);
}
.maxima {
background-color: hsl(110, 100%, 38%);
}
.unvisitedmaxima {
background-color: hsl(102, 100%, 56%);
}
.grma {
.gmra {
background-color: hsl(110, 100%, 60%);
}
.hill {
fill: hsl(217, 30%, 70%);
}
.hill-anneal-current-state {
fill: hsl(303, 100%, 50%);
}
.hill-maxima {
fill: hsl(110, 100%, 75%);
}
Expand All @@ -27,3 +33,6 @@
.hill-gmra {
fill: hsl(110, 25%, 60%);
}
.hill-maxima.hill-anneal-current-state {
fill: hsl(110, 100%, 85%);
}
56 changes: 29 additions & 27 deletions 4-Beyond-Classical-Search/simulatedAnnealing.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
var SimulatedAnnealing = function(x,k,T){
this.x = x; // Starting state
this.k = k; // Boltzmann constant
this.T = T; // Initial temperature
class SimulatedAnnealing {
constructor(hill, initial, k) {
this.states = hill.getStates();
this.initial = initial;
this.current = this.initial;
this.k = k;
}

this.anneal = function(f){
if(this.T == 0) return x;
var new_x = this.getRandomInt(0,f.length);
if(f[new_x] > f[x]){
// If the new chosen value is better
// then just move to new state
x = new_x;
} else {
// Calculate probability of transfer
var p = Math.exp((f[new_x] - f[x])/(k * T));
// If a randomly chosen value is within p
// then move to the new state
if(Math.random() < p)
x = new_x;
}
this.T--;
return x;
};

this.getRandomInt = function(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
};
};
anneal(temperature) {
let nextState = this.getRandomState();
let diff = this.states[nextState] - this.states[this.current];
if (diff > 0) {
this.current = nextState;
} else {
let p = Math.exp((diff) / parseInt(this.k * temperature));
if (Math.random() < p) {
this.current = nextState;
}
}
return {
state: this.current,
temp: temperature
};
}
getRandomState() {
let mini = 0;
let maxi = this.states.length;
return Math.floor(Math.random() * (maxi - mini + 1)) + mini;
}
}

0 comments on commit f44dc39

Please sign in to comment.