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.
434 lines
11 KiB
434 lines
11 KiB
'use strict'
|
|
|
|
module.exports = {
|
|
init: sqInit,
|
|
sweepBipartite: sweepBipartite,
|
|
sweepComplete: sweepComplete,
|
|
scanBipartite: scanBipartite,
|
|
scanComplete: scanComplete
|
|
}
|
|
|
|
var pool = require('typedarray-pool')
|
|
var bits = require('bit-twiddle')
|
|
var isort = require('./sort')
|
|
|
|
//Flag for blue
|
|
var BLUE_FLAG = (1<<28)
|
|
|
|
//1D sweep event queue stuff (use pool to save space)
|
|
var INIT_CAPACITY = 1024
|
|
var RED_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY)
|
|
var RED_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY)
|
|
var BLUE_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY)
|
|
var BLUE_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY)
|
|
var COMMON_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY)
|
|
var COMMON_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY)
|
|
var SWEEP_EVENTS = pool.mallocDouble(INIT_CAPACITY * 8)
|
|
|
|
//Reserves memory for the 1D sweep data structures
|
|
function sqInit(count) {
|
|
var rcount = bits.nextPow2(count)
|
|
if(RED_SWEEP_QUEUE.length < rcount) {
|
|
pool.free(RED_SWEEP_QUEUE)
|
|
RED_SWEEP_QUEUE = pool.mallocInt32(rcount)
|
|
}
|
|
if(RED_SWEEP_INDEX.length < rcount) {
|
|
pool.free(RED_SWEEP_INDEX)
|
|
RED_SWEEP_INDEX = pool.mallocInt32(rcount)
|
|
}
|
|
if(BLUE_SWEEP_QUEUE.length < rcount) {
|
|
pool.free(BLUE_SWEEP_QUEUE)
|
|
BLUE_SWEEP_QUEUE = pool.mallocInt32(rcount)
|
|
}
|
|
if(BLUE_SWEEP_INDEX.length < rcount) {
|
|
pool.free(BLUE_SWEEP_INDEX)
|
|
BLUE_SWEEP_INDEX = pool.mallocInt32(rcount)
|
|
}
|
|
if(COMMON_SWEEP_QUEUE.length < rcount) {
|
|
pool.free(COMMON_SWEEP_QUEUE)
|
|
COMMON_SWEEP_QUEUE = pool.mallocInt32(rcount)
|
|
}
|
|
if(COMMON_SWEEP_INDEX.length < rcount) {
|
|
pool.free(COMMON_SWEEP_INDEX)
|
|
COMMON_SWEEP_INDEX = pool.mallocInt32(rcount)
|
|
}
|
|
var eventLength = 8 * rcount
|
|
if(SWEEP_EVENTS.length < eventLength) {
|
|
pool.free(SWEEP_EVENTS)
|
|
SWEEP_EVENTS = pool.mallocDouble(eventLength)
|
|
}
|
|
}
|
|
|
|
//Remove an item from the active queue in O(1)
|
|
function sqPop(queue, index, count, item) {
|
|
var idx = index[item]
|
|
var top = queue[count-1]
|
|
queue[idx] = top
|
|
index[top] = idx
|
|
}
|
|
|
|
//Insert an item into the active queue in O(1)
|
|
function sqPush(queue, index, count, item) {
|
|
queue[count] = item
|
|
index[item] = count
|
|
}
|
|
|
|
//Recursion base case: use 1D sweep algorithm
|
|
function sweepBipartite(
|
|
d, visit,
|
|
redStart, redEnd, red, redIndex,
|
|
blueStart, blueEnd, blue, blueIndex) {
|
|
|
|
//store events as pairs [coordinate, idx]
|
|
//
|
|
// red create: -(idx+1)
|
|
// red destroy: idx
|
|
// blue create: -(idx+BLUE_FLAG)
|
|
// blue destroy: idx+BLUE_FLAG
|
|
//
|
|
var ptr = 0
|
|
var elemSize = 2*d
|
|
var istart = d-1
|
|
var iend = elemSize-1
|
|
|
|
for(var i=redStart; i<redEnd; ++i) {
|
|
var idx = redIndex[i]
|
|
var redOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -(idx+1)
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+iend]
|
|
SWEEP_EVENTS[ptr++] = idx
|
|
}
|
|
|
|
for(var i=blueStart; i<blueEnd; ++i) {
|
|
var idx = blueIndex[i]+BLUE_FLAG
|
|
var blueOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = blue[blueOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -idx
|
|
SWEEP_EVENTS[ptr++] = blue[blueOffset+iend]
|
|
SWEEP_EVENTS[ptr++] = idx
|
|
}
|
|
|
|
//process events from left->right
|
|
var n = ptr >>> 1
|
|
isort(SWEEP_EVENTS, n)
|
|
|
|
var redActive = 0
|
|
var blueActive = 0
|
|
for(var i=0; i<n; ++i) {
|
|
var e = SWEEP_EVENTS[2*i+1]|0
|
|
if(e >= BLUE_FLAG) {
|
|
//blue destroy event
|
|
e = (e-BLUE_FLAG)|0
|
|
sqPop(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive--, e)
|
|
} else if(e >= 0) {
|
|
//red destroy event
|
|
sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, e)
|
|
} else if(e <= -BLUE_FLAG) {
|
|
//blue create event
|
|
e = (-e-BLUE_FLAG)|0
|
|
for(var j=0; j<redActive; ++j) {
|
|
var retval = visit(RED_SWEEP_QUEUE[j], e)
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
sqPush(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive++, e)
|
|
} else {
|
|
//red create event
|
|
e = (-e-1)|0
|
|
for(var j=0; j<blueActive; ++j) {
|
|
var retval = visit(e, BLUE_SWEEP_QUEUE[j])
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, e)
|
|
}
|
|
}
|
|
}
|
|
|
|
//Complete sweep
|
|
function sweepComplete(d, visit,
|
|
redStart, redEnd, red, redIndex,
|
|
blueStart, blueEnd, blue, blueIndex) {
|
|
|
|
var ptr = 0
|
|
var elemSize = 2*d
|
|
var istart = d-1
|
|
var iend = elemSize-1
|
|
|
|
for(var i=redStart; i<redEnd; ++i) {
|
|
var idx = (redIndex[i]+1)<<1
|
|
var redOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -idx
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+iend]
|
|
SWEEP_EVENTS[ptr++] = idx
|
|
}
|
|
|
|
for(var i=blueStart; i<blueEnd; ++i) {
|
|
var idx = (blueIndex[i]+1)<<1
|
|
var blueOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = blue[blueOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = (-idx)|1
|
|
SWEEP_EVENTS[ptr++] = blue[blueOffset+iend]
|
|
SWEEP_EVENTS[ptr++] = idx|1
|
|
}
|
|
|
|
//process events from left->right
|
|
var n = ptr >>> 1
|
|
isort(SWEEP_EVENTS, n)
|
|
|
|
var redActive = 0
|
|
var blueActive = 0
|
|
var commonActive = 0
|
|
for(var i=0; i<n; ++i) {
|
|
var e = SWEEP_EVENTS[2*i+1]|0
|
|
var color = e&1
|
|
if(i < n-1 && (e>>1) === (SWEEP_EVENTS[2*i+3]>>1)) {
|
|
color = 2
|
|
i += 1
|
|
}
|
|
|
|
if(e < 0) {
|
|
//Create event
|
|
var id = -(e>>1) - 1
|
|
|
|
//Intersect with common
|
|
for(var j=0; j<commonActive; ++j) {
|
|
var retval = visit(COMMON_SWEEP_QUEUE[j], id)
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
|
|
if(color !== 0) {
|
|
//Intersect with red
|
|
for(var j=0; j<redActive; ++j) {
|
|
var retval = visit(RED_SWEEP_QUEUE[j], id)
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
}
|
|
|
|
if(color !== 1) {
|
|
//Intersect with blue
|
|
for(var j=0; j<blueActive; ++j) {
|
|
var retval = visit(BLUE_SWEEP_QUEUE[j], id)
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
}
|
|
|
|
if(color === 0) {
|
|
//Red
|
|
sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, id)
|
|
} else if(color === 1) {
|
|
//Blue
|
|
sqPush(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive++, id)
|
|
} else if(color === 2) {
|
|
//Both
|
|
sqPush(COMMON_SWEEP_QUEUE, COMMON_SWEEP_INDEX, commonActive++, id)
|
|
}
|
|
} else {
|
|
//Destroy event
|
|
var id = (e>>1) - 1
|
|
if(color === 0) {
|
|
//Red
|
|
sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, id)
|
|
} else if(color === 1) {
|
|
//Blue
|
|
sqPop(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive--, id)
|
|
} else if(color === 2) {
|
|
//Both
|
|
sqPop(COMMON_SWEEP_QUEUE, COMMON_SWEEP_INDEX, commonActive--, id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Sweep and prune/scanline algorithm:
|
|
// Scan along axis, detect intersections
|
|
// Brute force all boxes along axis
|
|
function scanBipartite(
|
|
d, axis, visit, flip,
|
|
redStart, redEnd, red, redIndex,
|
|
blueStart, blueEnd, blue, blueIndex) {
|
|
|
|
var ptr = 0
|
|
var elemSize = 2*d
|
|
var istart = axis
|
|
var iend = axis+d
|
|
|
|
var redShift = 1
|
|
var blueShift = 1
|
|
if(flip) {
|
|
blueShift = BLUE_FLAG
|
|
} else {
|
|
redShift = BLUE_FLAG
|
|
}
|
|
|
|
for(var i=redStart; i<redEnd; ++i) {
|
|
var idx = i + redShift
|
|
var redOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -idx
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+iend]
|
|
SWEEP_EVENTS[ptr++] = idx
|
|
}
|
|
for(var i=blueStart; i<blueEnd; ++i) {
|
|
var idx = i + blueShift
|
|
var blueOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = blue[blueOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -idx
|
|
}
|
|
|
|
//process events from left->right
|
|
var n = ptr >>> 1
|
|
isort(SWEEP_EVENTS, n)
|
|
|
|
var redActive = 0
|
|
for(var i=0; i<n; ++i) {
|
|
var e = SWEEP_EVENTS[2*i+1]|0
|
|
if(e < 0) {
|
|
var idx = -e
|
|
var isRed = false
|
|
if(idx >= BLUE_FLAG) {
|
|
isRed = !flip
|
|
idx -= BLUE_FLAG
|
|
} else {
|
|
isRed = !!flip
|
|
idx -= 1
|
|
}
|
|
if(isRed) {
|
|
sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, idx)
|
|
} else {
|
|
var blueId = blueIndex[idx]
|
|
var bluePtr = elemSize * idx
|
|
|
|
var b0 = blue[bluePtr+axis+1]
|
|
var b1 = blue[bluePtr+axis+1+d]
|
|
|
|
red_loop:
|
|
for(var j=0; j<redActive; ++j) {
|
|
var oidx = RED_SWEEP_QUEUE[j]
|
|
var redPtr = elemSize * oidx
|
|
|
|
if(b1 < red[redPtr+axis+1] ||
|
|
red[redPtr+axis+1+d] < b0) {
|
|
continue
|
|
}
|
|
|
|
for(var k=axis+2; k<d; ++k) {
|
|
if(blue[bluePtr + k + d] < red[redPtr + k] ||
|
|
red[redPtr + k + d] < blue[bluePtr + k]) {
|
|
continue red_loop
|
|
}
|
|
}
|
|
|
|
var redId = redIndex[oidx]
|
|
var retval
|
|
if(flip) {
|
|
retval = visit(blueId, redId)
|
|
} else {
|
|
retval = visit(redId, blueId)
|
|
}
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, e - redShift)
|
|
}
|
|
}
|
|
}
|
|
|
|
function scanComplete(
|
|
d, axis, visit,
|
|
redStart, redEnd, red, redIndex,
|
|
blueStart, blueEnd, blue, blueIndex) {
|
|
|
|
var ptr = 0
|
|
var elemSize = 2*d
|
|
var istart = axis
|
|
var iend = axis+d
|
|
|
|
for(var i=redStart; i<redEnd; ++i) {
|
|
var idx = i + BLUE_FLAG
|
|
var redOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -idx
|
|
SWEEP_EVENTS[ptr++] = red[redOffset+iend]
|
|
SWEEP_EVENTS[ptr++] = idx
|
|
}
|
|
for(var i=blueStart; i<blueEnd; ++i) {
|
|
var idx = i + 1
|
|
var blueOffset = elemSize*i
|
|
SWEEP_EVENTS[ptr++] = blue[blueOffset+istart]
|
|
SWEEP_EVENTS[ptr++] = -idx
|
|
}
|
|
|
|
//process events from left->right
|
|
var n = ptr >>> 1
|
|
isort(SWEEP_EVENTS, n)
|
|
|
|
var redActive = 0
|
|
for(var i=0; i<n; ++i) {
|
|
var e = SWEEP_EVENTS[2*i+1]|0
|
|
if(e < 0) {
|
|
var idx = -e
|
|
if(idx >= BLUE_FLAG) {
|
|
RED_SWEEP_QUEUE[redActive++] = idx - BLUE_FLAG
|
|
} else {
|
|
idx -= 1
|
|
var blueId = blueIndex[idx]
|
|
var bluePtr = elemSize * idx
|
|
|
|
var b0 = blue[bluePtr+axis+1]
|
|
var b1 = blue[bluePtr+axis+1+d]
|
|
|
|
red_loop:
|
|
for(var j=0; j<redActive; ++j) {
|
|
var oidx = RED_SWEEP_QUEUE[j]
|
|
var redId = redIndex[oidx]
|
|
|
|
if(redId === blueId) {
|
|
break
|
|
}
|
|
|
|
var redPtr = elemSize * oidx
|
|
if(b1 < red[redPtr+axis+1] ||
|
|
red[redPtr+axis+1+d] < b0) {
|
|
continue
|
|
}
|
|
for(var k=axis+2; k<d; ++k) {
|
|
if(blue[bluePtr + k + d] < red[redPtr + k] ||
|
|
red[redPtr + k + d] < blue[bluePtr + k]) {
|
|
continue red_loop
|
|
}
|
|
}
|
|
|
|
var retval = visit(redId, blueId)
|
|
if(retval !== void 0) {
|
|
return retval
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var idx = e - BLUE_FLAG
|
|
for(var j=redActive-1; j>=0; --j) {
|
|
if(RED_SWEEP_QUEUE[j] === idx) {
|
|
for(var k=j+1; k<redActive; ++k) {
|
|
RED_SWEEP_QUEUE[k-1] = RED_SWEEP_QUEUE[k]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
--redActive
|
|
}
|
|
}
|
|
} |