Skip to content

Commit

Permalink
barycenter returns the configuration with lowest crossings
Browse files Browse the repository at this point in the history
  • Loading branch information
jdfekete committed Apr 18, 2015
1 parent 592273a commit 9d92de5
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 25 deletions.
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
NODE_PATH = ./node_modules
JS_COMPILER = $(NODE_PATH)/uglify-js/bin/uglifyjs
JS_TESTER = $(NODE_PATH)/vows/bin/vows --nocolor -v

all: \
reorder.v1.js \
Expand Down Expand Up @@ -40,7 +39,7 @@ reorder.v1.js: \
src/ca.js

test: all
@$(JS_TESTER)
@npm test

%.min.js: %.js Makefile
@rm -f $@
Expand Down
36 changes: 25 additions & 11 deletions reorder.v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -726,19 +726,20 @@ function median(neighbors) {
return (neighbors[lm]*rspan + neighbors[rm]*lspan) / (lspan+rspan);
}

reorder.barycenter1 = function(graph, comp, iter) {
reorder.barycenter1 = function(graph, comp, max_iter) {
var nodes = graph.nodes(),
layer1, layer2,
layer,
layer1, layer2, crossings, iter,
best_layer1, best_layer2, best_crossings, best_iter,
layer,
i, v, neighbors;

if (comp.length < 3)
return comp;

if (! iter)
iter = 24;
else if ((iter%2)==1)
iter++; // want even number of iterations
if (! max_iter)
max_iter = 24;
else if ((max_iter%2)==1)
max_iter++; // want even number of iterations

layer1 = comp.filter(function(n) {
return graph.outEdges(n).length!=0;
Expand All @@ -750,9 +751,14 @@ reorder.barycenter1 = function(graph, comp, iter) {
for (i = 0; i < layer2.length; i++)
nodes[layer2[i]].pos = i;

for (layer = layer1;
iter--;
layer = (layer == layer1) ? layer2 : layer1) {
best_crossings = count_crossings(graph, layer1, layer2);
best_layer1 = layer1;
best_layer2 = layer2;
best_iter = 0;

for (layer = layer1, iter = 0;
iter < max_iter;
iter++, layer = (layer == layer1) ? layer2 : layer1) {
for (i = 0; i < layer.length; i++) {
// Compute the median/barycenter for this node and set
// its (real) value into node.mval
Expand Down Expand Up @@ -782,8 +788,16 @@ reorder.barycenter1 = function(graph, comp, iter) {
});
for (i = 0; i < layer2.length; i++)
nodes[layer[i]].pos = i;
crossings = count_crossings(graph, layer1, layer2);
if (crossings < best_crossings) {
best_crossings = crossings;
best_layer1 = layer1;
best_layer2 = layer2;
best_iter = iter;
}
}
return [layer1, layer2];
console.log('Best iter: '+best_iter);
return [best_layer1, best_layer2, best_crossings];
};
reorder.dijkstra = function(graph) {
var g = graph, dijkstra = {};
Expand Down
2 changes: 1 addition & 1 deletion reorder.v1.min.js

Large diffs are not rendered by default.

36 changes: 25 additions & 11 deletions src/barycenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@ function median(neighbors) {
return (neighbors[lm]*rspan + neighbors[rm]*lspan) / (lspan+rspan);
}

reorder.barycenter1 = function(graph, comp, iter) {
reorder.barycenter1 = function(graph, comp, max_iter) {
var nodes = graph.nodes(),
layer1, layer2,
layer,
layer1, layer2, crossings, iter,
best_layer1, best_layer2, best_crossings, best_iter,
layer,
i, v, neighbors;

if (comp.length < 3)
return comp;

if (! iter)
iter = 24;
else if ((iter%2)==1)
iter++; // want even number of iterations
if (! max_iter)
max_iter = 24;
else if ((max_iter%2)==1)
max_iter++; // want even number of iterations

layer1 = comp.filter(function(n) {
return graph.outEdges(n).length!=0;
Expand All @@ -56,9 +57,14 @@ reorder.barycenter1 = function(graph, comp, iter) {
for (i = 0; i < layer2.length; i++)
nodes[layer2[i]].pos = i;

for (layer = layer1;
iter--;
layer = (layer == layer1) ? layer2 : layer1) {
best_crossings = count_crossings(graph, layer1, layer2);
best_layer1 = layer1;
best_layer2 = layer2;
best_iter = 0;

for (layer = layer1, iter = 0;
iter < max_iter;
iter++, layer = (layer == layer1) ? layer2 : layer1) {
for (i = 0; i < layer.length; i++) {
// Compute the median/barycenter for this node and set
// its (real) value into node.mval
Expand Down Expand Up @@ -88,6 +94,14 @@ reorder.barycenter1 = function(graph, comp, iter) {
});
for (i = 0; i < layer2.length; i++)
nodes[layer[i]].pos = i;
crossings = count_crossings(graph, layer1, layer2);
if (crossings < best_crossings) {
best_crossings = crossings;
best_layer1 = layer1;
best_layer2 = layer2;
best_iter = iter;
}
}
return [layer1, layer2];
console.log('Best iter: '+best_iter);
return [best_layer1, best_layer2, best_crossings];
};
56 changes: 56 additions & 0 deletions src/count_crossings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Wilhelm Barth, Petra Mutzel, Michael Jünger:
// Simple and Efficient Bilayer Cross Counting.
// J. Graph Algorithms Appl. 8(2): 179-194 (2004)
function count_crossings(graph, north, south) {
var i, j, n,
firstIndex, treeSize, tree, index, weightSum,
invert = false, crosscount;

// Choose the smaller axis
if (north.length < south.length) {
var tmp = north;
north = south;
south = tmp;
invert = true;
}

var south_inv = inverse_permutation(south),
southsequence = [];

for (i = 0; i < north.length; i++) {
if (invert) {
n = graph.inEdges(north[i])
.map(function(e) {
return south_inv[e.target.index];
});
}
else {
n = graph.outEdges(north[i])
.map(function(e) {
return south_inv[e.source.index];
});
}
n.sort(function(a,b){ return a-b; });
southsequence = southsequence.concat(n);
}

firstIndex = 1;
while (firstIndex < south.length)
firstIndex <<= 1;
treeSize = 2 * firstIndex - 1;
firstIndex -= 1;
tree = science.zeroes(treeSize);

crosscount = 0;
for (i = 0; i < southsequence.length; i++) {
index = southsequence[i] + firstIndex;
tree[index]++;
while (index > 0) {
if (index%2) crosscount += tree[index+1];
index = (index - 1) >> 1;
tree[index]++;
}
}
return crosscount;
}
reorder.count_crossings = count_crossings;
3 changes: 3 additions & 0 deletions src/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ require("util").puts(JSON.stringify({
"vows": "0.8.1",
"jsonfile": "2.0.0"
},
"scripts": {
"test": "vows; echo"
},
"licenses": [
{
"type": "BSD-3",
Expand Down
2 changes: 2 additions & 0 deletions test/barycenter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ suite.addBatch({
expect = [0, 1, 3, 2, 4];
reorder.printmat(mat);
perm = reorder.barycenter(graph);
console.log('VOrder: %j, HOrder: %j, Crossings: %d',
perm[1], perm[0], perm[2]);
reorder.printmat(mat, perm[1], perm[0]);
//assert.deepEqual(perm, expect);
}
Expand Down
142 changes: 142 additions & 0 deletions test/count_crossings-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
require("science");
require("../reorder.v1");
require("../reorder.v1");

var vows = require("vows"),
assert = require("assert"),
seedrandom = require('seedrandom');
//var jf = require('jsonfile');

Math.seedrandom('reorder');

var suite = vows.describe("reorder.count_crossings");

function naive_count_crossings(graph, north, south) {
var i, j, e1, e2, v, count = 0,
inv_north = reorder.inverse_permutation(north),
inv_south = reorder.inverse_permutation(south),
links = [];
for (i = 0; i < north.length; i++) {
v = north[i];
links = links.concat(graph.outEdges(v).map(function(e) {
return [ inv_north[e.target.index], inv_south[e.source.index] ];
}));
}
for (i = 0; i < links.length; i++) {
e1 = links[i];
for (j = i+1; j < links.length; j++) {
e2 = links[j];
if ((e1[0] < e2[0] && e1[1] > e2[1])
|| (e1[0] > e2[0] && e1[1] < e2[1]))
count++;
}
}
return count;
}

function dohard(mat) {
var graph = reorder.mat2graph(mat, true),
comps = graph.components(),
comp = comps.reduce(function(a, b) {
return (a.length > b.length) ? a : b;
});
comp.sort(function(a,b){return a-b; });
var layer1 = comp.filter(function(n) {
return graph.outEdges(n).length!=0;
}),
layer2 = comp.filter(function(n) {
return graph.inEdges(n).length!=0;
});
//console.time('fast_crossings');
var c1 = reorder.count_crossings(graph, layer1, layer2);
//console.timeEnd('fast_crossings');
//console.time('naive_crossings');
var c2 = naive_count_crossings(graph, layer1, layer2);
//console.timeEnd('naive_crossings');
// if (c1 != c2) {
// var file = 'error_count_crossings.json';
// jf.writeFile(file, mat, function(err) {
// console.log(err);
// });
// }
assert.equal(c1, c2);
}

suite.addBatch({
"count_crossings": {
"simple": function() {
var graph = reorder.graph()
.nodes([{id: 0}, {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}])
.links([{source: 0, target: 0},
{source: 1, target: 1},
{source: 1, target: 2},
{source: 2, target: 0},
{source: 2, target: 3},
{source: 2, target: 4},
{source: 3, target: 0},
{source: 3, target: 2},
{source: 4, target: 3},
{source: 5, target: 2},
{source: 5, target: 4}])
.directed(true)
.init(),
comp = graph.components()[0],
layer1 = comp.filter(function(n) {
return graph.outEdges(n).length!=0;
}),
layer2 = comp.filter(function(n) {
return graph.inEdges(n).length!=0;
});


assert.equal(naive_count_crossings(graph, layer1, layer2),
12);
assert.equal(reorder.count_crossings(graph, layer1, layer2),
12);
},
"bug": function() {
dohard([
[0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0],
[0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
]);
},
"hard": function() {
for (var i = 10; i < 100; i += 20) {
for (var j = 10; j < 100; j += 20) {
var mat = reorder.randomMatrix(0.2, i, j, false);
dohard(mat);
}
}
}
}
});

suite.export(module);

0 comments on commit 9d92de5

Please sign in to comment.