Revamp neighbor sampling, shredding unused features and correct calculations
This commit is contained in:
@@ -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<stableVelocity){
|
||||
console.log("Neighbour sampling is now", velocityDiff, ", calling stableVeloHandler().")
|
||||
stableVeloHandler();
|
||||
}
|
||||
else console.log(velocityDiff);
|
||||
}
|
||||
velocityDiff /= nodes.length*(neighbourSize+sampleSize);
|
||||
// Now total velocity changes per link, alpha considered
|
||||
// TODO per property too
|
||||
if(stableVeloHandler!==null && velocityDiff<stableVelocity){
|
||||
console.log("Neighbour sampling is now", velocityDiff, ", calling stableVeloHandler().")
|
||||
stableVeloHandler();
|
||||
}
|
||||
else console.log(velocityDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the velocities of the source and target nodes.
|
||||
* @param {number} sourceId - source node id.
|
||||
* @param {number} targetId - target node id.
|
||||
* @param {number} dist - high dimensional distance between
|
||||
* the two nodes.
|
||||
* @param {number} alpha - controls the speed of simulation.
|
||||
* Apply force to both source and target nodes.
|
||||
* @param {number} source - source node object
|
||||
* @param {number} target - target node object
|
||||
* @param {number} dist - high dimensional distance between the two nodes
|
||||
* @param {number} alpha - multiplier for the amount of force applied
|
||||
*/
|
||||
function setVelocity(sourceId, targetId, dist, alpha) {
|
||||
let source, target, x, y, l;
|
||||
source = nodes[sourceId], target = nodes[targetId];
|
||||
// If x or y coordinates not defined, add some randomness.
|
||||
function setVelocity(source, target, dist, alpha) {
|
||||
let x, y, l;
|
||||
// jiggle so it wont divide / multiply by zero after this
|
||||
x = target.x + target.vx - source.x - source.vx || jiggle();
|
||||
y = target.y + target.vy - source.y - source.vy || jiggle();
|
||||
l = Math.sqrt(x * x + y * y);
|
||||
l = (l - dist) / l * alpha;
|
||||
l = (l - dist) / l * dataSizeFactor * alpha;
|
||||
x *= l, y *= l;
|
||||
// Set the calculated velocites for both nodes.
|
||||
target.vx -= x*0.5;
|
||||
target.vy -= y*0.5;
|
||||
source.vx += x*0.5;
|
||||
source.vy += y*0.5;
|
||||
target.vx -= x;
|
||||
target.vy -= y;
|
||||
source.vx += x;
|
||||
source.vy += y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the neighbour and sample set at the start.
|
||||
*/
|
||||
function initialize() {
|
||||
if (!nodes) return;
|
||||
|
||||
// Initialize for each node a neighbour and sample arrays
|
||||
// with random values.
|
||||
for (let i = 0, n = nodes.length; i < n; ++i) {
|
||||
let exclude = []; // Array that keeps the indices of nodes to ignore.
|
||||
exclude.push(i);
|
||||
|
||||
let neighbs = createRandomNeighbours(i, exclude, n, neighbourSize);
|
||||
// Initialize for each node some random neighbours.
|
||||
for (let i = nodes.length-1; i>=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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user