/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global _*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// /// @file /// /// DISCLAIMER /// /// Copyright 2010-2012 triagens GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Michael Hackstein /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// function ModularityJoiner() { "use strict"; var // Copy of underscore.js. importScripts doesn't work breaker = {}, nativeForEach = Array.prototype.forEach, nativeKeys = Object.keys, nativeIsArray = Array.isArray, toString = Object.prototype.toString, nativeIndexOf = Array.prototype.indexOf, nativeMap = Array.prototype.map, nativeSome = Array.prototype.some, _ = { isArray: nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }, isFunction: function(obj) { return typeof obj === 'function'; }, isString: function(obj) { return toString.call(obj) === '[object String]'; }, each: function(obj, iterator, context) { if (obj === null || obj === undefined) { return; } var i, l, key; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === breaker) { return; } } } else { for (key in obj) { if (obj.hasOwnProperty(key)) { if (iterator.call(context, obj[key], key, obj) === breaker) { return; } } } } }, keys: nativeKeys || function(obj) { if (typeof obj !== "object" || Array.isArray(obj)) { throw new TypeError('Invalid object'); } var keys = [], key; for (key in obj) { if (obj.hasOwnProperty(key)) { keys[keys.length] = key; } } return keys; }, min: function(obj, iterator, context) { if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return Math.min.apply(Math, obj); } if (!iterator && _.isEmpty(obj)) { return Infinity; } var result = {computed : Infinity, value: Infinity}; _.each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; if (computed < result.computed) { result = {value : value, computed : computed}; } }); return result.value; }, map: function(obj, iterator, context) { var results = []; if (obj === null) { return results; } if (nativeMap && obj.map === nativeMap) { return obj.map(iterator, context); } _.each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); return results; }, pluck: function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }, uniq: function(array, isSorted, iterator, context) { if (_.isFunction(isSorted)) { context = iterator; iterator = isSorted; isSorted = false; } var initial = iterator ? _.map(array, iterator, context) : array, results = [], seen = []; _.each(initial, function(value, index) { if (isSorted) { if (!index || seen[seen.length - 1] !== value) { seen.push(value); results.push(array[index]); } } else if (!_.contains(seen, value)) { seen.push(value); results.push(array[index]); } }); return results; }, union: function() { return _.uniq(Array.prototype.concat.apply(Array.prototype, arguments)); }, isEmpty: function(obj) { var key; if (obj === null) { return true; } if (_.isArray(obj) || _.isString(obj)) { return obj.length === 0; } for (key in obj) { if (obj.hasOwnProperty(key)) { return false; } } return true; }, any: function(obj, iterator, context) { iterator = iterator || _.identity; var result = false; if (obj === null) { return result; } if (nativeSome && obj.some === nativeSome) { return obj.some(iterator, context); } _.each(obj, function(value, index, list) { if (result) { return breaker; } result = iterator.call(context, value, index, list); return breaker; }); return !!result; }, contains: function(obj, target) { if (obj === null) { return false; } if (nativeIndexOf && obj.indexOf === nativeIndexOf) { return obj.indexOf(target) !== -1; } return _.any(obj, function(value) { return value === target; }); }, values: function(obj) { var values = [], key; for (key in obj) { if (obj.hasOwnProperty(key)) { values.push(obj[key]); } } return values; } }, matrix = {}, backwardMatrix = {}, degrees = {}, m = 0, revM = 0, a = null, dQ = null, heap = null, isRunning = false, comms = {}, //////////////////////////////////// // Private functions // //////////////////////////////////// setHeapToMax = function(id) { var maxT, maxV = Number.NEGATIVE_INFINITY; _.each(dQ[id], function(v, t) { if (maxV < v) { maxV = v; maxT = t; } }); if (maxV < 0) { delete heap[id]; return; } heap[id] = maxT; }, setHeapToMaxInList = function(l, id) { setHeapToMax(id); }, isSetDQVal = function(i, j) { if (i < j) { return dQ[i] && dQ[i][j]; } return dQ[j] && dQ[j][i]; }, // This does not check if everything exists, // do it before! getDQVal = function(i, j) { if (i < j) { return dQ[i][j]; } return dQ[j][i]; }, setDQVal = function(i, j, v) { if (i < j) { dQ[i] = dQ[i] || {}; dQ[i][j] = v; return; } dQ[j] = dQ[j] || {}; dQ[j][i] = v; }, delDQVal = function(i, j) { if (i < j) { if (!dQ[i]) { return; } delete dQ[i][j]; if (_.isEmpty(dQ[i])) { delete dQ[i]; } return; } if (i === j) { return; } delDQVal(j, i); }, updateHeap = function(i, j) { var hv, val; if (i < j) { if (!isSetDQVal(i, j)) { setHeapToMax(i); return; } val = getDQVal(i, j); if (heap[i] === j) { setHeapToMax(i); return; } if (!isSetDQVal(i, heap[i])) { setHeapToMax(i); return; } hv = getDQVal(i, heap[i]); if (hv < val) { heap[i] = j; } return; } if (i === j) { return; } updateHeap(j, i); }, updateDegrees = function(low, high) { a[low]._in += a[high]._in; a[low]._out += a[high]._out; delete a[high]; }, insertEdge = function(s, t) { matrix[s] = matrix[s] || {}; matrix[s][t] = (matrix[s][t] || 0) + 1; backwardMatrix[t] = backwardMatrix[t] || {}; backwardMatrix[t][s] = (backwardMatrix[t][s] || 0) + 1; degrees[s] = degrees[s] || {_in: 0, _out:0}; degrees[t] = degrees[t] || {_in: 0, _out:0}; degrees[s]._out++; degrees[t]._in++; m++; revM = Math.pow(m, -1); }, deleteEdge = function(s, t) { if (matrix[s]) { matrix[s][t]--; if (matrix[s][t] === 0) { delete matrix[s][t]; } backwardMatrix[t][s]--; if (backwardMatrix[t][s] === 0) { delete backwardMatrix[t][s]; } degrees[s]._out--; degrees[t]._in--; m--; if (m > 0) { revM = Math.pow(m, -1); } else { revM = 0; } if (_.isEmpty(matrix[s])) { delete matrix[s]; } if (_.isEmpty(backwardMatrix[t])) { delete backwardMatrix[t]; } if (degrees[s]._in === 0 && degrees[s]._out === 0) { delete degrees[s]; } if (degrees[t]._in === 0 && degrees[t]._out === 0) { delete degrees[t]; } } }, makeInitialDegrees = function() { a = {}; _.each(degrees, function (n, id) { a[id] = { _in: n._in / m, _out: n._out / m }; }); return a; }, notConnectedPenalty = function(s, t) { return a[s]._out * a[t]._in + a[s]._in * a[t]._out; }, neighbors = function(sID) { var outbound = _.keys(matrix[sID] || {}), inbound = _.keys(backwardMatrix[sID] || {}); return _.union(outbound, inbound); }, makeInitialDQ = function() { dQ = {}; _.each(matrix, function(tars, s) { var bw = backwardMatrix[s] || {}, keys = neighbors(s); _.each(keys, function(t) { var ast = (tars[t] || 0), value; ast += (bw[t] || 0); value = ast * revM - notConnectedPenalty(s, t); if (value > 0) { setDQVal(s, t, value); } return; }); }); }, makeInitialHeap = function() { heap = {}; _.each(dQ, setHeapToMaxInList); return heap; }, // i < j && i != j != k updateDQAndHeapValue = function (i, j, k) { var val; if (isSetDQVal(k, i)) { val = getDQVal(k, i); if (isSetDQVal(k, j)) { val += getDQVal(k, j); setDQVal(k, i, val); delDQVal(k, j); updateHeap(k, i); updateHeap(k, j); return; } val -= notConnectedPenalty(k, j); if (val < 0) { delDQVal(k, i); } updateHeap(k, i); return; } if (isSetDQVal(k, j)) { val = getDQVal(k, j); val -= notConnectedPenalty(k, i); if (val > 0) { setDQVal(k, i, val); } updateHeap(k, i); delDQVal(k, j); updateHeap(k, j); } }, updateDQAndHeap = function (low, high) { _.each(dQ, function (list, s) { if (s === low || s === high) { _.each(list, function(v, t) { if (t === high) { delDQVal(low, high); updateHeap(low, high); return; } updateDQAndHeapValue(low, high, t); }); return; } updateDQAndHeapValue(low, high, s); }); }, //////////////////////////////////// // getters // //////////////////////////////////// getAdjacencyMatrix = function() { return matrix; }, getHeap = function() { return heap; }, getDQ = function() { return dQ; }, getDegrees = function() { return a; }, getCommunities = function() { return comms; }, getBest = function() { var bestL, bestS, bestV = Number.NEGATIVE_INFINITY; _.each(heap, function(lID, sID) { if (bestV < dQ[sID][lID]) { bestL = lID; bestS = sID; bestV = dQ[sID][lID]; } }); if (bestV <= 0) { return null; } return { sID: bestS, lID: bestL, val: bestV }; }, getBestCommunity = function (communities) { var bestQ = Number.NEGATIVE_INFINITY, bestC; _.each(communities, function (obj) { if (obj.q > bestQ) { bestQ = obj.q; bestC = obj.nodes; } }); return bestC; }, //////////////////////////////////// // setup // //////////////////////////////////// setup = function() { makeInitialDegrees(); makeInitialDQ(); makeInitialHeap(); comms = {}; }, //////////////////////////////////// // computation // //////////////////////////////////// joinCommunity = function(comm) { var s = comm.sID, l = comm.lID, q = comm.val; comms[s] = comms[s] || {nodes: [s], q: 0}; if (comms[l]) { comms[s].nodes = comms[s].nodes.concat(comms[l].nodes); comms[s].q += comms[l].q; delete comms[l]; } else { comms[s].nodes.push(l); } comms[s].q += q; updateDQAndHeap(s, l); updateDegrees(s, l); }, ////////////////////////////////////////////// // Evaluate value of community by distance // ////////////////////////////////////////////// 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(matrix, function(u, n) { dist[n] = Number.POSITIVE_INFINITY; }); dist[sID] = 0; if (floatDistStep(dist, 1, neighbors(sID))) { return dist; } throw "FAIL!"; }, minDist = function(dist) { return function(a) { return dist[a]; }; }, //////////////////////////////////// // Get only the Best Community // //////////////////////////////////// getCommunity = function(limit, focus) { var coms = {}, res = [], dist = {}, best, sortByDistance = function (a, b) { var d1 = dist[_.min(a,minDist(dist))], d2 = dist[_.min(b,minDist(dist))], val = d2 - d1; if (val === 0) { val = coms[b[b.length-1]].q - coms[a[a.length-1]].q; } return val; }; setup(); best = getBest(); while (best !== null) { joinCommunity(best); best = getBest(); } coms = getCommunities(); if (focus !== undefined) { _.each(coms, function(obj, key) { if (_.contains(obj.nodes, focus)) { delete coms[key]; } }); res = _.pluck(_.values(coms), "nodes"); dist = floatDist(focus); res.sort(sortByDistance); return res[0]; } return getBestCommunity(coms); }; //////////////////////////////////// // Public functions // //////////////////////////////////// this.insertEdge = insertEdge; this.deleteEdge = deleteEdge; this.getAdjacencyMatrix = getAdjacencyMatrix; this.getHeap = getHeap; this.getDQ = getDQ; this.getDegrees = getDegrees; this.getCommunities = getCommunities; this.getBest = getBest; this.setup = setup; this.joinCommunity = joinCommunity; this.getCommunity = getCommunity; }