Refactor: Extract functions

This commit is contained in:
Pitchaya Boonsarngsuk
2018-01-19 09:29:14 +00:00
parent 1d711f828d
commit feec778c62
12 changed files with 351 additions and 163 deletions

View File

@@ -129,7 +129,7 @@
<div class="left">
<p>Select algorithm:</p>
<div id="algorithms">
<input id="HLButton" type="radio" name="algorithm" onclick="d3.select('#startSimulation').on('click', startHybridSimulation)">Hybrid
<input id="HLButton" type="radio" name="algorithm" checked onclick="d3.select('#startSimulation').on('click', startHybridSimulation)">Hybrid
layout
<br>
<div id="HLParameters" class="parameters" style="display:none">
@@ -232,7 +232,7 @@
<div class="left">
<p>Select distance function:</p>
<div id="distance">
<input type="radio" name="distance" onclick="distanceFunction=calculateDistance"> General<br>
<input type="radio" name="distance" checked onclick="distanceFunction=calculateDistance"> General<br>
<input type="radio" name="distance" onclick="distanceFunction=calculateEuclideanDistance"> Euclidean<br>
<input type="radio" name="distance" onclick="distanceFunction=calculateManhattanDistance"> Manhattan<br>
<input type="radio" name="distance" onclick="distanceFunction=calculateJaccardDissimilarity"> Jaccard<br>
@@ -252,6 +252,9 @@
<script src="js/lib/intercom.js"></script>
<script src="../build/d3-neighbour-sampling.js"></script>
<script src="js/src/neighbourSampling-papaparsing.js"></script>
<script src="js/src/neighbourSampling-papaparsing/hybrid.js"></script>
<script src="js/src/neighbourSampling-papaparsing/linkForce.js"></script>
<script src="js/src/neighbourSampling-papaparsing/neighbourSampling.js"></script>
<script src="js/distances/distancePokerHands.js"></script>
<script src="js/distances/distance.js"></script>
<script src="js/distances/euclideanDistance.js"></script>

View File

@@ -0,0 +1,43 @@
/**
* Initialize the hybrid layout algorithm and start simulation.
*/
function startHybridSimulation() {
console.log("startHybridSimulation");
springForce = false;
d3.selectAll(".nodes").remove();
simulation.stop();
p1 = performance.now();
configuration = {
iteration: ITERATIONS,
neighbourSize: NEIGHBOUR_SIZE,
sampleSize: SAMPLE_SIZE,
distanceRange: SELECTED_DISTANCE * MULTIPLIER,
fullIterations: FULL_ITERATIONS,
fullNeighbourSize: FULL_NEIGHBOUR_SIZE,
fullSampleSize: FULL_SAMPLE_SIZE,
fullDistanceRange: FULL_SELECTED_DISTANCE * MULTIPLIER,
distanceFn: function (s, t) {return distanceFunction(s, t, props, norm) * MULTIPLIER;},
pivots: PIVOTS,
numPivots: NUM_PIVOTS
};
console.log(configuration);
hybridSimulation = d3.hybridSimulation(nodes, configuration);
let sample = hybridSimulation.sample();
let remainder = hybridSimulation.remainder();
addNodesToDOM(sample);
hybridSimulation
.on("sampleTick", ticked)
.on("fullTick", ticked)
.on("startFull", startedFull)
.on("end", ended);
function startedFull() {
console.log("startedFull");
d3.selectAll(".nodes").remove();
addNodesToDOM(nodes);
}
}

View File

@@ -0,0 +1,40 @@
/**
* Initialize the link force algorithm and start simulation.
*/
function startLinkSimulation() {
console.log("startLinkSimulation")
springForce = false;
simulation.stop();
p1 = performance.now();
let links = [];
// Initialize link array.
nodes = simulation.nodes();
for (i = 0; i < nodes.length; i++) {
for (j = 0; j < nodes.length; j++) {
if (i !== j) {
links.push({
source: nodes[i],
target: nodes[j],
});
}
}
}
// Add the links to the simulation.
simulation.force(forceName, d3.forceLink().links(links));
simulation
.alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
.force(forceName)
// The distance function that will be used to calculate distances
// between nodes.
.distance(function (n) {
return distanceFunction(n.source, n.target, props, norm) * MULTIPLIER;
})
// Set the parameter for the algorithm (optional).
.strength(1);
// Restart the simulation.
simulation.alpha(1).restart();
}

View File

@@ -0,0 +1,29 @@
/**
* Initialize the Chalmers' 1996 algorithm and start simulation.
*/
function startNeighbourSamplingSimulation() {
console.log("startNeighbourSamplingSimulation");
springForce = true;
simulation.stop();
p1 = performance.now();
simulation
.alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
.force(forceName, d3.forceNeighbourSamplingDistance()
// Set the parameters for the algorithm (optional).
.neighbourSize(NEIGHBOUR_SIZE)
.sampleSize(SAMPLE_SIZE)
// .freeness(0.5)
.distanceRange(SELECTED_DISTANCE * MULTIPLIER)
// The distance function that will be used to calculate distances
// between nodes.
.distance(function (s, t) {
return distanceFunction(s, t, props, norm) * MULTIPLIER;
})
.stableVelocity(1.2 * MULTIPLIER)
.stableVeloHandler( function(){simulation.stop(); ended();} )
);
// Restart the simulation.
console.log(simulation.force(forceName).neighbourSize(), simulation.force(forceName).sampleSize());
simulation.alpha(1).restart();
}

View File

@@ -0,0 +1,43 @@
/**
* Initialize the t-SNE algorithm and start simulation.
*/
function starttSNE() {
springForce = false;
simulation.stop();
p1 = performance.now();
simulation
.alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
.force(forceName, d3.tSNE()
// Set the parameter for the algorithm (optional).
.perplexity(PERPLEXITY)
.learningRate(LEARNING_RATE)
// The distance function that will be used to calculate distances
// between nodes.
.distance(function (s, t) {
return distanceFunction(s, t, props, norm) * MULTIPLIER;
}));
// Restart the simulation.
console.log(simulation.force(forceName).perplexity(), simulation.force(forceName).learningRate());
simulation.alpha(1).restart();
}
/**
* Initialize the Barnes-Hut algorithm and start simulation.
*/
function startBarnesHutSimulation() {
springForce = false;
simulation.stop();
p1 = performance.now();
simulation
.alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS))
.force(forceName, d3.forceBarnesHut()
// The distance function that will be used to calculate distances
// between nodes.
.distance(function (s, t) {
return distanceFunction(s, t, props, norm) * MULTIPLIER;
}));
// Restart the simulation.
simulation.alpha(1).restart();
}

View File

@@ -1,8 +1,9 @@
import { dispatch } from "d3-dispatch";
import constant from "./constant";
import interpolation from "./interpolation";
import interpolationPivots from "./interpolationPivots";
import interpBruteForce from "./interpolation/interpBruteForce";
import interpolationPivots from "./interpolation/interpolationPivots";
import neighbourSamplingDistance from "./neighbourSamplingDistance";
import { takeSampleFrom } from "./interpolation/helpers";
export default function (nodes, config) {
@@ -21,10 +22,9 @@ export default function (nodes, config) {
NUMPIVOTS = config.hasOwnProperty("numPivots") ? config.numPivots : 3,
event = d3.dispatch("sampleTick", "fullTick", "startFull", "end");
var sets = sampleFromNodes(nodes, Math.sqrt(nodes.length));
var sets = takeSampleFrom(nodes, Math.sqrt(nodes.length));
var sample = sets.sample;
var remainder = sets.remainder;
var sampleSubset = sampleFromNodes(sample, Math.sqrt(sample.length)).sample;
var sampleSimulation = d3.forceSimulation(sample)
.stop()
@@ -46,15 +46,19 @@ export default function (nodes, config) {
.alpha(1).restart();
function ended() {
sample.forEach(function (d) {
d.vx = 0;
d.vy = 0;
});
event.call("startFull");
console.log("Ended sample simulation");
alert('About to interpolate');
interpolation(sample, remainder, sampleSubset, distanceFn);
interpBruteForce(sample, remainder, distanceFn);
/*
if (PIVOTS) {
interpolationPivots(sample, remainder, sampleSubSet, NUMPIVOTS, distance);
} else {
interpolation(sample, remainder, sampleSubSet, distance);
interpBruteForce(sample, remainder, sampleSubSet, distance);
}
*/
event.call("fullTick");

View File

@@ -1,153 +0,0 @@
export default function(sampleSet, remainderSet, sampleSubset, distanceFn) {
// var distance = calculateEuclideanDistance;
// console.log("Brute-force");
for (let node of remainderSet) {
let nearestSample = undefined,
minDist = undefined,
sampleSubsetDistanceCache = [];
for (let sample of sampleSet) {
let dist = distanceFn(node, sample);
if (nearestSample === undefined || dist < minDist) {
minDist = dist;
nearestSample = sample;
}
let index = sampleSubset.indexOf(sample);
if (index !== -1) {
sampleSubsetDistanceCache[index] = dist;
}
}
placeNearToNearestNeighbour(node, nearestSample, minDist, sampleSubset, sampleSubsetDistanceCache);
}
}
function placeNearToNearestNeighbour(node, minNode, radius, sampleSubset, realDistances) {
let
dist0 = sumDistError(pointOnCircle(minNode.x, minNode.y, 0, radius), sampleSubset, realDistances),
dist90 = sumDistError(pointOnCircle(minNode.x, minNode.y, 90, radius), sampleSubset, realDistances),
dist180 = sumDistError(pointOnCircle(minNode.x, minNode.y, 180, radius), sampleSubset, realDistances),
dist270 = sumDistError(pointOnCircle(minNode.x, minNode.y, 270, radius), sampleSubset, realDistances),
lowBound = 0.0,
highBound = 0.0;
// Determine the closest quadrant
if (dist0 == dist180) {
if (dist90 > dist270)
lowBound = highBound = 270;
else
lowBound = highBound = 90;
} else if (dist90 == dist270) {
if (dist0 > dist180)
lowBound = highBound = 180;
else
lowBound = highBound = 0;
} else if (dist0 > dist180) {
if (dist90 > dist270) {
lowBound = 180;
highBound = 270;
} else {
lowBound = 90;
highBound = 180;
}
} else {
if (dist90 > dist270) {
lowBound = 270;
highBound = 360;
} else {
lowBound = 0;
highBound = 90;
}
}
let angle = binarySearchMin(lowBound, highBound,
function(angle){
return sumDistError(pointOnCircle(minNode.x, minNode.y, angle, radius), sampleSubset, realDistances);
});
let newPoint = pointOnCircle(minNode.x, minNode.y, angle, radius);
// console.log(newPoint);
node.x = newPoint.x;
node.y = newPoint.y;
for (let i = 0; i < 20; i++) {
let forces = sumForcesToSample(node, sampleSubset, realDistances);
// console.log(forces);
node.x += forces.x*0.5;
node.y += forces.y*0.5;
}
}
// With a circle radius r, and center at (h,k),
// Find the coordinate of a point at angle degree
function pointOnCircle(h, k, angle, r) {
let x = h + r*Math.cos(toRadians(angle));
let y = k + r*Math.sin(toRadians(angle));
return {
x: x,
y: y
};
}
function toRadians(degrees) {
return degrees * (Math.PI / 180);
}
function sumDistError(currentPos, samples, realDistances) {
let total = 0.0;
for (let i = 0; i < samples.length; i++) {
let sample = samples[i];
let lowDDistance = Math.hypot(sample.x - currentPos.x, sample.y - currentPos.y);
total += Math.abs(lowDDistance - realDistances[i]);
}
return total;
}
function sumForcesToSample(node, samples, sampleCache) {
let nodeVx = 0,
nodeVy = 0;
for (let i = 0; i < samples.length; i++) {
var sample = samples[i];
if(sample === node) continue;
let x = node.x - sample.x,
y = node.y - sample.y,
l = Math.sqrt(x * x + y * y);
l = (l - sampleCache[i]) / l;
x *= l, y *= l;
nodeVx -= x;
nodeVy -= y;
}
return {x: nodeVx, y: nodeVy};
}
function binarySearchMin(lb, hb, fn) {
while (lb <= hb) {
if(lb === hb) return lb;
if(hb-lb == 1) {
if (fn(lb) >= fn(hb)) return hb;
else return lb;
}
let range = hb-lb;
let valLowerHalf = fn(lb + range/4);
let valHigherHalf = fn(lb + range*3/4);
if (valLowerHalf > valHigherHalf)
lb = Math.floor((lb + hb) / 2);
else if (valLowerHalf < valHigherHalf)
hb = Math.ceil((lb + hb) / 2);
else {
lb += Math.floor(range/4);
hb -= Math.ceil(range/4);
}
}
return -1;
}

View File

@@ -0,0 +1,48 @@
export function takeSampleFrom(nodes, amount) {
let randElements = [],
max = nodes.length;
for (var i = 0; i < amount; ++i) {
var rand = nodes[Math.floor((Math.random() * max))];
// If the rand is already in random list or in exclude list
// ignore it and get a new value.
while (randElements.includes(rand)) {
rand = nodes[Math.floor((Math.random() * max))];
}
randElements.push(rand);
}
var remainder = nodes.filter(function (node) {
return !randElements.includes(node);
});
return {
sample: randElements,
remainder: remainder
};
}
// With a circle radius r, and center at (h,k),
// Find the coordinate of a point at angle degree
export function pointOnCircle(h, k, angle, r) {
let x = h + r*Math.cos(toRadians(angle));
let y = k + r*Math.sin(toRadians(angle));
return {
x: x,
y: y
};
}
function toRadians(degrees) {
return degrees * (Math.PI / 180);
}
export function sumDistError(node, samples, realDistances) {
let total = 0.0;
for (let i = 0; i < samples.length; i++) {
let sample = samples[i];
let lowDDistance = Math.hypot(sample.x - node.x, sample.y - node.y);
total += Math.abs(lowDDistance - realDistances[i]);
}
return total;
}

View File

@@ -0,0 +1,27 @@
import { pointOnCircle, takeSampleFrom } from "./helpers";
import { placeNearToNearestNeighbour } from "./interpCommon";
export default function(sampleSet, remainderSet, distanceFn) {
// var distance = calculateEuclideanDistance;
// console.log("Brute-force");
let sampleSubset = takeSampleFrom(sampleSet, Math.sqrt(sampleSet.length)).sample;
for (let node of remainderSet) {
let nearestSample = undefined,
minDist = undefined,
sampleSubsetDistanceCache = [];
for (let sample of sampleSet) {
let dist = distanceFn(node, sample);
if (nearestSample === undefined || dist < minDist) {
minDist = dist;
nearestSample = sample;
}
let index = sampleSubset.indexOf(sample);
if (index !== -1)
sampleSubsetDistanceCache[index] = dist;
}
placeNearToNearestNeighbour(node, nearestSample, minDist, sampleSubset, sampleSubsetDistanceCache);
}
}

View File

@@ -0,0 +1,102 @@
import { pointOnCircle, sumDistError } from "./helpers";
export function placeNearToNearestNeighbour(node, nearNeighbour, radius, sampleSubset, realDistances) {
let
dist0 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 0, radius), sampleSubset, realDistances),
dist90 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 90, radius), sampleSubset, realDistances),
dist180 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 180, radius), sampleSubset, realDistances),
dist270 = sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, 270, radius), sampleSubset, realDistances),
lowBound = 0.0,
highBound = 0.0;
// Determine the closest quadrant
if (dist0 == dist180) {
if (dist90 > dist270)
lowBound = highBound = 270;
else
lowBound = highBound = 90;
} else if (dist90 == dist270) {
if (dist0 > dist180)
lowBound = highBound = 180;
else
lowBound = highBound = 0;
} else if (dist0 > dist180) {
if (dist90 > dist270) {
lowBound = 180;
highBound = 270;
} else {
lowBound = 90;
highBound = 180;
}
} else {
if (dist90 > dist270) {
lowBound = 270;
highBound = 360;
} else {
lowBound = 0;
highBound = 90;
}
}
let angle = binarySearchMin(lowBound, highBound,
function(angle){
return sumDistError(pointOnCircle(nearNeighbour.x, nearNeighbour.y, angle, radius), sampleSubset, realDistances);
});
let newPoint = pointOnCircle(nearNeighbour.x, nearNeighbour.y, angle, radius);
// console.log(newPoint);
node.x = newPoint.x;
node.y = newPoint.y;
for (let i = 0; i < 20; i++) {
let forces = sumForcesToSample(node, sampleSubset, realDistances);
//console.log(forces);
node.x += forces.x*0.5;
node.y += forces.y*0.5;
}
}
function sumForcesToSample(node, samples, sampleCache) {
let nodeVx = 0,
nodeVy = 0;
for (let i = 0; i < samples.length; i++) {
var sample = samples[i];
if(sample === node) continue;
let x = node.x - sample.x,
y = node.y - sample.y,
l = Math.sqrt(x * x + y * y);
l = (l - sampleCache[i]) / l;
x *= l, y *= l;
nodeVx -= x;
nodeVy -= y;
}
return {x: nodeVx, y: nodeVy};
}
function binarySearchMin(lb, hb, fn) {
while (lb <= hb) {
if(lb === hb) return lb;
if(hb-lb == 1) {
if (fn(lb) >= fn(hb)) return hb;
else return lb;
}
let range = hb-lb;
let valLowerHalf = fn(lb + range/4);
let valHigherHalf = fn(lb + range*3/4);
if (valLowerHalf > valHigherHalf)
lb = Math.floor((lb + hb) / 2);
else if (valLowerHalf < valHigherHalf)
hb = Math.ceil((lb + hb) / 2);
else {
lb += Math.floor(range/4);
hb -= Math.ceil(range/4);
}
}
return -1;
}

View File

@@ -56,8 +56,10 @@ export default function () {
// for each node.
findNewNeighbours(i);
}
velocity /= nodes.length*alpha;
// Now total velocity changes per node, alpha not considered
velocity /= nodes.length*(neighbours.length+samples.length)*alpha;
// Now total velocity changes per link, alpha not considered
// TODO per property too
stableVelocity = 0;
if(Math.abs(velocity)<stableVelocity && stableVeloHandler!== null){
console.log("Neighbour sampling is now", velocity, ", calling stableVeloHandler().")
stableVeloHandler();