UNPKG

battleship-search

Version:

maximize an n-dimensional landscape using the battleship search algorithm

175 lines (149 loc) 4.95 kB
var expandBounds = require('./lib/expand_bounds.js'); var Region = require('./lib/region.js'); var mean = require('./lib/mean.js'); var dist = require('./lib/dist.js'); var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; module.exports = Search; inherits(Search, EventEmitter); function Search (range, opts, fn) { var self = this; if (!(this instanceof Search)) return new Search(range, opts, fn); if (typeof opts === 'function') { fn = opts; opts = {}; } if (!opts) opts = {}; if (range.length < 2) { throw new Error('dimensionality must be >= 2'); } this.range = range; this.corners = expandBounds(range); this.fn = fn; this.center = range.map(mean); this.regions = []; this.k = range.map(function () { return 0 }); this.smooth = range.map(function () { return 1 }); this.tansum = range.map(function () { return 1 }); this._pointMap = {}; this._pending = []; this.max = -Infinity; this.iteration = 0; } Search.prototype.next = function () { var self = this; if (this._pending.length > 0) { var previously = false; var c = this._pending[0].next(function (pt) { previously = self.has(pt); return self.test(pt); }); if (previously) return this.next(); if (c === null) { this._pending.shift(); return this.next(); } this.iteration ++; return c; } if (this.iteration === 0) { var first = true; this._pending.push({ next: function (fn) { if (!first) return null; first = false; var value = fn(self.center); return { point: self.center, value: value }; } }); } else if ((this.iteration - 1) / 2 < this.corners.length) { var i = (this.iteration - 1) / 2; var pts = [ this.center ]; for (var j = 0; j < 2; j++) { pts.push(this.corners[(i+j) % this.corners.length]); } var r = Region(pts); this._pending.push(r); this.regions.push(r); this.emit('region', r); } else { var best = this.best(); var subRegions = best.region.divide(); this.emit('divide', best.region); var xs = [ best.index, 1 ].concat(subRegions); this.regions.splice.apply(this.regions, xs); for (var i = 0; i < subRegions.length; i++) { var r = subRegions[i]; this._pending.push(r); this.emit('region', r); } } return this.next(); }; Search.prototype.best = function () { var max = this.regions[0]; var score = max.getScore(this.regions, this.max); var index = 0; for (var i = 1; i < this.regions.length; i++) { var r = this.regions[i]; var v = r.getScore(this.regions, this.max); if (v > score) { max = r; index = i; score = v; } } this.emit('best', r, score); return { region: max, index: index }; }; Search.prototype.has = function (pt) { var key = pt.join(','); return this._pointMap[key] !== undefined; }; Search.prototype.test = function (pt) { var key = pt.join(','); if (this._pointMap[key] !== undefined) return this._pointMap[key]; var value = this.fn(pt); this._pointMap[key] = value; var sm = this._smooth(pt, value); this.regions.forEach(function (r) { r.setSmooth(sm); }); this.emit('test', pt, value); if (value > this.max) { this.max = value; this.emit('max', pt, value); } return value; }; Search.prototype._smooth = function (pt, value) { var pcoords = pt.concat(value); var epsilon = []; var ptKeys = Object.keys(this._pointMap); var key = pt.join(','); if (ptKeys.length < 2) return this.smooth; for (var i = 0; i < this.range.length; i++) { epsilon[i] = 0; var csum = 0; for (var j = 0; j < ptKeys.length; j++) { if (key === ptKeys[j]) continue; var otherPt = ptKeys[j].split(','); var otherValue = this._pointMap[ptKeys[j]]; var projMag = Math.sqrt( Math.pow(otherPt[i] - pt[i], 2) + Math.pow(otherValue - value, 2) ); var c = projMag / dist(pt, ptKeys[j].split(',').map(parseFloat)); epsilon[i] += c; csum += Math.atan2(otherValue - value, otherPt[i] - pt[i]) * c; } this.tansum[i] = (this.k[i] / (this.k[i] + epsilon[i])) * this.tansum[i] + csum / (this.k[i] + epsilon[i]) ; this.k[i] += epsilon[i]; this.smooth[i] = Math.tan(this.tansum[i]); } return this.smooth; };