StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
7.8 KiB
342 lines
7.8 KiB
"use strict"; "use restrict";
|
|
|
|
var bits = require("bit-twiddle")
|
|
, UnionFind = require("union-find")
|
|
|
|
//Returns the dimension of a cell complex
|
|
function dimension(cells) {
|
|
var d = 0
|
|
, max = Math.max
|
|
for(var i=0, il=cells.length; i<il; ++i) {
|
|
d = max(d, cells[i].length)
|
|
}
|
|
return d-1
|
|
}
|
|
exports.dimension = dimension
|
|
|
|
//Counts the number of vertices in faces
|
|
function countVertices(cells) {
|
|
var vc = -1
|
|
, max = Math.max
|
|
for(var i=0, il=cells.length; i<il; ++i) {
|
|
var c = cells[i]
|
|
for(var j=0, jl=c.length; j<jl; ++j) {
|
|
vc = max(vc, c[j])
|
|
}
|
|
}
|
|
return vc+1
|
|
}
|
|
exports.countVertices = countVertices
|
|
|
|
//Returns a deep copy of cells
|
|
function cloneCells(cells) {
|
|
var ncells = new Array(cells.length)
|
|
for(var i=0, il=cells.length; i<il; ++i) {
|
|
ncells[i] = cells[i].slice(0)
|
|
}
|
|
return ncells
|
|
}
|
|
exports.cloneCells = cloneCells
|
|
|
|
//Ranks a pair of cells up to permutation
|
|
function compareCells(a, b) {
|
|
var n = a.length
|
|
, t = a.length - b.length
|
|
, min = Math.min
|
|
if(t) {
|
|
return t
|
|
}
|
|
switch(n) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
return a[0] - b[0];
|
|
case 2:
|
|
var d = a[0]+a[1]-b[0]-b[1]
|
|
if(d) {
|
|
return d
|
|
}
|
|
return min(a[0],a[1]) - min(b[0],b[1])
|
|
case 3:
|
|
var l1 = a[0]+a[1]
|
|
, m1 = b[0]+b[1]
|
|
d = l1+a[2] - (m1+b[2])
|
|
if(d) {
|
|
return d
|
|
}
|
|
var l0 = min(a[0], a[1])
|
|
, m0 = min(b[0], b[1])
|
|
, d = min(l0, a[2]) - min(m0, b[2])
|
|
if(d) {
|
|
return d
|
|
}
|
|
return min(l0+a[2], l1) - min(m0+b[2], m1)
|
|
|
|
//TODO: Maybe optimize n=4 as well?
|
|
|
|
default:
|
|
var as = a.slice(0)
|
|
as.sort()
|
|
var bs = b.slice(0)
|
|
bs.sort()
|
|
for(var i=0; i<n; ++i) {
|
|
t = as[i] - bs[i]
|
|
if(t) {
|
|
return t
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
exports.compareCells = compareCells
|
|
|
|
function compareZipped(a, b) {
|
|
return compareCells(a[0], b[0])
|
|
}
|
|
|
|
//Puts a cell complex into normal order for the purposes of findCell queries
|
|
function normalize(cells, attr) {
|
|
if(attr) {
|
|
var len = cells.length
|
|
var zipped = new Array(len)
|
|
for(var i=0; i<len; ++i) {
|
|
zipped[i] = [cells[i], attr[i]]
|
|
}
|
|
zipped.sort(compareZipped)
|
|
for(var i=0; i<len; ++i) {
|
|
cells[i] = zipped[i][0]
|
|
attr[i] = zipped[i][1]
|
|
}
|
|
return cells
|
|
} else {
|
|
cells.sort(compareCells)
|
|
return cells
|
|
}
|
|
}
|
|
exports.normalize = normalize
|
|
|
|
//Removes all duplicate cells in the complex
|
|
function unique(cells) {
|
|
if(cells.length === 0) {
|
|
return []
|
|
}
|
|
var ptr = 1
|
|
, len = cells.length
|
|
for(var i=1; i<len; ++i) {
|
|
var a = cells[i]
|
|
if(compareCells(a, cells[i-1])) {
|
|
if(i === ptr) {
|
|
ptr++
|
|
continue
|
|
}
|
|
cells[ptr++] = a
|
|
}
|
|
}
|
|
cells.length = ptr
|
|
return cells
|
|
}
|
|
exports.unique = unique;
|
|
|
|
//Finds a cell in a normalized cell complex
|
|
function findCell(cells, c) {
|
|
var lo = 0
|
|
, hi = cells.length-1
|
|
, r = -1
|
|
while (lo <= hi) {
|
|
var mid = (lo + hi) >> 1
|
|
, s = compareCells(cells[mid], c)
|
|
if(s <= 0) {
|
|
if(s === 0) {
|
|
r = mid
|
|
}
|
|
lo = mid + 1
|
|
} else if(s > 0) {
|
|
hi = mid - 1
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
exports.findCell = findCell;
|
|
|
|
//Builds an index for an n-cell. This is more general than dual, but less efficient
|
|
function incidence(from_cells, to_cells) {
|
|
var index = new Array(from_cells.length)
|
|
for(var i=0, il=index.length; i<il; ++i) {
|
|
index[i] = []
|
|
}
|
|
var b = []
|
|
for(var i=0, n=to_cells.length; i<n; ++i) {
|
|
var c = to_cells[i]
|
|
var cl = c.length
|
|
for(var k=1, kn=(1<<cl); k<kn; ++k) {
|
|
b.length = bits.popCount(k)
|
|
var l = 0
|
|
for(var j=0; j<cl; ++j) {
|
|
if(k & (1<<j)) {
|
|
b[l++] = c[j]
|
|
}
|
|
}
|
|
var idx=findCell(from_cells, b)
|
|
if(idx < 0) {
|
|
continue
|
|
}
|
|
while(true) {
|
|
index[idx++].push(i)
|
|
if(idx >= from_cells.length || compareCells(from_cells[idx], b) !== 0) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
exports.incidence = incidence
|
|
|
|
//Computes the dual of the mesh. This is basically an optimized version of buildIndex for the situation where from_cells is just the list of vertices
|
|
function dual(cells, vertex_count) {
|
|
if(!vertex_count) {
|
|
return incidence(unique(skeleton(cells, 0)), cells, 0)
|
|
}
|
|
var res = new Array(vertex_count)
|
|
for(var i=0; i<vertex_count; ++i) {
|
|
res[i] = []
|
|
}
|
|
for(var i=0, len=cells.length; i<len; ++i) {
|
|
var c = cells[i]
|
|
for(var j=0, cl=c.length; j<cl; ++j) {
|
|
res[c[j]].push(i)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
exports.dual = dual
|
|
|
|
//Enumerates all cells in the complex
|
|
function explode(cells) {
|
|
var result = []
|
|
for(var i=0, il=cells.length; i<il; ++i) {
|
|
var c = cells[i]
|
|
, cl = c.length|0
|
|
for(var j=1, jl=(1<<cl); j<jl; ++j) {
|
|
var b = []
|
|
for(var k=0; k<cl; ++k) {
|
|
if((j >>> k) & 1) {
|
|
b.push(c[k])
|
|
}
|
|
}
|
|
result.push(b)
|
|
}
|
|
}
|
|
return normalize(result)
|
|
}
|
|
exports.explode = explode
|
|
|
|
//Enumerates all of the n-cells of a cell complex
|
|
function skeleton(cells, n) {
|
|
if(n < 0) {
|
|
return []
|
|
}
|
|
var result = []
|
|
, k0 = (1<<(n+1))-1
|
|
for(var i=0; i<cells.length; ++i) {
|
|
var c = cells[i]
|
|
for(var k=k0; k<(1<<c.length); k=bits.nextCombination(k)) {
|
|
var b = new Array(n+1)
|
|
, l = 0
|
|
for(var j=0; j<c.length; ++j) {
|
|
if(k & (1<<j)) {
|
|
b[l++] = c[j]
|
|
}
|
|
}
|
|
result.push(b)
|
|
}
|
|
}
|
|
return normalize(result)
|
|
}
|
|
exports.skeleton = skeleton;
|
|
|
|
//Computes the boundary of all cells, does not remove duplicates
|
|
function boundary(cells) {
|
|
var res = []
|
|
for(var i=0,il=cells.length; i<il; ++i) {
|
|
var c = cells[i]
|
|
for(var j=0,cl=c.length; j<cl; ++j) {
|
|
var b = new Array(c.length-1)
|
|
for(var k=0, l=0; k<cl; ++k) {
|
|
if(k !== j) {
|
|
b[l++] = c[k]
|
|
}
|
|
}
|
|
res.push(b)
|
|
}
|
|
}
|
|
return normalize(res)
|
|
}
|
|
exports.boundary = boundary;
|
|
|
|
//Computes connected components for a dense cell complex
|
|
function connectedComponents_dense(cells, vertex_count) {
|
|
var labels = new UnionFind(vertex_count)
|
|
for(var i=0; i<cells.length; ++i) {
|
|
var c = cells[i]
|
|
for(var j=0; j<c.length; ++j) {
|
|
for(var k=j+1; k<c.length; ++k) {
|
|
labels.link(c[j], c[k])
|
|
}
|
|
}
|
|
}
|
|
var components = []
|
|
, component_labels = labels.ranks
|
|
for(var i=0; i<component_labels.length; ++i) {
|
|
component_labels[i] = -1
|
|
}
|
|
for(var i=0; i<cells.length; ++i) {
|
|
var l = labels.find(cells[i][0])
|
|
if(component_labels[l] < 0) {
|
|
component_labels[l] = components.length
|
|
components.push([cells[i].slice(0)])
|
|
} else {
|
|
components[component_labels[l]].push(cells[i].slice(0))
|
|
}
|
|
}
|
|
return components
|
|
}
|
|
|
|
//Computes connected components for a sparse graph
|
|
function connectedComponents_sparse(cells) {
|
|
var vertices = unique(normalize(skeleton(cells, 0)))
|
|
, labels = new UnionFind(vertices.length)
|
|
for(var i=0; i<cells.length; ++i) {
|
|
var c = cells[i]
|
|
for(var j=0; j<c.length; ++j) {
|
|
var vj = findCell(vertices, [c[j]])
|
|
for(var k=j+1; k<c.length; ++k) {
|
|
labels.link(vj, findCell(vertices, [c[k]]))
|
|
}
|
|
}
|
|
}
|
|
var components = []
|
|
, component_labels = labels.ranks
|
|
for(var i=0; i<component_labels.length; ++i) {
|
|
component_labels[i] = -1
|
|
}
|
|
for(var i=0; i<cells.length; ++i) {
|
|
var l = labels.find(findCell(vertices, [cells[i][0]]));
|
|
if(component_labels[l] < 0) {
|
|
component_labels[l] = components.length
|
|
components.push([cells[i].slice(0)])
|
|
} else {
|
|
components[component_labels[l]].push(cells[i].slice(0))
|
|
}
|
|
}
|
|
return components
|
|
}
|
|
|
|
//Computes connected components for a cell complex
|
|
function connectedComponents(cells, vertex_count) {
|
|
if(vertex_count) {
|
|
return connectedComponents_dense(cells, vertex_count)
|
|
}
|
|
return connectedComponents_sparse(cells)
|
|
}
|
|
exports.connectedComponents = connectedComponents
|
|
|