Skip to content

Instantly share code, notes, and snippets.

@dannyko
Last active August 29, 2015 14:07
Show Gist options
  • Save dannyko/ed7e970977ff1cb7d098 to your computer and use it in GitHub Desktop.
Save dannyko/ed7e970977ff1cb7d098 to your computer and use it in GitHub Desktop.
Visual Optimization

Visual Optimization

This is an example of interactive or "visual" optimization.

Sliding the slider above the heatmap adjusts the color-map scale for the heatmap display, relative to the current best computed value.

The sliders on the right control which pair of coordinates is plotted using the heatmap.

Click on the heatmap to update the current position along the 2 coordinates shown in the heat map.

Use the scroll-wheel while hovering over the heatmap to zoom in and out.

Dark green color shows the lowest known function value.

Clicking on dark green points until all pairs directions are in low-energy regions moves towards lower function values.

The colormap continually updates to show dark green as the current lowest known value.

Scanning through all dimension-pairs before making a move updates the color scale according to the current lowest known value.

Iteratevly Zooming in and shifting the colormap scale slider to the left refines optimal positions.

For this example, a four-particle Lennard-Jones cluster problem was used, with a known global minimum function value of -6.0 as shown in the thumbnail:

thumbnail

In principle, almost any objective function could be used, even one whose global minimum is not known.

<!doctype html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
body {
font: 18pt courier;
font-weight: bold;
}
table {
padding: 0;
border: 0;
margin: 0;
margin-left: 60px;
}
td {
/*white-space:nowrap; */
}
canvas {
width: 80vmin;
height: 80vmin;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
[data-selected="true"] {
fill: #FFF ;
}
#ylabel {
position:absolute;
-webkit-transform: rotate(270deg);
-moz-transform: rotate(270deg);
-ms-transform: rotate(270deg);
-o-transform: rotate(270deg);
-webkit-transform: rotate(270deg);
-webkit-transform-origin: left center;
-moz-transform-origin: left center;
-ms-transform-origin: left center;
-o-transform-origin: left center;
transform-origin: left center;
left: 50px;
top: 65% ;
</style>
</head>
<body>
<table>
<tbody>
<tr>
<td colspan="2" align="center">
<span id="temperatureText">
</span>
<br/>
<input min=".1" max="10" step=".01" type="range" id="fscaleSlider" value="1" title="colormap scale" autocomplete="off">
</td>
<td id="scanner">
</td>
</tr>
<tr>
<td>
<span id="ylabel">
</span>
</td>
<td id="canvas_td">
<canvas id="canvas_1">
Please use a modern browser
</canvas>
</td>
<td id="svg_td">
</td>
</tr>
<tr>
<td>
</td>
<td align="center">
<span id="xlabel">
</span>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<script type="text/javascript" src="iopt.js"></script>
</body>
</html>
var copyPosition = function() {
copyToClipboard(r0.toString());
}
var copyToClipboard = function(text) {
window.prompt("Copy to clipboard: Ctrl+C, Enter", text);
} ;
var fscale = 3 ;
var fscaleSlider = d3.select("#fscaleSlider")
.attr('value', fscale)
.on("input", function() {
fscale = +this.value;
set_zscale() ;
z_range() ;
redraw() ;
}) ;
/// *******************
///
/// initialize position:
///
/// *******************
var rmin = Math.pow(2, 1/6) ; // the minimizer of the lennard-jones potential
var r0 = [0, 0, 0, rmin, 0, 0, 0, rmin, 0, 0, 0, rmin] ; // , rmin, rmin, rmin
var N = r0.length / 3 ; // number of L-J particles
var xI = 0 ;
var yI = 1 ;
var Ndim = r0.length ; // number of free dimensions / degrees of freedom
var canvas = d3.select('#canvas_1') ;
var size = 127 ;
canvas
.attr('width', size)
.attr('height', size)
var element = document.getElementById("canvas_1") ;
var c = element.getContext("2d") ;
// read the width and height of the canvas
var width = element.width ;
var height = element.height ;
var body = d3.select(document.body) ;
var td = d3.select('#svg_td') ;
var copyButton = td.append('input')
.attr('type', 'button')
.attr('value', 'current position')
.attr('onClick', 'copyPosition() ; return false ;') ;
td.append('br')
var dimSelect = [] ;
var Ndsel = 2 ;
dimSelect1 = td.append('input')
.attr('id', 'dimSelect1')
.attr('type', 'range')
.attr('value', 1)
.attr('min', 1)
.attr('max', Ndim - 1)
.attr('step', 1)
.on('input', function() {
if(Number(this.value) >= Number(dimSelect2.node().value)) {
dimSelect2.node().value = Number(this.value) + 1 ;
}
xI = Number(dimSelect1.node().value) - 1 ;
yI = Number(dimSelect2.node().value) - 1 ;
dselOut1.html('i = ' + (xI + 1)) ;
dselOut2.html('j = ' + (yI + 1)) ;
update_figure(true) ;
}) ;
var dselOut1 = td.append('output')
.attr('id', 'dimSelect1out')
.attr('for', 'dimSelect1')
.html('i = ' + (xI + 1)) ;
td.append('br') ;
dimSelect2 = td.append('input')
.attr('id', 'dimSelect2')
.attr('type', 'range')
.attr('value', 2)
.attr('min', 2)
.attr('max', Ndim)
.attr('step', 1)
.on('input', function() {
if(Number(this.value) <= Number(dimSelect1.node().value)) {
dimSelect1.node().value = Number(this.value) - 1 ;
}
xI = Number(dimSelect1.node().value) - 1 ;
yI = Number(dimSelect2.node().value) - 1 ;
dselOut1.html('i = ' + (xI + 1)) ;
dselOut2.html('j = ' + (yI + 1)) ;
update_figure(true) ;
}) ;
var dselOut2 = td.append('output')
.attr('id', 'dimSelect2out')
.attr('for', 'dimSelect2')
.html('j = ' + (yI + 1)) ;
function setPixel(imageData, x, y, r, g, b, a) {
var index = (x + y * imageData.width) * 4 ;
imageData.data[index + 0] = r ;
imageData.data[index + 1] = g ;
imageData.data[index + 2] = b ;
imageData.data[index + 3] = a ;
}
// create a new pixel array
var imageData = c.createImageData(width, height) ;
var xRange = [-2.5, 2.5] ;
var yRange = [-2.5, 2.5] ;
var xscale ;
var yscale ;
var set_scale = function() {
xscale = d3.scale.linear()
.domain([0, width])
.range(xRange) ;
yscale = d3.scale.linear()
.domain([0, height])
.range(yRange) ;
} ;
set_scale() ;
var zscale = d3.scale.linear() ;
var zdomain ;
var zrange = ["rgb(0, 88, 11)", "rgb(0, 255, 33)", "rgb(0, 3, 33)", "rgb(0, 11, 55)", "rgb(0, 88, 155)", "rgb(0, 88, 155)", "rgb(111, 0, 33)", "rgb(122, 0, 33)", "rgb(155, 0, 88)"] ;
var f = [] ;
var fvec = [] ;
var fmin = Infinity, fmax = -Infinity ;
var lennard_jones = function(d) { // Lennard-Jones term
var V = Math.pow(d, -6) ;
V = V * V - V ;
return V ;
}
var objective_function = function(r) {
var V = 0 ;
for(var i = 0 ; i < N - 1 ; i++) {
for(var j = i + 1 ; j < N ; j++) {
var d2 = 0 ;
for(k = 0 ; k < 3 ; k++) {
var drk = r[i * 3 + k] - r[j * 3 + k] ; // 3 dimensions per particle: (x, y, z)
d2 += drk * drk ;
}
var d = Math.sqrt(d2) ; // the distance between the spheres
V += lennard_jones(d) ;
}
}
V *= 4 ; // so each perfect contact scores a -1 energy
return V ;
}
var stepSize = 0.00001 ; // for numerical gradient (secant)
var gradient_function = function(r) {
var g = [] ;
for(var i = 0 ; i < r.length ; i++) {
var r1 = r.slice(0) ;
r1[i] += stepSize ;
fp = objective_function(r1) ;
r1[i] -= 2 * stepSize ;
fm = objective_function(r1) ;
g[i] = (fp - fm) / (2 * stepSize) ;
}
// console.log('grad', g, r.length)
return g ;
} ;
var xval = [] ;
var yval = [] ;
function update_image() {
var count = 0 ;
for (var i = 0 ; i < height ; i++) {
f[i] = [] ;
xval[i] = [] ;
yval[i] = [] ;
for(var j = 0 ; j < width ; j++) {
var x = xRange[0] + (i / width) * (xRange[1] - xRange[0]) ;
var y = yRange[0] + (j / width) * (yRange[1] - yRange[0]) ;
var r = r0.slice(0) ;
for (k = 0 ; k < r0.length ; k++) {
if(k == xI) r[xI] = x + r0[xI] ;
if(k == yI) r[yI] = y + r0[yI] ;
}
f[i][j] = objective_function(r) ;
xval[i][j] = x ;
yval[i][j] = y ;
// console.log('x', x, 'y', y, 'r', r, 'f', f[i][j])
if(f[i][j] < fmin) {
fmin = f[i][j] ;
ropt = r.slice(0) ;
}
if(f[i][j] > fmax) fmax = f[i][j] ;
fvec[count] = f[i][j] ;
count++ ;
}
}
var fmid = 0.5 * (fmin + fmax) ;
var fsort = fvec.slice(0).sort(d3.ascending)
var fmed = d3.quantile(fsort, 0.5) ;
var fran = fmed - fmin ;
// zdomain = [fmin, d3.quantile(fsort, 1/3), d3.quantile(fsort, 2/3), Infinity] ;
set_zscale() ;
//.domain([fmin, d3.quantile(fvec, 0.1), d3.quantile(fvec, 0.45), d3.quantile(fvec, 0.5), d3.quantile(fvec, 0.75), d3.quantile(fvec, 0.95), fmax])
//.range(["rgb(0, 0, 44)", "rgb(11, 33, 121)", "rgb(22, 88, 121)", "rgb(111, 111, 111)", "rgb(121, 88, 22)", "rgb(111, 11, 0)", "rgb(44, 0, 0)"]);
} ; // end update_image
var set_zscale = function() {
zdomain = [fmin, fmin + .01 * fscale, fmin + .1 * fscale, fmin + 0.2 * fscale, fmin + 0.4 * fscale, fmin + .8 * fscale, fmin + 1.6 * fscale, fmax * 0.1 * fscale, Infinity] ;
zdomain.sort(d3.ascending) ;
}
var redraw = function() {
for (var i = 0 ; i < height ; i++) {
for(var j = 0 ; j < width ; j++) {
var r = d3.rgb(zscale(f[i][j])).r ;
var g = d3.rgb(zscale(f[i][j])).g ;
var b = d3.rgb(zscale(f[i][j])).b ;
if(i == Math.ceil(height / 2) && j == Math.ceil(width / 2)) {
r = 255 ;
g = 255 ;
b = 0 ;
}
setPixel(imageData, i, j, r, g, b, 255) ; // 255 opaque
}
}
// copy the image data back onto the canvas
c.putImageData(imageData, 0, 0) ; // at coords 0,0
}
var update_figure = function (scaleSwitch) {
update_image() ;
if(scaleSwitch) {
set_scale() ;
z_range() ;
}
redraw() ;
update_text() ;
}
var updating = false ;
var update_position = function(dx, dy, i, j) {
if(updating) return ;
updating = true ;
var Nstep = 4 ;
var kstep = 0 ;
// console.log('upos', 'r0', r0, dx, dy)
var step = function() {
if(kstep < Nstep) {
r0[i] += dx / Nstep ;
r0[j] += dy / Nstep ;
update_figure() ;
kstep++ ;
var delay = 16 ;
setTimeout(step, delay) ;
// console.log('stepping', kstep)
} else {
// console.log('done stepping', kstep, 'r0', r0)
z_range() ;
redraw() ;
update_text() ;
updating = false ;
}
}
step() ;
}
var update_text = function() {
update_xtext() ;
update_ytext() ;
update_ttext() ;
}
var update_ttext = function() {
d3.select('#temperatureText').text('f(x) = ' + objective_function(r0).toPrecision(6)) ;
}
var update_xtext = function() {
d3.select('#xlabel').html('x<sub>' + (xI + 1) + '</sub> = ' + r0[xI].toPrecision(8)) ;
}
var update_ytext = function() {
d3.select('#ylabel').html('x<sub>' + (yI + 1) + '</sub> = ' + r0[yI].toPrecision(8)) ;
}
var z_range = function() {
zscale
.domain(zdomain)
.range(zrange) ;
}
update_position(0, 0, xI, yI) ;
canvas.on('click', function() {
var xy = d3.mouse(canvas.node()) ;
xy[0] /= canvas.node().clientWidth ;
xy[1] /= canvas.node().clientHeight ;
xy[0] *= width ;
xy[1] *= height ;
xy[0] = Math.floor(xy[0]) ;
xy[1] = Math.floor(xy[1]) ;
console.log(xy)
Npix = 3 ; // for local search to "snap" to the minimum pixel close to where the user clicked
var minf = f[xy[1]][xy[0]] ;
var mini = xy[1] ;
var minj = xy[0] ;
var iStart = Math.max(0, xy[0] - Npix) ;
var iEnd = Math.min(width - 1, xy[0] + Npix) ;
var jStart = Math.max(0, xy[1] - Npix) ;
var jEnd = Math.min(height - 1, xy[1] + Npix) ;
for (var i = iStart ; i <= iEnd ; i++) {
for (var j = jStart ; j <= jEnd ; j++) {
if(f[i][j] < minf) {
minf = f[i][j] ;
mini = i ;
minj = j ;
}
}
}
update_position(xval[mini][minj], yval[mini][minj], xI, yI) ;
}
) ;
var scrolling = false ;
var scroll = function() {
if(scrolling) return ;
scrolling = true ;
console.log(d3.event)
if(d3.event.wheelDelta !== undefined) {
var delta = d3.event.wheelDelta / Math.abs(d3.event.wheelDelta) ;
}
if(d3.event.detail !== undefined) {
var delta = -d3.event.detail / Math.abs(d3.event.detail) ;
}
var step = .2 ;
if(delta < 0) {
step = 1 + step ;
} else {
step = 1 - step ;
}
xRange[0] = step * xRange[0] ;
xRange[1] = step * xRange[1] ;
yRange[0] = step * yRange[0] ;
yRange[1] = step * yRange[1] ;
set_scale()
update_image() ;
z_range() ;
redraw() ;
scrolling = false ;
} ;
canvas
.on("mousewheel.zoom", null)
.on("DOMMouseScroll.zoom", null) // disables older versions of Firefox
.on("wheel.zoom", null) // disables newer versions of Firefox
canvas.on("mousewheel", scroll) // default scroll wheel listener
canvas.on("DOMMouseScroll.zoom", scroll) ;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment