diff --git a/4-Beyond-Classical-Search/c_hillClimbing.js b/4-Beyond-Classical-Search/c_hillClimbing.js index ed62733..43b4c6f 100644 --- a/4-Beyond-Classical-Search/c_hillClimbing.js +++ b/4-Beyond-Classical-Search/c_hillClimbing.js @@ -1,159 +1,177 @@ -$(document).ready(function() { +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; + this.hill = hill + this.h = h; + this.w = w; + this.svg = svg; + this.states = this.hill.getStates(); + this.hillData = []; + for (let i = 0; i < this.states.length; i++) { + this.hillData.push({ + state: i, + objective: this.states[i], + visited: false, + maxima: false + }); + } - //Class to draw the hills - class HillDiagram { - constructor(hill, svg, h, w) { - this.padding = 20; - this.hill = hill - this.h = h; - this.w = w; - this.svg = svg; - this.states = this.hill.getStates(); - this.hillData = []; - for (let i = 0; i < this.states.length; i++) { - this.hillData.push({ - state: i, - objective: this.states[i], - visited: false, - maxima: false - }) - } + let maximas = this.hill.getBestStates(); + for (let i = 0; i < maximas.length; i++) { + this.hillData[maximas[i]].maxima = true; + } - let maximas = this.hill.getBestStates(); - for (let i = 0; i < maximas.length; i++) { - this.hillData[maximas[i]].maxima = true; - } + this.xScale = d3.scaleLinear() + .domain([0, this.states.length]) + .range([this.padding, this.w - this.padding]); + this.blockWidth = (this.xScale(this.states.length) - this.xScale(0)) / this.states.length; + this.yScale = d3.scaleLinear() + .domain([0, d3.max(this.states)]) + .range([this.padding, this.h - this.padding]); + this.renderHill(); - this.xScale = d3.scaleLinear() - .domain([0, this.states.length]) - .range([this.padding, this.w - this.padding]); - this.blockWidth = (this.xScale(this.states.length) - this.xScale(0)) / this.states.length; - this.yScale = d3.scaleLinear() - .domain([0, d3.max(this.states)]) - .range([this.padding, this.h - this.padding]); - this.renderHill(); + } - } - renderHill() { - this.svgHill = this.svg.selectAll('.block') - .data(this.hillData) - .enter() - .append('g') - .attr('class', 'block'); - this.svgRects = this.svgHill - .append('rect') - .attr('height', d => this.yScale(d.objective)) - .attr('width', this.blockWidth) - .attr('x', (d) => this.xScale(d.state)) - .attr('y', (d) => this.h - this.yScale(d.objective)) - .style('opacity', (d) => (d.visited) ? 1 : 0) - .style('fill', 'hsl(217,61.2%,53.5%)') - .style('border', '1px solid'); - } + renderHill() { + this.svgHill = this.svg.selectAll('.block') + .data(this.hillData) + .enter() + .append('g') + .attr('class', 'block'); + this.svgRects = this.svgHill + .append('rect') + .attr('height', d => this.yScale(d.objective)) + .attr('width', this.blockWidth) + .attr('x', (d) => this.xScale(d.state)) + .attr('y', (d) => this.h - this.yScale(d.objective)) + .style('opacity', (d) => (d.visited) ? 1 : 0) + .classed('hill', true); + } - visit(state) { - this.hillData[state].visited = true; - this.svgRects.filter((d) => d.state == state) - .transition() - .duration(100) - .style('opacity', 1); - } + visit(state) { + this.hillData[state].visited = true; + this.svgRects.filter((d) => d.state == state) + .transition() + .duration(100) + .style('opacity', 1); + } - showAll() { - this.svgRects.transition() - .duration(100) - .style('opacity', (d) => { - return (d.visited) ? 1 : 0.6; - }) - .style('fill', (d) => { - if (d.maxima) { - if (d.visited) { - return 'hsl(110, 100%, 38%)'; - } else { - return 'hsl(102, 100%, 56%)'; - } + showAll() { + this.svgRects.transition() + .duration(100) + .style('opacity', (d) => { + return (d.visited) ? 1 : 0.6; + }) + .attr('class', (d) => { + if (d.maxima) { + if (d.visited) { + return 'hill-maxima'; } else { - return 'hsl(217,61.2%,53.5%)'; + return 'hill-unvisitedmaxima'; } - }) - } + } else { + return 'hill'; + } + }) } +} - //Class to draw the robot - class HillClimberDiagram { - constructor(hill, svg, h, w, hillDiagram, hillClimber) { - this.h = h; - this.w = w; - this.hill = hill; - this.hillDiagram = hillDiagram; - this.hillClimber = hillClimber; - this.states = this.hill.getStates(); - this.svg = svg; - this.xScale = this.hillDiagram.xScale; - this.yScale = this.hillDiagram.yScale; - this.blockWidth = this.hillDiagram.blockWidth; - this.botHeight = 50; - this.botWidth = this.blockWidth + 20; - this.xOffset = 10; - this.yOffset = 40; - this.renderHillClimber(); - } - renderHillClimber() { - let robotLocation = this.hillClimber.getCurrentState(); - let robotStateValue = this.states[robotLocation]; - this.robot = this.svg.append('g') - .attr('class', 'robot') - .append('svg:image') - .attr('xlink:href', '../third-party/robot.png') - .attr('height', this.botHeight) - .attr('width', this.botWidth) - .attr('x', this.xScale(robotLocation) - this.xOffset) - .attr('y', this.h - this.yScale(robotStateValue) - this.yOffset); - this.hillDiagram.visit(robotLocation); +//Class to draw the robot +class HillClimberDiagram { + constructor(hill, svg, h, w, hillDiagram, hillClimber) { + this.h = h; + this.w = w; + this.hill = hill; + this.hillDiagram = hillDiagram; + this.hillClimber = hillClimber; + this.states = this.hill.getStates(); + this.svg = svg; + this.xScale = this.hillDiagram.xScale; + this.yScale = this.hillDiagram.yScale; + this.blockWidth = this.hillDiagram.blockWidth; + this.botHeight = 50; + this.botWidth = this.blockWidth + 20; + this.xOffset = 10; + this.yOffset = 40; + this.renderHillClimber(); + } + renderHillClimber() { + let robotLocation = this.hillClimber.getCurrentState(); + let robotStateValue = this.states[robotLocation]; + this.robot = this.svg.append('g') + .attr('class', 'robot') + .append('svg:image') + .attr('xlink:href', '../third-party/robot.png') + .attr('height', this.botHeight) + .attr('width', this.botWidth) + .attr('x', this.xScale(robotLocation) - this.xOffset) + .attr('y', this.h - this.yScale(robotStateValue) - this.yOffset); + this.hillDiagram.visit(robotLocation); - } - move(state) { - let robotLocation = state; - let robotStateValue = this.states[state]; - this.robot.transition() - .duration(100) - .attr('x', this.xScale(robotLocation) - this.xOffset) - .attr('y', this.h - this.yScale(robotStateValue) - this.yOffset); - } } + move(state) { + let robotLocation = state; + let robotStateValue = this.states[state]; + this.robot.transition() + .duration(100) + .attr('x', this.xScale(robotLocation) - this.xOffset) + .attr('y', this.h - this.yScale(robotStateValue) - this.yOffset); + } +} + +class HillWorld { + constructor(selector, h, w) { + this.h = h; + this.w = w; + this.svg = d3.select(selector).html("") + .append('svg') + .attr('height', this.h) + .attr('width', this.w); + + this.hill = new Hill(); + this.hillClimber = new HillClimber(this.hill); + this.hillDiagram = new HillDiagram(this.hill, this.svg, this.h, this.w); + this.hillClimberDiagram = new HillClimberDiagram(this.hill, this.svg, this.h, this.w, this.hillDiagram, this.hillClimber); + + this.borderPath = this.svg.append('rect') + .attr('x', this.hillDiagram.padding) + .attr('y', 0) + .attr('height', this.h) + .attr('width', this.w - 2 * this.hillDiagram.padding) + .style('stroke', 'black') + .style('fill', 'none') + .style('stroke-width', 1); - //Wrapper class for the entire diagram - class HillClimbingDiagram { + } +} + +//First Diagram (interactive hill world) +$(document).ready(function() { + + class InteractiveHillWorld extends HillWorld { constructor(selector, h, w) { - this.h = h; - this.w = w; - this.svg = d3.select(selector).html("") - .append('svg') - .attr('height', this.h) - .attr('width', this.w) - .attr('border', 2); - - this.hill = new Hill(); - this.hillClimber = new HillClimber(this.hill); - this.hillDiagram = new HillDiagram(this.hill, this.svg, this.h, this.w); - this.hillClimberDiagram = new HillClimberDiagram(this.hill, this.svg, this.h, this.w, this.hillDiagram, this.hillClimber); + super(selector, h, w); this.bindClicks(); - this.borderPath = this.svg.append('rect') - .attr('x', this.hillDiagram.padding) - .attr('y', 0) - .attr('height', this.h) - .attr('width', this.w - 2 * this.hillDiagram.padding) - .style('stroke', 'black') - .style('fill', 'none') - .style('stroke-width', 1); - this.moves = 0; this.maxMoves = 25; this.moveAllowed = true; this.updateMoves(); + + } + updateMoves() { + let leftMoves = this.maxMoves - this.moves; + if (leftMoves >= 0) { + d3.select('#hillMoves').html('Moves Left :' + (this.maxMoves - this.moves)); + } } bindClicks() { @@ -189,8 +207,129 @@ $(document).ready(function() { } function init() { - var diagram = new HillClimbingDiagram('#hillCanvas', 500, 1000); + var diagram = new InteractiveHillWorld('#hillCanvas', 500, 1000); } init(); $('#hillClimbRestart').click(init); }); + +//Second diagram (Hill climbing Search) +$(document).ready(function() { + class HCSearchDiagram extends HillWorld { + constructor(selector, h, w) { + super(selector, h, w); + this.getGMRAreas(); + this.colorGlobalRegions(); + this.bindClicks(); + this.startClimbing(); + } + + //GMRAreas => Global Maxima Reachable Areas + //Areas from where the globam maxima is reachable + getGMRAreas() { + let hillData = this.hillDiagram.hillData; + //Initialize to false + for (let i = 0; i < hillData.length; i++) { + hillData[i].isGMRA = false; + } + + let bestStates = this.hill.getBestStates(); + let states = this.hill.states; + //For every global maxima, + for (let i = 0; i < bestStates.length; i++) { + //Go right side + let j = bestStates[i] + 1; + while (j + 1 < states.length && states[j - 1] > states[j + 1]) { + hillData[j].isGMRA = true; + j++; + } + //corner case for the last state in the diagram + if (j == states.length - 1 && states[j - 1] > states[j]) { + hillData[j].isGMRA = true; + } + //Go left side + j = bestStates[i] - 1; + while (j - 1 >= 0 && states[j + 1] > states[j - 1]) { + hillData[j].isGMRA = true; + j--; + } + //corner case for the first state in the diagram + if (j == 0 && states[j + 1] > states[j]) { + hillData[j].isGMRA = true; + } + } + } + + colorGlobalRegions() { + let svgRects = this.hillDiagram.svgRects; + svgRects.transition() + .duration(200) + .style('opacity', 1) + .style('class', (d) => { + if (d.maxima) { + return 'hill-maxima'; + } else { + if (d.isGMRA) { + return 'hill-gmra'; + } else { + return 'hill'; + } + } + }) + .style('fill', (d) => { + if (d.maxima) { + return colors.maxima; + } else { + if (d.isGMRA) { + return colors.GMRA; + } else { + return colors.hill; + } + } + }); + } + + startClimbing() { + this.climber = this.hillClimber.climb(); + this.stopClimbing(); + this.intervalFunction = setInterval(() => { + if (!this.climb()) { + this.stopClimbing(); + } + }, 500); + } + stopClimbing() { + clearInterval(this.intervalFunction); + } + + climb() { + let next = this.climber.next(); + this.hillClimber.changeState(next.value); + this.hillClimberDiagram.move(next.value); + if (next.done) { + return false; + } else { + return true; + } + } + + bindClicks() { + this.clickHandler = () => { + let state = Math.floor(this.hillDiagram.xScale.invert(d3.mouse(this.svg.node())[0])); + if (state >= 0 && state < 100) { + this.hillClimber.changeState(state); + this.hillClimberDiagram.move(state); + this.startClimbing(); + } + }; + this.svg.on('mousedown', this.clickHandler); + } + + } + + function init() { + var diagram = new HCSearchDiagram('#hillSearchCanvas', 500, 1000); + } + init(); + $('#hillSearchRestart').click(init); +}) diff --git a/4-Beyond-Classical-Search/hillClimbing.js b/4-Beyond-Classical-Search/hillClimbing.js index 40584ac..cf65a55 100644 --- a/4-Beyond-Classical-Search/hillClimbing.js +++ b/4-Beyond-Classical-Search/hillClimbing.js @@ -48,6 +48,7 @@ class Hill { } return bestStates; } + } class HillClimber { @@ -77,7 +78,7 @@ class HillClimber { //For hillclimbSearch getNewState() { - let currentState = this.hill.getCurrentState(); + let currentState = this.currentState; let options = []; options.push(currentState); if (currentState + 1 < this.hill.getStates().length) { @@ -86,10 +87,15 @@ class HillClimber { if (currentState - 1 >= 0) { options.push(currentState - 1); } - let newState = this.decideNewState(options); - return { - state: newState, - isNew: (newState == currentState) - }; + return this.decideNewState(options); + } + + * climb() { + let newState = this.getNewState(); + while (this.currentState != newState) { + yield newState; + newState = this.getNewState(); + } + return newState; } } diff --git a/4-Beyond-Classical-Search/index.html b/4-Beyond-Classical-Search/index.html index e29eff6..6408986 100644 --- a/4-Beyond-Classical-Search/index.html +++ b/4-Beyond-Classical-Search/index.html @@ -1,5 +1,6 @@ +
In many optimization problems, the path to the solution is irrelevant. - In pure optimization problems, the best state is defined by the objective function. - To represent such problems, a state-space landscape is used. It has a location (state) - 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
-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 25 moves to find the highest peak before the hill is revealed.
+In many optimization problems, the path to the solution is irrelevant. In pure optimization problems, the best state is defined by the objective function. To represent such problems, a state-space landscape is used. It has a location (state) + 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
+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 25 moves to find the highest peak before the hill is revealed.
+represents found global maximas and represents unfound global maximas
Try to come up with a strategy that can be used for al kinds of hills.
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.
+The represents global maximas and represents the states from where the hill climbing search can reach a global maxima.
+Click on different states to start Hill Climbing Search from there and see which direction it prefers and when does it stop.
+Use the restart button to try it on different kinds of hills.
+Click on the screen to restart the simulation.
The orange line represents the current position.
- + @@ -61,33 +73,33 @@Little critters change the color of their fur to match the background to camouflage themselves from predators.
Click on the canvas to generate next generation. Keep clicking to generate another progeny.
Note: Single point crossover might not be suitable for all applications
-Click to reset. Green tile is destination.
-