import {LinkAccessor} from './linklengths' export interface LinkTypeAccessor extends LinkAccessor { // return a unique identifier for the type of the link getType(l: Link): number; } export class PowerEdge { constructor( public source: any, public target: any, public type: number) { } } export class Configuration { // canonical list of modules. // Initialized to a module for each leaf node, such that the ids and indexes of the module in the array match the indexes of the nodes in links // Modules created through merges are appended to the end of this. modules: Module[]; // top level modules and candidates for merges roots: ModuleSet[]; // remaining edge count R: number; constructor(n: number, edges: Link[], private linkAccessor: LinkTypeAccessor, rootGroup?: any[]) { this.modules = new Array(n); this.roots = []; if (rootGroup) { this.initModulesFromGroup(rootGroup); } else { this.roots.push(new ModuleSet()); for (var i = 0; i < n; ++i) this.roots[0].add(this.modules[i] = new Module(i)); } this.R = edges.length; edges.forEach(e => { var s = this.modules[linkAccessor.getSourceIndex(e)], t = this.modules[linkAccessor.getTargetIndex(e)], type = linkAccessor.getType(e); s.outgoing.add(type, t); t.incoming.add(type, s); }); } private initModulesFromGroup(group): ModuleSet { var moduleSet = new ModuleSet(); this.roots.push(moduleSet); for (var i = 0; i < group.leaves.length; ++i) { var node = group.leaves[i]; var module = new Module(node.id); this.modules[node.id] = module; moduleSet.add(module); } if (group.groups) { for (var j = 0; j < group.groups.length; ++j) { var child = group.groups[j]; // Propagate group properties (like padding, stiffness, ...) as module definition so that the generated power graph group will inherit it var definition = {}; for (var prop in child) if (prop !== "leaves" && prop !== "groups" && child.hasOwnProperty(prop)) definition[prop] = child[prop]; // Use negative module id to avoid clashes between predefined and generated modules moduleSet.add(new Module(-1-j, new LinkSets(), new LinkSets(), this.initModulesFromGroup(child), definition)); } } return moduleSet; } // merge modules a and b keeping track of their power edges and removing the from roots merge(a: Module, b: Module, k: number = 0): Module { var inInt = a.incoming.intersection(b.incoming), outInt = a.outgoing.intersection(b.outgoing); var children = new ModuleSet(); children.add(a); children.add(b); var m = new Module(this.modules.length, outInt, inInt, children); this.modules.push(m); var update = (s: LinkSets, i: string, o: string) => { s.forAll((ms, linktype) => { ms.forAll(n => { var nls = n[i]; nls.add(linktype, m); nls.remove(linktype, a); nls.remove(linktype, b); (a[o]).remove(linktype, n); (b[o]).remove(linktype, n); }); }); }; update(outInt, "incoming", "outgoing"); update(inInt, "outgoing", "incoming"); this.R -= inInt.count() + outInt.count(); this.roots[k].remove(a); this.roots[k].remove(b); this.roots[k].add(m); return m; } private rootMerges(k: number = 0): { id: number; nEdges: number; a: Module; b: Module; }[] { var rs = this.roots[k].modules(); var n = rs.length; var merges = new Array(n * (n - 1)); var ctr = 0; for (var i = 0, i_ = n - 1; i < i_; ++i) { for (var j = i+1; j < n; ++j) { var a = rs[i], b = rs[j]; merges[ctr] = { id: ctr, nEdges: this.nEdges(a, b), a: a, b: b }; ctr++; } } return merges; } greedyMerge(): boolean { for (var i = 0; i < this.roots.length; ++i) { // Handle single nested module case if (this.roots[i].modules().length < 2) continue; // find the merge that allows for the most edges to be removed. secondary ordering based on arbitrary id (for predictability) var ms = this.rootMerges(i).sort((a, b) => a.nEdges == b.nEdges ? a.id - b.id : a.nEdges - b.nEdges); var m = ms[0]; if (m.nEdges >= this.R) continue; this.merge(m.a, m.b, i); return true; } } private nEdges(a: Module, b: Module): number { var inInt = a.incoming.intersection(b.incoming), outInt = a.outgoing.intersection(b.outgoing); return this.R - inInt.count() - outInt.count(); } getGroupHierarchy(retargetedEdges: PowerEdge[]): any[]{ var groups = []; var root = {}; toGroups(this.roots[0], root, groups); var es = this.allEdges(); es.forEach(e => { var a = this.modules[e.source]; var b = this.modules[e.target]; retargetedEdges.push(new PowerEdge( typeof a.gid === "undefined" ? e.source : groups[a.gid], typeof b.gid === "undefined" ? e.target : groups[b.gid], e.type )); }); return groups; } allEdges(): PowerEdge[] { var es = []; Configuration.getEdges(this.roots[0], es); return es; } static getEdges(modules: ModuleSet, es: PowerEdge[]) { modules.forAll(m => { m.getEdges(es); Configuration.getEdges(m.children, es); }); } } function toGroups(modules: ModuleSet, group, groups) { modules.forAll(m => { if (m.isLeaf()) { if (!group.leaves) group.leaves = []; group.leaves.push(m.id); } else { var g = group; m.gid = groups.length; if (!m.isIsland() || m.isPredefined()) { g = { id: m.gid }; if (m.isPredefined()) // Apply original group properties for (var prop in m.definition) g[prop] = m.definition[prop]; if (!group.groups) group.groups = []; group.groups.push(m.gid); groups.push(g); } toGroups(m.children, g, groups); } }); } export class Module { gid: number; constructor( public id: number, public outgoing: LinkSets = new LinkSets(), public incoming: LinkSets = new LinkSets(), public children: ModuleSet = new ModuleSet(), public definition?: any) { } getEdges(es: PowerEdge[]) { this.outgoing.forAll((ms, edgetype) => { ms.forAll(target => { es.push(new PowerEdge(this.id, target.id, edgetype)); }); }); } isLeaf() { return this.children.count() === 0; } isIsland() { return this.outgoing.count() === 0 && this.incoming.count() === 0; } isPredefined(): boolean { return typeof this.definition !== "undefined"; } } function intersection(m: any, n: any): any { var i = {}; for (var v in m) if (v in n) i[v] = m[v]; return i; } export class ModuleSet { table: any = {}; count() { return Object.keys(this.table).length; } intersection(other: ModuleSet): ModuleSet { var result = new ModuleSet(); result.table = intersection(this.table, other.table); return result; } intersectionCount(other: ModuleSet): number { return this.intersection(other).count(); } contains(id: number): boolean { return id in this.table; } add(m: Module): void { this.table[m.id] = m; } remove(m: Module): void { delete this.table[m.id]; } forAll(f: (m: Module) => void) { for (var mid in this.table) { f(this.table[mid]); } } modules(): Module[] { var vs = []; this.forAll(m => { if (!m.isPredefined()) vs.push(m); }); return vs; } } export class LinkSets { sets: any = {}; n: number = 0; count(): number { return this.n; } contains(id: number) { var result = false; this.forAllModules(m => { if (!result && m.id == id) { result = true; } }); return result; } add(linktype: number, m: Module) { var s: ModuleSet = linktype in this.sets ? this.sets[linktype] : this.sets[linktype] = new ModuleSet(); s.add(m); ++this.n; } remove(linktype: number, m: Module) { var ms = this.sets[linktype]; ms.remove(m); if (ms.count() === 0) { delete this.sets[linktype]; } --this.n; } forAll(f: (ms: ModuleSet, linktype: number) => void) { for (var linktype in this.sets) { f(this.sets[linktype], Number(linktype)); } } forAllModules(f: (m: Module) => void) { this.forAll((ms, lt) => ms.forAll(f)); } intersection(other: LinkSets): LinkSets { var result: LinkSets = new LinkSets(); this.forAll((ms, lt) => { if (lt in other.sets) { var i = ms.intersection(other.sets[lt]), n = i.count(); if (n > 0) { result.sets[lt] = i; result.n += n; } } }); return result; } } function intersectionCount(m: any, n: any): number { return Object.keys(intersection(m, n)).length } export function getGroups(nodes: any[], links: Link[], la: LinkTypeAccessor, rootGroup?: any[]): { groups: any[]; powerEdges: PowerEdge[] } { var n = nodes.length, c = new Configuration(n, links, la, rootGroup); while (c.greedyMerge()); var powerEdges: PowerEdge[] = []; var g = c.getGroupHierarchy(powerEdges); powerEdges.forEach(function (e) { var f = (end) => { var g = e[end]; if (typeof g == "number") e[end] = nodes[g]; }; f("source"); f("target"); }); return { groups: g, powerEdges: powerEdges }; }