From 6b53b3301d10851c46ee995f5340a938aa8c84f8 Mon Sep 17 00:00:00 2001 From: Pitchaya Boonsarngsuk <2285135b@student.gla.ac.uk> Date: Wed, 24 Jan 2018 08:55:49 +0000 Subject: [PATCH] Change velocity-difference threshold calculation method and misc small non-functional changes (PROPER) --- .../linkForce.js | 1 + .../neighbourSampling.js | 2 +- src/hybridSimulation.js | 13 +- src/interpolation/interpBruteForce.js | 3 +- src/interpolation/interpCommon.js | 7 +- src/interpolation/interpolationPivots.js | 325 ++++-------------- src/neighbourSamplingDistance.js | 23 +- 7 files changed, 95 insertions(+), 279 deletions(-) diff --git a/examples/js/src/neighbourSampling-papaparsing/linkForce.js b/examples/js/src/neighbourSampling-papaparsing/linkForce.js index 7e05dc5..27a6e2c 100644 --- a/examples/js/src/neighbourSampling-papaparsing/linkForce.js +++ b/examples/js/src/neighbourSampling-papaparsing/linkForce.js @@ -27,6 +27,7 @@ function startLinkSimulation() { simulation .alphaDecay(1 - Math.pow(0.001, 1 / ITERATIONS)) + //.velocityDecay(0.8) .force(forceName) // The distance function that will be used to calculate distances // between nodes. diff --git a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js index 0fc01dd..5636d6b 100644 --- a/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js +++ b/examples/js/src/neighbourSampling-papaparsing/neighbourSampling.js @@ -20,7 +20,7 @@ function startNeighbourSamplingSimulation() { .distance(function (s, t) { return distanceFunction(s, t, props, norm); }) - .stableVelocity(1.2) + .stableVelocity(0.004) .stableVeloHandler( function(){simulation.stop(); ended();} ) ); // Restart the simulation. diff --git a/src/hybridSimulation.js b/src/hybridSimulation.js index a11cffc..9eb38d4 100644 --- a/src/hybridSimulation.js +++ b/src/hybridSimulation.js @@ -39,7 +39,7 @@ export default function (nodes, config) { .sampleSize(sampleSize) .distanceRange(distanceRange) .distance(distanceFn) - .stableVelocity(60) + .stableVelocity(0.004) .stableVeloHandler(function(){sampleSimulation.stop(); ended();}) ) .alpha(1).restart(); @@ -52,14 +52,11 @@ export default function (nodes, config) { event.call("startFull"); console.log("Ended sample simulation"); alert('About to interpolate'); - interpBruteForce(sample, remainder, distanceFn); - /* if (PIVOTS) { - interpolationPivots(sample, remainder, sampleSubSet, NUMPIVOTS, distance); + interpolationPivots(sample, remainder, NUMPIVOTS, distanceFn); } else { - interpBruteForce(sample, remainder, sampleSubSet, distance); + interpBruteForce(sample, remainder, distanceFn); } - */ event.call("fullTick"); alert('About to Full run'); if (FULL_ITERATIONS==0) { @@ -77,7 +74,7 @@ export default function (nodes, config) { .sampleSize(FullsampleSize) .distanceRange(FulldistanceRange) .distance(distanceFn) - .stableVelocity(60) + .stableVelocity(0.004) .stableVeloHandler(function(){fullSimulation.stop(); event.call("end");}) ) .on("tick", function () { @@ -152,5 +149,5 @@ export default function (nodes, config) { } function readConf(config, prop, def){ - return config.hasOwnProperty(prop) ? config.prop : def; + return config.hasOwnProperty(prop) ? config[prop] : def; } diff --git a/src/interpolation/interpBruteForce.js b/src/interpolation/interpBruteForce.js index 8f6a668..a7d6d94 100644 --- a/src/interpolation/interpBruteForce.js +++ b/src/interpolation/interpBruteForce.js @@ -7,8 +7,7 @@ export default function(sampleSet, remainderSet, distanceFn) { let sampleSubset = takeSampleFrom(sampleSet, Math.sqrt(sampleSet.length)).sample; for (let node of remainderSet) { - let nearestSample = undefined, - minDist = undefined, + let nearestSample, minDist, sampleSubsetDistanceCache = []; for (let sample of sampleSet) { diff --git a/src/interpolation/interpCommon.js b/src/interpolation/interpCommon.js index 82d15a7..bf8234b 100644 --- a/src/interpolation/interpCommon.js +++ b/src/interpolation/interpCommon.js @@ -48,11 +48,12 @@ export function placeNearToNearestNeighbour(node, nearNeighbour, radius, sampleS node.x = newPoint.x; node.y = newPoint.y; + let sumForces; // Common temp variables, avoid GC for (let i = 0; i < 20; i++) { - let forces = sumForcesToSample(node, sampleSubset, realDistances); + sumForces = sumForcesToSample(node, sampleSubset, realDistances); //console.log(forces); - node.x += forces.x*0.5; - node.y += forces.y*0.5; + node.x += sumForces.x*0.5; + node.y += sumForces.y*0.5; } } diff --git a/src/interpolation/interpolationPivots.js b/src/interpolation/interpolationPivots.js index ea6cc53..0ccd076 100644 --- a/src/interpolation/interpolationPivots.js +++ b/src/interpolation/interpolationPivots.js @@ -1,287 +1,98 @@ -export default function(sampleSet, remainderSet, interpSubset, nPivots, distanceFunction) { - var distance = distanceFunction; +import {pointOnCircle, takeSampleFrom} from "./helpers"; +import {placeNearToNearestNeighbour} from "./interpCommon"; +export default function(sampleSet, remainderSet, numPivots, distanceFn) { // Pivot based parent finding + let numBuckets = Math.floor(Math.sqrt(sampleSet.length)); + let pivots = takeSampleFrom(sampleSet, numPivots); + console.log("Pivots", pivots); + // Common temporary variables + let i, j, pivot, sample, bucketWidth; // Temp var, declared seperately to avoid GC - var numBuckets = Math.floor(Math.sqrt(sampleSet.length)), - numPivots = nPivots, - parents = [], - maxDists = [], - bucketWidths = [], - pivotsBuckets = []; + let pivotsBuckets = []; // [ For each Pivot:[For each bucket:[each point in bucket]] ] - console.log("Parents, pivots=", numPivots); - - var pivots = createRandomSample(sampleSet.concat(remainderSet), sampleSet.length, numPivots); - - for (var i = 0; i < numPivots; i++) { + for (i = 0; i < numPivots; i++) { pivotsBuckets[i] = []; - for (var j = 0; j < numBuckets; j++) { + for (j = 0; j < numBuckets; j++) { pivotsBuckets[i][j] = []; } } - // Pre-processing - var fullDists = [] - for (var i = 0; i < sampleSet.length; i++) { - fullDists[i] = []; - } + // Pre-calculate distance between each sample to each pivot + let distCache = [], // [ For each Sample:[For each Pivot: distance] ] + bucketWidths = []; // [ For each Pivot: width of each bucket ] - for (var j = 0, maxDist = -1; j < numPivots; j++) { - var c1 = pivots[j]; - for (var i = 0; i < sampleSet.length; i++) { - var c2 = sampleSet[i]; - if (c1 !== c2) { - var dist = distance(c1, c2, props, norm); - // console.log(dist, c1, c2); - if (dist > maxDist) { - maxDist = dist; - } - fullDists[i][j] = dist; + for (i = 0; i < sampleSet.length; i++) + distCache[i] = []; + + for (j = 0; j < numPivots; j++) { + pivot = pivots[j], + maxDist = -1; + + for (i = 0; i < sampleSet.length; i++) { + sample = sampleSet[i]; + if (pivot !== sample) { + distCache[i][j] = distanceFn(pivot, sample); + if (distCache[i][j] > maxDist) + maxDist = distCache[i][j]; } else { - fullDists[i][j] = 0.0001; + distCache[i][j] = 0; } } - maxDists.push(maxDist); + bucketWidths.push(maxDist / numBuckets); } - // console.log(fullDists); + // console.log(distCaches); - for (var j = 0; j < numPivots; j++) { - var bucketWidth = bucketWidths[j]; - for (var i = 0; i < sampleSet.length; i++) { - var tmp = pivotsBuckets[j][Math.floor((fullDists[i][j] - 0.0001) / bucketWidth)]; - // pivotsBuckets[j][Math.floor((fullDists[i][j] - 0.0001) / bucketWidth)].push(sampleSet[i]); - // console.log(tmp, i, j, bucketWidth, Math.floor((fullDists[i][j] - 0.0001) / bucketWidth)); - tmp.push(sampleSet[i]); + // Put samples (pivot included) into buckets + for (j = 0; j < numPivots; j++) { + bucketWidth = bucketWidths[j]; + for (i = 0; i < sampleSet.length; i++) { + sample = sampleSet[i]; + pivotsBuckets[j][Math.floor(distCache[i][j] / bucketWidth)].push(sample); } } - for (var i = 0; i < remainderSet.length; i++) { - var node = remainderSet[i], - minNode = sampleSet[0], - minDist = 0, - sampleCache = []; + + //Plot each of the remainder nodes + for (let node of remainderSet) { + let sampleSubsetDistanceCache = [], + sampleSubset = takeSampleFrom(sampleSet, Math.sqrt(sampleSet.length)).sample; // Pivot based parent search + for (let p = 0; p < numPivots; p++) { + let pivot = pivots[p]; + let bucketWidth = bucketWidths[p]; - var node = remainderSet[i]; - var clDist = Number.MAX_VALUE; - for (var p = 0; p < numPivots; p++) { - var comp = pivots[p]; - var bucketWidth = bucketWidths[p]; - if (node !== comp) { - var dist = distance(node, comp, props, norm); - var bNum = Math.floor((dist - 0.0001) / bucketWidth); - if (bNum >= numBuckets) { - bNum = numBuckets - 1; - } else if (bNum < 0) { - bNum = 0; - } - var bucketContents = pivotsBuckets[p][bNum]; - for (var w = 0; w < bucketContents.length; w++) { - var c1 = bucketContents[w]; - if (c1 != node) { - dist = distance(c1, node, props, norm); - if (dist <= clDist) { - clDist = dist; - minNode = bucketContents[w]; - } - } + let dist = distanceFn(node, pivot); + let index = sampleSubset.indexOf(pivot); + if (index !== -1) + sampleSubsetDistanceCache[index] = dist; + + let bucketNumber = Math.floor(dist / bucketWidth); + if (bucketNumber >= numBuckets) { + bucketNumber = numBuckets - 1; + } else if (bucketNumber < 0) { // Should never be negative anyway + bucketNumber = 0; + } + + let clDist, minNode; + for (let candidateNode of pivotsBuckets[p][bucketNumber]) { + + dist = distanceFn(candidateNode, node); + if (candidateNode <= clDist) { + clDist = candidateNode; + minNode = bucketContents[w]; } } } - - for (var k = 0; k < interpSubset.length; k++) { - sampleCache[k] = distance(node, interpSubset[k], props, norm); + for (let k = 0; k < sampleSubset.length; k++) { + if (sampleSubsetDistanceCache[k] !== undefined) + sampleSubsetDistanceCache[k] = distanceFn(node, sampleSubset[k]); } - var radius = distance(node, minNode, props, norm); - placeNearToNearestNeighbour(node, minNode, interpSubset, sampleCache, radius); + let radius = distanceFn(node, minNode); + placeNearToNearestNeighbour(node, minNode, radius, sampleSubset, sampleSubsetDistanceCache); } } - - -function placeNearToNearestNeighbour(node, minNode, sample, sampleCache, radius) { - var - dist0 = 0.0, - dist90 = 0.0, - dist180 = 0.0, - dist270 = 0.0, - lowBound = 0.0, - highBound = 0.0; - - dist0 = sumDistToSample(node, centerPoint(0, radius, minNode.x, minNode.y), sample, sampleCache); - dist90 = sumDistToSample(node, centerPoint(90, radius, minNode.x, minNode.y), sample, sampleCache); - dist180 = sumDistToSample(node, centerPoint(180, radius, minNode.x, minNode.y), sample, sampleCache); - dist270 = sumDistToSample(node, centerPoint(270, radius, minNode.x, minNode.y), sample, sampleCache); - - // console.log(dist0, dist90, dist180, dist270); - - // 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; - } - } - - var angle = binarySearch(lowBound, highBound, minNode.x, minNode.y, radius, node, sample, sampleCache); - var newPoint = centerPoint(angle, radius, minNode.x, minNode.y); - - // console.log(newPoint); - node.x = newPoint.x; - node.y = newPoint.y; - - // for (var i = 0; i < 20; i++) { - // var forces = sumForcesToSample(node, sample, sampleCache); - // // console.log(forces); - // node.x += forces.x; - // node.y += forces.y; - // } - -} - - -function centerPoint(angle, radius, posX, posY) { - var x = posX + Math.cos(toRadians(angle) * radius); - var y = posY + Math.sin(toRadians(angle) * radius); - - return { - x: x, - y: y - }; -} - -function toRadians(degrees) { - return degrees * (Math.PI / 180); -} - -function sumDistToSample(node, point, sample, sampleCache) { - var total = 0.0; - // console.log(total, sample); - - for (var i = 0; i < sample.length; i++) { - var s = sample[i]; - var realDist = Math.hypot(s.x - point.x, s.y - point.y); - var desDist = sampleCache[i]; - total += Math.abs(realDist - desDist); - } - - return total; -} - - -function sumForcesToSample(node, sample, sampleCache) { - var x = 0, - y = 0, - // len = 0, - dist = 0, - force, - SPRING_FORCE = 0.7; - - for (var i = 0, unitX, unitY; i < sample.length; i++) { - var s = sample[i]; - if (s !== node) { - unitX = s.x - node.x; - unitY = s.y - node.y; - - // Normalize coordinates - len = Math.sqrt(unitX * unitX + unitY * unitY); - unitX /= len; - unitY /= len; - - console.log(unitX, unitY); - - var realDist = Math.sqrt(unitX * unitX + unitY * unitY); - var desDist = sampleCache[i]; - dist += realDist - desDist; - force = (SPRING_FORCE * dist); - - x += unitX * force; - y += unitY * force; - } - - x *= (1.0 / sample.length); - y *= (1.0 / sample.length); - - return { - x: x, - y: y - }; - } -} - -function createRandomSample(nodes, max, size) { - var randElements = []; - - for (var i = 0; i < size; ++i) { - // Stop when no new elements can be found. - if (randElements.size >= nodes.length) { - break; - } - var 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.includes(rand)) { - rand = Math.floor((Math.random() * max)); - } - randElements.push(nodes[rand]); - } - - return randElements; -} - - -function binarySearch(lb, hb, x, y, r, node, sample, sampleCache) { - while (lb <= hb) { - var mid = Math.round((lb + hb) / 2); - - if ((mid === lb) || (mid === hb)) { - if (sumDistToSample(node, centerPoint(lb, r, x, y), sample, sampleCache) >= - sumDistToSample(node, centerPoint(hb, r, x, y), sample, sampleCache)) { - return hb; - } else { - return lb; - } - } else { - var distMidLeft = sumDistToSample(node, centerPoint(mid + 1, r, x, y), sample, sampleCache); - var distMidRight = sumDistToSample(node, centerPoint(mid - 1, r, x, y), sample, sampleCache); - var distMid = sumDistToSample(node, centerPoint(mid, r, x, y), sample, sampleCache); - - if (distMid > distMidLeft) { - lb = mid + 1; - } else if (distMid > distMidRight) { - hb = mid - 1; - } else { - return mid; - } - } - } - - return -1; -} diff --git a/src/neighbourSamplingDistance.js b/src/neighbourSamplingDistance.js index 7f02062..2bbc24c 100644 --- a/src/neighbourSamplingDistance.js +++ b/src/neighbourSamplingDistance.js @@ -41,7 +41,11 @@ export default function () { * particle simulations. */ function force(alpha) { - velocity = 0; + 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); @@ -57,15 +61,19 @@ export default function () { // for each node. findNewNeighbours(i); } - velocity /= nodes.length*(neighbours.length+samples.length)*alpha; - // Now total velocity changes per link, alpha not considered + for (let node of nodes) { + velocityDiff += Math.abs(node.oldvx - node.vx) + Math.abs(node.oldvy - node.vy); + delete node.oldvx; + delete node.oldvy; + } + velocityDiff /= nodes.length*(neighbourSize+sampleSize); + // Now total velocity changes per link, alpha considered // TODO per property too - stableVelocity = 0; - if(Math.abs(velocity)