entropyx
Version:
A simple data mining library, written in TypeScript
129 lines • 5.47 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.FastNewman = void 0;
const community_manager_1 = require("@/utils/community-manager");
const max_heap_1 = require("@/utils/max-heap");
function makeKey(a, b) {
return a < b ? `${a}#${b}` : `${b}#${a}`;
}
class FastNewman {
constructor(options = {}) {
this.history = [];
this.options = { ...options };
}
async fit(adjacencyMatrix) {
const nodeCount = adjacencyMatrix.length;
const adjacency = new Map();
const degrees = new Array(nodeCount).fill(0);
for (let i = 0; i < nodeCount; i++) {
const neighbors = [];
for (let j = 0; j < nodeCount; j++) {
if (adjacencyMatrix[i][j] !== 0) {
neighbors.push(j);
degrees[i]++;
}
}
adjacency.set(i, neighbors);
}
const totalDegree = degrees.reduce((acc, deg) => acc + deg, 0);
const edgeCount = totalDegree / 2;
const cm = new community_manager_1.CommunityManager(nodeCount, degrees, edgeCount);
const commEdges = new Map();
for (let node = 0; node < nodeCount; node++) {
const neighbors = adjacency.get(node) ?? [];
for (const neighbor of neighbors) {
if (neighbor <= node)
continue;
const commA = cm.nodeToComm[node];
const commB = cm.nodeToComm[neighbor];
if (commA === commB)
continue;
if (!commEdges.has(commA))
commEdges.set(commA, new Map());
if (!commEdges.has(commB))
commEdges.set(commB, new Map());
commEdges.get(commA).set(commB, (commEdges.get(commA).get(commB) ?? 0) + 1);
commEdges.get(commB).set(commA, (commEdges.get(commB).get(commA) ?? 0) + 1);
}
}
const heap = new max_heap_1.MaxHeap();
const insertedPairs = new Set();
for (const [comm, neighborMap] of commEdges.entries()) {
for (const [nbr, edgeCountBetween] of neighborMap.entries()) {
const key = makeKey(comm, nbr);
if (insertedPairs.has(key))
continue;
insertedPairs.add(key);
const delta = cm.deltaQ(comm, nbr, edgeCountBetween);
heap.insert({ deltaQ: delta, commA: comm, commB: nbr });
}
}
let currentQ = this.computeInitialModularity(cm);
this.history.push({ q: currentQ, communities: this.extractCommunities(cm) });
const desired = this.options.numberOfCommunities ?? 1;
while (heap.size() > 0) {
if (cm.commDegree.size <= desired)
break;
const candidate = heap.extractMax();
if (candidate.deltaQ <= 0)
break;
if (!cm.commDegree.has(candidate.commA) || !cm.commDegree.has(candidate.commB)) {
continue;
}
const mergedId = cm.mergeCommunities(candidate.commA, candidate.commB);
currentQ += candidate.deltaQ;
this.history.push({ q: currentQ, communities: this.extractCommunities(cm) });
const newNeighbors = new Map();
const updateNeighbors = (oldComm) => {
const nbrMap = commEdges.get(oldComm);
if (!nbrMap)
return;
for (const [nbr, count] of nbrMap.entries()) {
if (nbr === candidate.commA || nbr === candidate.commB)
continue;
newNeighbors.set(nbr, (newNeighbors.get(nbr) ?? 0) + count);
const neighborMap = commEdges.get(nbr);
if (neighborMap) {
neighborMap.delete(candidate.commA);
neighborMap.delete(candidate.commB);
neighborMap.set(mergedId, (neighborMap.get(mergedId) ?? 0) + count);
}
}
};
updateNeighbors(candidate.commA);
updateNeighbors(candidate.commB);
commEdges.delete(candidate.commA);
commEdges.delete(candidate.commB);
commEdges.set(mergedId, newNeighbors);
for (const [nbr, edgeCountBetween] of newNeighbors.entries()) {
const delta = cm.deltaQ(mergedId, nbr, edgeCountBetween);
heap.insert({ deltaQ: delta, commA: mergedId, commB: nbr });
}
}
const best = this.history.reduce((acc, cur) => (cur.q > acc.q ? cur : acc));
return {
communities: best.communities,
modularityHistory: this.history,
};
}
computeInitialModularity(cm) {
const m = cm.totalEdges;
let sum = 0;
cm.commDegree.forEach((deg) => {
const fraction = deg / (2 * m);
sum += fraction * fraction;
});
return -sum;
}
extractCommunities(cm) {
const communities = new Map();
cm.nodeToComm.forEach((comm, node) => {
if (!communities.has(comm))
communities.set(comm, []);
communities.get(comm).push(node);
});
return Array.from(communities.values());
}
}
exports.FastNewman = FastNewman;
//# sourceMappingURL=fast-newman.js.map
;