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 @@ 3
-
- 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; };