From 97d8b71963d2c27d0b69faa28f8234060018d916 Mon Sep 17 00:00:00 2001
From: Pitchaya Boonsarngsuk <2285135b@student.gla.ac.uk>
Date: Fri, 26 Jan 2018 13:33:07 +0000
Subject: [PATCH] Revamp neighbor sampling, shredding unused features and
correct calculations
---
...3-force-neighbourSampling-papaparsing.html | 18 --
.../js/src/neighbourSampling-papaparsing.js | 6 +-
.../neighbourSampling.js | 29 +-
src/neighbourSamplingDistance.js | 299 ++++++------------
4 files changed, 121 insertions(+), 231 deletions(-)
diff --git a/examples/d3-force-neighbourSampling-papaparsing.html b/examples/d3-force-neighbourSampling-papaparsing.html
index 1549a70..1b36720 100644
--- a/examples/d3-force-neighbourSampling-papaparsing.html
+++ b/examples/d3-force-neighbourSampling-papaparsing.html
@@ -157,12 +157,6 @@
-
-
-
-
Neighbour
@@ -202,12 +190,6 @@
-
-
Link force in D3
Link force (tweaked)
diff --git a/examples/js/src/neighbourSampling-papaparsing.js b/examples/js/src/neighbourSampling-papaparsing.js
index 13fa8bd..2717cad 100644
--- a/examples/js/src/neighbourSampling-papaparsing.js
+++ b/examples/js/src/neighbourSampling-papaparsing.js
@@ -57,10 +57,8 @@ var MULTIPLIER = 50,
FULL_ITERATIONS = 20,
NODE_SIZE = 10,
COLOR_ATTRIBUTE = "",
- SELECTED_DISTANCE = 10,
FULL_NEIGHBOUR_SIZE = 6,
- FULL_SAMPLE_SIZE = 3,
- FULL_SELECTED_DISTANCE = 10;
+ FULL_SAMPLE_SIZE = 3;
// Create a color scheme for a range of numbers.
var color = d3.scaleOrdinal(d3.schemeCategory10);
@@ -205,12 +203,12 @@ function ticked() {
intercom.emit("passedData", simulation.force(forceName).distributionData());
}
if(alreadyRanIterations == ITERATIONS) {
- simulation.stop();
ended();
}
}
function ended() {
+ simulation.stop();
console.log("ended");
if (rendering !== true) { // Never drawn anything before? Now it's time.
node
diff --git a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js
index b0c21a8..2b1e5b1 100644
--- a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js
+++ b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js
@@ -4,25 +4,24 @@
function startNeighbourSamplingSimulation() {
console.log("startNeighbourSamplingSimulation");
//springForce = true;
+ alreadyRanIterations = 0;
simulation.stop();
p1 = performance.now();
+ let force = d3.forceNeighbourSamplingDistance()
+ .neighbourSize(NEIGHBOUR_SIZE)
+ .sampleSize(SAMPLE_SIZE)
+ .distance(function (s, t) {
+ return distanceFunction(s, t, props, norm);
+ })
+ .stableVelocity(0.000001)
+ .stableVeloHandler(ended);
+
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)
- // The distance function that will be used to calculate distances
- // between nodes.
- .distance(function (s, t) {
- return distanceFunction(s, t, props, norm);
- })
- .stableVelocity(0.004)
- .stableVeloHandler( function(){simulation.stop(); ended();} )
- );
+ .alphaDecay(0)
+ .alpha(1)
+ .force(forceName, force);
// Restart the simulation.
console.log(simulation.force(forceName).neighbourSize(), simulation.force(forceName).sampleSize());
- simulation.alpha(1).restart();
+ simulation.restart();
}
diff --git a/src/neighbourSamplingDistance.js b/src/neighbourSamplingDistance.js
index 3c862c4..120e5af 100644
--- a/src/neighbourSamplingDistance.js
+++ b/src/neighbourSamplingDistance.js
@@ -2,240 +2,171 @@ import constant from "./constant";
import jiggle from "./jiggle";
import {getStress} from "./stress";
-/**
- * Set the node id accessor to the specified i.
- * @param {node} d - node.
- * @param {accessor} i - id accessor.
- * @return {accessor} - node id accessor.
- */
-function index(d, i) {
- return i;
-}
-
/**
* The implementation of Chalmers' 1996 Neighbour and Sampling algorithm.
* It uses random sampling to find the most suited neighbours from the
* data set.
- * @return {force} calculated forces.
*/
+
+function sortDistances(a, b) {
+ return b[1] - a[1];
+}
+
export default function () {
- var id = index,
- neighbours = [],
- samples = new Array(),
+ var neighbours = [],
distance = constant(300),
nodes,
neighbourSize = 6,
sampleSize = 3,
- //freeness = 0.85,
- //springForce = 0.7,
- //dampingFactor = 0.3,
- velocity,
stableVelocity = 0,
- stableVeloHandler = null;
+ stableVeloHandler = null,
+ dataSizeFactor;
/**
- * Calculates the forces at each iteration between the node and the
- * objects in neighbour and sample sets.
- * @param {number} alpha - controls the stopping of the
- * particle simulations.
+ * Apply spring forces at each iteration.
+ * @param {number} alpha - multiplier for amount of force applied
*/
function force(alpha) {
- let velocityDiff = 0;
- for (let node of nodes) {
- node.oldvx = node.vx;
- node.oldvy = node.vy;
- }
- for (let i = 0, n = nodes.length; i < n; ++i) {
- // Randomize the samples for every node.
- samples[i] = randomizeSample(i);
- // Calculate the forces between node and its neighbours.
- for (let [keyN, valueN] of neighbours[i]) {
- setVelocity(i, keyN, valueN, alpha);
+ let n = nodes.length;
+ // Cache old velocity for comparison later
+ if (stableVeloHandler!==null && stableVelocity!=0) {
+ for (let i = n-1, node; i>=0; i--) {
+ node = nodes[i];
+ node.oldvx = node.vx;
+ node.oldvy = node.vy;
}
- // Calculate the forces between node and its sample set.
- for (let [keyS, valueS] of samples[i]) {
- setVelocity(i, keyS, valueS, alpha);
+ }
+
+ for (let i = n-1, node, samples; i>=0; i--) {
+ node = nodes[i];
+ samples = createRandomSamples(i);
+
+ for (let [neighbourID, highDDist] of neighbours[i]) {
+ setVelocity(node, nodes[neighbourID], highDDist, alpha);
}
- // Check if there are a better neighbours in a sample array
- // for each node.
- findNewNeighbours(i);
+
+ for (let [sampleID, highDDist] of samples) {
+ setVelocity(node, nodes[sampleID], highDDist, alpha);
+ }
+ // Replace neighbours with better ones found in the samples
+ neighbours[i] = findNewNeighbours(neighbours[i], samples);
}
- for (let node of nodes) {
- velocityDiff += Math.abs(node.oldvx - node.vx) + Math.abs(node.oldvy - node.vy);
- delete node.oldvx;
- delete node.oldvy;
+
+ // Calculate velocity changes, aka force applied.
+ if (stableVeloHandler!==null && stableVelocity!=0) {
+ let velocityDiff = 0;
+ for (let i = n-1, node; i>=0; i--) {
+ node = nodes[i];
+ velocityDiff += Math.abs(Math.hypot(node.vx-node.oldvx, node.vy-node.oldvy));
+ }
+ velocityDiff /= n*(neighbourSize+sampleSize);
+
+ if(stableVeloHandler!==null && velocityDiff=0; i--) {
+ let neighbs = pickRandomNodesFor(i, [i], neighbourSize);
// Sort the neighbour set by the distances.
- neighbs = new Map([...neighbs.entries()].sort(sortDistances));
- neighbours[i] = neighbs;
-
- //exclude.concat(neighbs);
- //samples[i] = createRandomSample(i, exclude, n, sampleSize);
- //Samples will be created at the start of each it anyway.
+ neighbours[i] = new Map(neighbs.sort(sortDistances));
}
+
+ initDataSizeFactor();
+ }
+
+ function initDataSizeFactor(){
+ dataSizeFactor = 0.5/(neighbourSize+sampleSize);
}
/**
- * Function that compares to map elements by its values.
- * @param {object} a
- * @param {object} b
- * @return {number} - 0, if values are equal, positive number if b > a,
- * negative otherwise.
- */
- function sortDistances(a, b) {
- return b[1] - a[1];
- }
-
- /**
- * Create an array of random integers, all different, with maximum
- * value max and with size elements. No elements from exlucde should
- * be included.
- * @param {number} index - index of current node.
+ * Generates an array of array[index, high-d distance to the node of index]
+ * where all indices are different and the size is as specified unless
+ * impossible (may be due to too large size requested)
+ * @param {number} index - index of a node to calculate distance against
* @param {array} exclude - indices of the nodes to ignore.
- * @param {number} max - maximum value.
- * @param {number} size - the number of elements in map to return.
- * @return {map} - a created map that contains random elements from
- * data set.
+ * @param {number} size - max number of elements in the map to return.
+ * @return {array}
*/
- function createRandomNeighbours(index, exclude, max, size) {
- let randElements = new Map();
- let triedElements = 0;
+ function pickRandomNodesFor(index, exclude, size) {
+ let randElements = [];
+ let max = nodes.length;
- while ((randElements.size < size) && (randElements.size + exclude.length + triedElements < nodes.length)) {
- let rand = 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.has(rand) || exclude.includes(rand)) {
- rand = Math.floor((Math.random() * max));
- }
- let dist = +distance(nodes[index], nodes[rand]);
- randElements.set(rand, dist);
- }
-
- return randElements;
- }
-
-
- function createRandomSample(index, exclude, max, size) {
- let randElements = new Map();
-
- for (let i = 0; i < size; ++i) {
+ for (let i = 0; i < size; i++) {
// Stop when no new elements can be found.
- if (randElements.size + exclude.length >= nodes.length) {
+ if (randElements.length + exclude.length >= nodes.length) {
break;
}
+
let rand = 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.has(rand) || exclude.includes(rand)) {
+ // Re-random until suitable value is found.
+ while (randElements.includes(rand) || exclude.includes(rand)) {
rand = Math.floor((Math.random() * max));
}
- randElements.set(rand, +distance(nodes[index], nodes[rand]));
+ randElements.push(rand);
+ }
+ for(let i=randElements.length-1, rand; i>=0; i--){
+ rand = randElements[i];
+ randElements[i] = [rand, distance(nodes[index], nodes[rand])];
}
-
return randElements;
}
/**
- * Creates a new map of random numbers to be used by the samples list.
- * @param {number} index - index of current node.
- * @return {map} - map that contains random elements from data set.
+ * Generates a map {index: high-dimensional distance to the node of index}
+ * to be used as samples set for the node of the specified index.
+ * @param {number} index - index of the node to generate sample for
+ * @return {map}
*/
- function randomizeSample(index) {
+ function createRandomSamples(index) {
// Ignore the current neighbours of the node and itself.
let exclude = [index];
exclude = exclude.concat(Array.from(neighbours[index].keys()));
- return createRandomSample(index, exclude, nodes.length, sampleSize);
+ return new Map(pickRandomNodesFor(index, exclude, sampleSize));
}
/**
- * Compares the elements from sample set to the neighbour set and
- * replaces the elements from neighbour set if better neighbours are
- * found in sample set.
- * @param {number} index - index of current node.
+ * Compares the elements from sample set to the neighbour set and replaces the
+ * elements in the neighbour set if any better neighbours are found.
+ * @param {map} neighbours - map of neighbours
+ * @param {map} samples - map of samples
+ * @return {map} - new map of neighbours
*/
- function findNewNeighbours(index) {
- let sample = samples[index];
-
- if (neighbours[index].size > 0) {
- for (let [key, value] of sample) {
- let neighbMax = neighbours[index].entries().next().value;
-
- // Check if a value from sample could be a better neighbour
- // if so, replace it.
- if (value < neighbMax[1]) {
- neighbours[index].delete(neighbMax[0]);
- neighbours[index].set(key, value)
- neighbours[index] = new Map([...neighbours[index].entries()].sort(sortDistances));
- }
- }
- }
+ function findNewNeighbours(neighbours, samples) {
+ let combined = [...neighbours.entries()].concat([...samples.entries()]);
+ combined = combined.sort(sortDistances);
+ return new Map(combined.slice(0, neighbourSize));
}
- /**
- * Calculates the average velocity of the force calculation at the
- * current iteration.
- * @return {number} - average velocity.
- */
- function getAvgVelocity() {
- return velocity / ((neighbourSize + sampleSize) * nodes.length);
- }
-
-
- function getDistributionData() {
- let d = [];
- for (let i = 0; i < nodes.length; i++) {
- d.push({ "index": i, "size": neighbours[i].size });
- }
- return { "maxSize": neighbourSize, "l": nodes.length, "distribution": d };
- }
// API for initializing the algorithm, setting parameters and querying
// metrics.
@@ -244,42 +175,22 @@ export default function () {
initialize();
};
- force.id = function (_) {
- return arguments.length ? (id = _, force) : id;
- };
-
force.neighbourSize = function (_) {
- return arguments.length ? (neighbourSize = +_, force) : neighbourSize;
+ return arguments.length ? (neighbourSize = +_, initDataSizeFactor(), force) : neighbourSize;
};
force.sampleSize = function (_) {
- return arguments.length ? (sampleSize = +_, force) : sampleSize;
+ return arguments.length ? (sampleSize = +_, initDataSizeFactor(), force) : sampleSize;
};
force.distance = function (_) {
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance;
};
- force.stress = function () {
- return getStress(nodes, distance);
- };
-
force.velocity = function () {
return getAvgVelocity();
};
- /*force.freeness = function (_) {
- return arguments.length ? (freeness = +_, force) : freeness;
- };*/
-
- force.nodeNeighbours = function (_) {
- return arguments.length ? neighbours[+_] : neighbours;
- };
-
- force.distributionData = function () {
- return getDistributionData();
- };
-
force.stableVeloHandler = function (_) {
return arguments.length ? (stableVeloHandler = _, force) : stableVeloHandler;
};