diff --git a/html/admin/js/graphViewer/graph/modularityJoiner.js b/html/admin/js/graphViewer/graph/modularityJoiner.js index e369a3ed80..383d2c5a8f 100644 --- a/html/admin/js/graphViewer/graph/modularityJoiner.js +++ b/html/admin/js/graphViewer/graph/modularityJoiner.js @@ -1,4 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ +/*global _*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// @@ -70,9 +71,9 @@ function ModularityJoiner(nodes, edges) { a = {}; _.each(nodes, function (n) { a[n._id] = { - in: n._inboundCounter / m, - out: n._outboundCounter / m - } + _in: n._inboundCounter / m, + _out: n._outboundCounter / m + }; }); return a; }, @@ -83,12 +84,12 @@ function ModularityJoiner(nodes, edges) { _.each(ts, function(tar) { if (src < tar) { dQ[src] = dQ[src] || {}; - dQ[src][tar] = (dQ[src][tar] || 0) + revM - a[src].in * a[tar].out; + dQ[src][tar] = (dQ[src][tar] || 0) + revM - a[src]._in * a[tar]._out; return; } if (tar < src) { dQ[tar] = dQ[tar] || {}; - dQ[tar][src] = (dQ[tar][src] || 0) + revM - a[src].in * a[tar].out; + dQ[tar][src] = (dQ[tar][src] || 0) + revM - a[src]._in * a[tar]._out; return; } }); @@ -110,12 +111,12 @@ function ModularityJoiner(nodes, edges) { } if (s < t) { dQ[s] = dQ[s] || {}; - dQ[s][t] = (dQ[s][t] || 0) + ast * revM - a[s].in * a[t].out; + dQ[s][t] = (dQ[s][t] || 0) + ast * revM - a[s]._in * a[t]._out; return; } if (t < s) { dQ[t] = dQ[t] || {}; - dQ[t][s] = (dQ[t][s] || 0) + ast * revM - a[s].in * a[t].out; + dQ[t][s] = (dQ[t][s] || 0) + ast * revM - a[s]._in * a[t]._out; return; } }); diff --git a/html/admin/js/graphViewer/graph/nodeReducer.js b/html/admin/js/graphViewer/graph/nodeReducer.js index 7c687c287a..738a0e6257 100644 --- a/html/admin/js/graphViewer/graph/nodeReducer.js +++ b/html/admin/js/graphViewer/graph/nodeReducer.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ -/*global $, d3, _, console, alert*/ +/*global $, d3, _, console, alert, ModularityJoiner*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// @@ -38,325 +38,92 @@ function NodeReducer(nodes, edges) { } var self = this, - getDegree = function(id) { - return $.grep(nodes, function(e){ - return e._id === id; - })[0]; + joiner = new ModularityJoiner(nodes, edges), + + neighbors = function(sID) { + var neigh = []; + _.each(edges, function(e) { + if (e.source._id === sID) { + neigh.push(e.target._id); + return; + } + if (e.target._id === sID) { + neigh.push(e.source._id); + return; + } + }); + return neigh; }, - - populateValues = function(dQ, a, heap) { - var m = edges.length, - twoM = 2 * m; - _.each(nodes, function(n) { - a[n._id] = { - in: n._inboundCounter / m, - out: n._outboundCounter / m - }; - }); - - _.each(edges, function(e) { - var sID, lID; - if (e.source._id < e.target._id) { - sID = e.source._id; - lID = e.target._id; - } else { - sID = e.target._id; - lID = e.source._id; - } - if (dQ[sID] === undefined) { - dQ[sID] = {}; - heap[sID] = {}; - heap[sID].val = Number.NEGATIVE_INFINITY; - } - dQ[sID][lID] = (dQ[sID][lID] || 0) + 1 / twoM - a[e.source._id].in * a[e.target._id].out; - if (heap[sID].val < dQ[sID][lID]) { - heap[sID].val = dQ[sID][lID]; - heap[sID].sID = sID; - heap[sID].lID = lID; - } - }); - console.log(dQ); - }, - - // Will Overwrite dQ, a and heap! - // Old undirected version - populateValuesUndirected = function(dQ, a, heap) { - var m = edges.length, - twoM = 2 * m; - _.each(nodes, function(n) { - a[n._id] = n._outboundCounter / m; - }); - - _.each(edges, function(e) { - var sID, lID; - if (e.source._id < e.target._id) { - sID = e.source._id; - lID = e.target._id; - } else { - sID = e.target._id; - lID = e.source._id; - } - if (dQ[sID] === undefined) { - dQ[sID] = {}; - heap[sID] = {}; - heap[sID].val = Number.NEGATIVE_INFINITY; - } - dQ[sID][lID] = (dQ[sID][lID] || 0) + 1 / twoM - a[sID] * a[lID]; - if (heap[sID].val < dQ[sID][lID]) { - heap[sID].val = dQ[sID][lID]; - heap[sID].sID = sID; - heap[sID].lID = lID; - } - }); - console.log(dQ); - }, - - getLargestOnHeap = function(heap) { - return _.max(heap, function(e) { - return e.val; - }); - }, - - joinCommunities = function(sID, lID, coms, heap, val) { - coms[sID] = coms[sID] || {com: [sID], q: 0}; - coms[lID] = coms[lID] || {com: [lID], q: 0}; - var old = coms[sID].com, - newC = coms[lID].com; - coms[sID].com = old.concat(newC); - coms[sID].q += coms[sID].q + val; - delete coms[lID]; - }, - - getDQValue = function(dQ, a, b) { - if (a < b) { - if (dQ[a] !== undefined) { - return dQ[a][b]; - } - return undefined; - } - if (dQ[b] !== undefined) { - return dQ[b][a]; - } - return undefined; - }, - - setDQValue = function(dQ, heap, a, b, val) { - if (a < b) { - if(dQ[a] === undefined) { - dQ[a] = {}; - heap[a] = {}; - heap[a].val = -1; - } - dQ[a][b] = val; - if (heap[a].val < val) { - heap[a].val = val; - heap[a].sID = a; - heap[a].lID = b; + floatDistStep = function(dist, depth, todo) { + if (todo.length === 0) { + return true; } - } else { - if(dQ[b] === undefined) { - dQ[b] = {}; - heap[b] = {}; - heap[b].val = -1; - } - dQ[b][a] = val; - if (heap[b].val < val) { - heap[b].val = val; - heap[b].sID = b; - heap[b].lID = a; + var nextTodo = []; + _.each(todo, function(t) { + if (dist[t] !== Number.POSITIVE_INFINITY) { + return; + } + dist[t] = depth; + nextTodo = nextTodo.concat(neighbors(t)); + }); + return floatDistStep(dist, depth+1, nextTodo); + }, + + floatDist = function(sID) { + var dist = {}; + _.each(_.pluck(nodes, "_id"), function(n) { + dist[n] = Number.POSITIVE_INFINITY; + }); + dist[sID] = 0; + if (floatDistStep(dist, 1, neighbors(sID))) { + return dist; } - } - }, - - delDQValue = function(dQ, a, b) { - if (a < b) { - delete dQ[a][b]; - } else { - delete dQ[b][a]; - } - }, - - calcDQValue2 = function(a, i, j, k, vi, vj) { - if (vi !== undefined || vj !== undefined) { - if (vi !== undefined) { - if (vj !== undefined) { - return vi + vj; - } else { - return vi - 2 * a[j] * a[k]; - } - } else { - return vj - 2 * a[i] * a[k]; - } - } - }, - - updateHeap = function(dQ, heap) { - _.each(heap, function (last, k) { - var l = dQ[k], - max = Number.NEGATIVE_INFINITY, - maxId; - if (l) { - _.each(l, function (v, id) { - if (max < v) { - max = v; - maxId = id; - } - }); - heap[k] = { - sID: k, - lID: maxId, - val: max - } - } else { - delete heap[k]; - } - }); - }, - - updateValues = function(largest, dQ, a, heap) { - var lID = largest.lID, - sID = largest.sID, - listS = dQ[sID] || {}, - listL = dQ[lID] || {}; - _.each(dQ, function (l, k) { - var c1, c2; - if (k < sID) { - l[sID] = calcDQValue2(a, sID, lID, k, l[sID], l[lID]); - delete l[lID]; - return; - } else if (k === sID) { - delete l[lID]; - _.each(l, function(val, key) { - if (key < lID) { - if (dQ[key] && dQ[key][lID] !== undefined) { - l[key] = calcDQValue2(a, sID, lID, key, val, dQ[key][lID]); - delete dQ[key][lID]; - } else { - l[key] = calcDQValue2(a, sID, lID, key, val, undefined); - } - return; - } else { - if (dQ[lID] && dQ[lID][key]) { - l[key] = calcDQValue2(a, sID, lID, key, val, listL[key]); - delete listL[key]; - } else { - l[key] = calcDQValue2(a, sID, lID, key, val, undefined); - } - return; - } - }); - return; - } else if (k < lID) { - listS[k] = calcDQValue2(a, sID, lID, k, undefined, l[lID]); - } else if (lID === k) { - _.each(listL, function(val, key) { - listS[key] = calcDQValue2(a, sID, lID, k, undefined, listL[key]); - }); - } - }); - delete dQ[lID]; - a[sID] += a[sID]; - delete a[lID]; - updateHeap(dQ, heap); - }, - - sortBySize = function(a, b) { - return b.length - a.length; - }, - - neighbors = function(sID) { - var neigh = []; - _.each(edges, function(e) { - if (e.source._id === sID) { - neigh.push(e.target._id); - return; - } - if (e.target._id === sID) { - neigh.push(e.source._id); - return; - } - }); - return neigh; - }, - - minDist = function(dist) { - return function(a) { - return dist[a]; - }; - }, - + throw "FAIL!"; + }, + + minDist = function(dist) { + return function(a) { + return dist[a]; + }; + }, + + +/* dijkstra = function(sID) { var dist = {}, - toDo, next, neigh, + leftOvers, next, neigh, filterNotNext = function(e) { return e !== next; }, computeNeighbours = function(v) { - if (_.contains(toDo, v)) { + if (_.contains(leftOvers, v)) { if (dist[v] > dist[next] + 1) { dist[v] = dist[next] + 1; } } }; - toDo = _.pluck(nodes, "_id"); - _.each(toDo, function(n) { + leftOvers = _.pluck(nodes, "_id"); + _.each(leftOvers, function(n) { dist[n] = Number.POSITIVE_INFINITY; }); dist[sID] = 0; - while(toDo.length > 0) { - next = _.min(toDo, minDist(dist)); + while(leftOvers.length > 0) { + next = _.min(leftOvers, minDist(dist)); if (next === Number.POSITIVE_INFINITY) { break; } - toDo = toDo.filter(filterNotNext); + leftOvers = leftOvers.filter(filterNotNext); neigh = neighbors(next); _.each(neigh, computeNeighbours); } return dist; }, - floatDistStep = function(dist, depth, todo) { - if (todo.length === 0) { - return true; - } - var nextTodo = []; - _.each(todo, function(t) { - if (dist[t] !== Number.POSITIVE_INFINITY) { - return; - } - dist[t] = depth; - nextTodo = nextTodo.concat(neighbors(t)); - }); - return floatDistStep(dist, depth+1, nextTodo); - }, - - floatDist = function(sID) { - var dist = {}; - _.each(_.pluck(nodes, "_id"), function(n) { - dist[n] = Number.POSITIVE_INFINITY; - }); - dist[sID] = 0; - if (floatDistStep(dist, 1, neighbors(sID))) { - return dist; - } - throw "FAIL!"; - }, - - communityDetectionStep = function(dQ, a, heap, coms) { - var l = getLargestOnHeap(heap); - if (l === Number.NEGATIVE_INFINITY || l.val < 0) { - return false; - } - joinCommunities(l.sID, l.lID, coms, heap, l.val); - updateValues(l, dQ, a, heap); - return true; - }, - + */ addNode = function(bucket, node) { bucket.push(node); - }, getSimilarityValue = function(bucket, node) { @@ -380,23 +147,19 @@ function NodeReducer(nodes, edges) { countMatch++; return countMatch / propCount; }; - + self.getCommunity = function(limit, focus) { - var dQ = {}, - a = {}, - heap = {}, - coms = {}, + var coms = {}, res = [], dist = {}, - dist2 = {}, - detectSteps = true, + best, getBest = function (communities) { var bestQ = Number.NEGATIVE_INFINITY, bestC; _.each(communities, function (obj) { if (obj.q > bestQ) { bestQ = obj.q; - bestC = obj.com; + bestC = obj.nodes; } }); return bestC; @@ -413,33 +176,26 @@ function NodeReducer(nodes, edges) { if (nodes.length === 0 || edges.length === 0) { throw "Load some nodes first."; } - populateValues(dQ, a, heap); - while (detectSteps) { - detectSteps = communityDetectionStep(dQ, a, heap, coms); + joiner.setup(); + best = joiner.getBest(); + while (best !== null) { + joiner.joinCommunity(best); + best = joiner.getBest(); } - console.log(coms); - + coms = joiner.getCommunities(); + if (focus !== undefined) { _.each(coms, function(obj, key) { - if (obj.com.length === 1) { - delete coms[key]; - } - if (_.contains(obj.com, focus._id)) { + if (_.contains(obj.nodes, focus._id)) { delete coms[key]; } }); - res = _.pluck(_.values(coms), "com"); + res = _.pluck(_.values(coms), "nodes"); dist = floatDist(focus._id); res.sort(sortByDistance); return res[0]; - } else { - _.each(coms, function(obj, key) { - if (obj.com.length === 1) { - delete coms[key]; - } - }); - return getBest(coms); } + return getBest(coms); }; self.bucketNodes = function(toSort, numBuckets) { diff --git a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/modularityJoinerSpec.js b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/modularityJoinerSpec.js index b56dd5dec6..11d7ea8a8e 100644 --- a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/modularityJoinerSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/modularityJoinerSpec.js @@ -4,7 +4,7 @@ /*global window, eb, loadFixtures, document */ /*global $, _, d3*/ /*global helper*/ -/*global NodeReducer*/ +/*global ModularityJoiner*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality @@ -111,7 +111,7 @@ return true; } }); - }) + }); it('should offer the adjacency matrix', function() { expect("getAdjacencyMatrix").toBeGetter(); @@ -209,20 +209,20 @@ three = 3 / m; initDeg = { "0": { - in: one, - out: two + _in: one, + _out: two }, "1": { - in: three, - out: one + _in: three, + _out: one }, "2": { - in: two, - out: two + _in: two, + _out: two }, "3": { - in: two, - out: three + _in: two, + _out: three } }; }); @@ -261,34 +261,34 @@ beforeEach(function() { m = edges.length; zero = { - in: 1/m, - out: 2/m + _in: 1/m, + _out: 2/m }; one = { - in: 3/m, - out: 1/m + _in: 3/m, + _out: 1/m }; two = { - in: 2/m, - out: 2/m + _in: 2/m, + _out: 2/m }; three = { - in: 2/m, - out: 3/m + _in: 2/m, + _out: 3/m }; initDQ = { "0": { - "1": 1/m - zero.in * one.out - zero.out * one.in, - "2": - zero.in * two.out - zero.out * two.in, - "3": 2/m - zero.in * three.out - zero.out * three.in + "1": 1/m - zero._in * one._out - zero._out * one._in, + "2": - zero._in * two._out - zero._out * two._in, + "3": 2/m - zero._in * three._out - zero._out * three._in }, "1": { - "2": 2/m - one.in * two.out - one.out * two.in, - "3": 1/m - one.in * three.out - one.out * three.in + "2": 2/m - one._in * two._out - one._out * two._in, + "3": 1/m - one._in * three._out - one._out * three._in }, "2": { - "3": 2/m - two.in * three.out - two.out * three.in - }, + "3": 2/m - two._in * three._out - two._out * three._in + } }; }); @@ -382,25 +382,25 @@ */ describe('the heap', function() { - var m, zero, one, two, three + var m, zero, one, two, three; beforeEach(function() { m = edges.length; zero = { - in: 1/m, - out: 2/m + _in: 1/m, + _out: 2/m }; one = { - in: 3/m, - out: 1/m + _in: 3/m, + _out: 1/m }; two = { - in: 2/m, - out: 2/m + _in: 2/m, + _out: 2/m }; three = { - in: 2/m, - out: 3/m + _in: 2/m, + _out: 3/m }; }); @@ -416,7 +416,7 @@ expect(joiner.getBest()).toEqual({ sID: "0", lID: "3", - val: 2/m - zero.in * three.out - zero.out * three.in + val: 2/m - zero._in * three._out - zero._out * three._in }); }); @@ -447,7 +447,7 @@ expect(joiner.getBest()).toEqual({ sID: "1", lID: "2", - val: 2/m - one.in * two.out - one.out * two.in + val: 2/m - one._in * two._out - one._out * two._in }); }); @@ -483,20 +483,20 @@ expected = {}, m = edges.length, zero = { - in: 1/m, - out: 2/m + _in: 1/m, + _out: 2/m }, one = { - in: 3/m, - out: 1/m + _in: 3/m, + _out: 1/m }, two = { - in: 2/m, - out: 2/m + _in: 2/m, + _out: 2/m }, three = { - in: 2/m, - out: 3/m + _in: 2/m, + _out: 3/m }; expected[lowId] = { nodes: [ @@ -508,7 +508,7 @@ expect(toJoin).toEqual({ sID: "0", lID: "3", - val: 2/m - zero.in * three.out - zero.out * three.in + val: 2/m - zero._in * three._out - zero._out * three._in }); joiner.joinCommunity(toJoin); expect(joiner.getCommunities()).toEqual(expected); @@ -635,11 +635,10 @@ o.nodes = _.filter(o.nodes, function(n) { return _.contains(duplicate, n); }); - }) - + }); return "Found duplicate nodes [" + duplicate + "] in communities: " + JSON.stringify(outComms); - } + }; return !failed; } }); @@ -655,7 +654,10 @@ it('should be able to find communities', function() { this.addMatchers({ toContainKarateClubCommunities: function() { - var c1 = ["15", "16", "19", "21", "23", "24", "27", "28", "30", "31", "33", "34", "9"].sort().join(), + var c1 = [ + "15", "16", "19", "21", "23", "24", "27", + "28", "30", "31", "33", "34", "9" + ].sort().join(), c2 = ["10", "13", "14", "18", "2", "22", "3", "4", "8"].sort().join(), c3 = ["1", "11", "12", "17", "20" , "5", "6", "7"].sort().join(), c4 = ["25", "26", "29", "32"].sort().join(), @@ -702,14 +704,10 @@ return !failed; } }); - - var best = joiner.getBest(); - var steps = 0; while (best !== null) { joiner.joinCommunity(best); best = joiner.getBest(); - steps++; } expect(joiner.getCommunities()).toContainKarateClubCommunities(); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js index f84679cfff..4ea011b159 100644 --- a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js @@ -58,6 +58,14 @@ var s = new NodeReducer([], []); }).not.toThrow(); }); + + it('should create an instance of the modularityJoiner', function() { + spyOn(window, "ModularityJoiner"); + var n = [], + e = [], + s = new NodeReducer(n, e); + expect(window.ModularityJoiner).wasCalledWith(n, e); + }); }); describe('setup correctly', function() { @@ -158,24 +166,8 @@ expect(com).toContainNodes(["0", "1", "2"]); }); - it('should also take the best community if no focus is given', function() { - helper.insertSimpleNodes(nodes, ["0", "1", "2", "3", "4", "5", "6", "7"]); - - edges.push(helper.createSimpleEdge(nodes, 0, 1)); - edges.push(helper.createSimpleEdge(nodes, 0, 2)); - edges.push(helper.createSimpleEdge(nodes, 1, 2)); - edges.push(helper.createSimpleEdge(nodes, 2, 3)); - edges.push(helper.createSimpleEdge(nodes, 3, 4)); - edges.push(helper.createSimpleEdge(nodes, 4, 5)); - edges.push(helper.createSimpleEdge(nodes, 5, 6)); - edges.push(helper.createSimpleEdge(nodes, 5, 7)); - - var com = reducer.getCommunity(6); - expect(com).toContainNodes(["0", "1", "2", "3", "4"]); - }); - }); - /* + describe('checking bucket sort of nodes', function() { var allNodes, buckets; @@ -315,7 +307,7 @@ }); }); - */ + }); }); }()); \ No newline at end of file