webgme-gridlabd
Version:
Metamodel, visualization, and model generators for gridlab-d in WebGME. Allows graphical model-driven development and simulation of power grids and power generation / transmission / distribution / storage systems.
1,152 lines (1,151 loc) • 227 kB
JavaScript
var cola;
(function (cola) {
var packingOptions = {
PADDING: 10,
GOLDEN_SECTION: (1 + Math.sqrt(5)) / 2,
FLOAT_EPSILON: 0.0001,
MAX_INERATIONS: 100
};
// assign x, y to nodes while using box packing algorithm for disconnected graphs
function applyPacking(graphs, w, h, node_size, desired_ratio) {
if (desired_ratio === void 0) { desired_ratio = 1; }
var init_x = 0, init_y = 0, svg_width = w, svg_height = h, desired_ratio = typeof desired_ratio !== 'undefined' ? desired_ratio : 1, node_size = typeof node_size !== 'undefined' ? node_size : 0, real_width = 0, real_height = 0, min_width = 0, global_bottom = 0, line = [];
if (graphs.length == 0)
return;
/// that would take care of single nodes problem
// graphs.forEach(function (g) {
// if (g.array.length == 1) {
// g.array[0].x = 0;
// g.array[0].y = 0;
// }
// });
calculate_bb(graphs);
apply(graphs, desired_ratio);
put_nodes_to_right_positions(graphs);
// get bounding boxes for all separate graphs
function calculate_bb(graphs) {
graphs.forEach(function (g) {
calculate_single_bb(g);
});
function calculate_single_bb(graph) {
var min_x = Number.MAX_VALUE, min_y = Number.MAX_VALUE, max_x = 0, max_y = 0;
graph.array.forEach(function (v) {
var w = typeof v.width !== 'undefined' ? v.width : node_size;
var h = typeof v.height !== 'undefined' ? v.height : node_size;
w /= 2;
h /= 2;
max_x = Math.max(v.x + w, max_x);
min_x = Math.min(v.x - w, min_x);
max_y = Math.max(v.y + h, max_y);
min_y = Math.min(v.y - h, min_y);
});
graph.width = max_x - min_x;
graph.height = max_y - min_y;
}
}
//function plot(data, left, right, opt_x, opt_y) {
// // plot the cost function
// var plot_svg = d3.select("body").append("svg")
// .attr("width", function () { return 2 * (right - left); })
// .attr("height", 200);
// var x = d3.time.scale().range([0, 2 * (right - left)]);
// var xAxis = d3.svg.axis().scale(x).orient("bottom");
// plot_svg.append("g").attr("class", "x axis")
// .attr("transform", "translate(0, 199)")
// .call(xAxis);
// var lastX = 0;
// var lastY = 0;
// var value = 0;
// for (var r = left; r < right; r += 1) {
// value = step(data, r);
// // value = 1;
// plot_svg.append("line").attr("x1", 2 * (lastX - left))
// .attr("y1", 200 - 30 * lastY)
// .attr("x2", 2 * r - 2 * left)
// .attr("y2", 200 - 30 * value)
// .style("stroke", "rgb(6,120,155)");
// lastX = r;
// lastY = value;
// }
// plot_svg.append("circle").attr("cx", 2 * opt_x - 2 * left).attr("cy", 200 - 30 * opt_y)
// .attr("r", 5).style('fill', "rgba(0,0,0,0.5)");
//}
// actual assigning of position to nodes
function put_nodes_to_right_positions(graphs) {
graphs.forEach(function (g) {
// calculate current graph center:
var center = { x: 0, y: 0 };
g.array.forEach(function (node) {
center.x += node.x;
center.y += node.y;
});
center.x /= g.array.length;
center.y /= g.array.length;
// calculate current top left corner:
var corner = { x: center.x - g.width / 2, y: center.y - g.height / 2 };
var offset = { x: g.x - corner.x + svg_width / 2 - real_width / 2, y: g.y - corner.y + svg_height / 2 - real_height / 2 };
// put nodes:
g.array.forEach(function (node) {
node.x += offset.x;
node.y += offset.y;
});
});
}
// starts box packing algorithm
// desired ratio is 1 by default
function apply(data, desired_ratio) {
var curr_best_f = Number.POSITIVE_INFINITY;
var curr_best = 0;
data.sort(function (a, b) { return b.height - a.height; });
min_width = data.reduce(function (a, b) {
return a.width < b.width ? a.width : b.width;
});
var left = x1 = min_width;
var right = x2 = get_entire_width(data);
var iterationCounter = 0;
var f_x1 = Number.MAX_VALUE;
var f_x2 = Number.MAX_VALUE;
var flag = -1; // determines which among f_x1 and f_x2 to recompute
var dx = Number.MAX_VALUE;
var df = Number.MAX_VALUE;
while ((dx > min_width) || df > packingOptions.FLOAT_EPSILON) {
if (flag != 1) {
var x1 = right - (right - left) / packingOptions.GOLDEN_SECTION;
var f_x1 = step(data, x1);
}
if (flag != 0) {
var x2 = left + (right - left) / packingOptions.GOLDEN_SECTION;
var f_x2 = step(data, x2);
}
dx = Math.abs(x1 - x2);
df = Math.abs(f_x1 - f_x2);
if (f_x1 < curr_best_f) {
curr_best_f = f_x1;
curr_best = x1;
}
if (f_x2 < curr_best_f) {
curr_best_f = f_x2;
curr_best = x2;
}
if (f_x1 > f_x2) {
left = x1;
x1 = x2;
f_x1 = f_x2;
flag = 1;
}
else {
right = x2;
x2 = x1;
f_x2 = f_x1;
flag = 0;
}
if (iterationCounter++ > 100) {
break;
}
}
// plot(data, min_width, get_entire_width(data), curr_best, curr_best_f);
step(data, curr_best);
}
// one iteration of the optimization method
// (gives a proper, but not necessarily optimal packing)
function step(data, max_width) {
line = [];
real_width = 0;
real_height = 0;
global_bottom = init_y;
for (var i = 0; i < data.length; i++) {
var o = data[i];
put_rect(o, max_width);
}
return Math.abs(get_real_ratio() - desired_ratio);
}
// looking for a position to one box
function put_rect(rect, max_width) {
var parent = undefined;
for (var i = 0; i < line.length; i++) {
if ((line[i].space_left >= rect.height) && (line[i].x + line[i].width + rect.width + packingOptions.PADDING - max_width) <= packingOptions.FLOAT_EPSILON) {
parent = line[i];
break;
}
}
line.push(rect);
if (parent !== undefined) {
rect.x = parent.x + parent.width + packingOptions.PADDING;
rect.y = parent.bottom;
rect.space_left = rect.height;
rect.bottom = rect.y;
parent.space_left -= rect.height + packingOptions.PADDING;
parent.bottom += rect.height + packingOptions.PADDING;
}
else {
rect.y = global_bottom;
global_bottom += rect.height + packingOptions.PADDING;
rect.x = init_x;
rect.bottom = rect.y;
rect.space_left = rect.height;
}
if (rect.y + rect.height - real_height > -packingOptions.FLOAT_EPSILON)
real_height = rect.y + rect.height - init_y;
if (rect.x + rect.width - real_width > -packingOptions.FLOAT_EPSILON)
real_width = rect.x + rect.width - init_x;
}
;
function get_entire_width(data) {
var width = 0;
data.forEach(function (d) { return width += d.width + packingOptions.PADDING; });
return width;
}
function get_real_ratio() {
return (real_width / real_height);
}
}
cola.applyPacking = applyPacking;
/**
* connected components of graph
* returns an array of {}
*/
function separateGraphs(nodes, links) {
var marks = {};
var ways = {};
var graphs = [];
var clusters = 0;
for (var i = 0; i < links.length; i++) {
var link = links[i];
var n1 = link.source;
var n2 = link.target;
if (ways[n1.index])
ways[n1.index].push(n2);
else
ways[n1.index] = [n2];
if (ways[n2.index])
ways[n2.index].push(n1);
else
ways[n2.index] = [n1];
}
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (marks[node.index])
continue;
explore_node(node, true);
}
function explore_node(n, is_new) {
if (marks[n.index] !== undefined)
return;
if (is_new) {
clusters++;
graphs.push({ array: [] });
}
marks[n.index] = clusters;
graphs[clusters - 1].array.push(n);
var adjacent = ways[n.index];
if (!adjacent)
return;
for (var j = 0; j < adjacent.length; j++) {
explore_node(adjacent[j], false);
}
}
return graphs;
}
cola.separateGraphs = separateGraphs;
})(cola || (cola = {}));
var cola;
(function (cola) {
var vpsc;
(function (vpsc) {
var PositionStats = (function () {
function PositionStats(scale) {
this.scale = scale;
this.AB = 0;
this.AD = 0;
this.A2 = 0;
}
PositionStats.prototype.addVariable = function (v) {
var ai = this.scale / v.scale;
var bi = v.offset / v.scale;
var wi = v.weight;
this.AB += wi * ai * bi;
this.AD += wi * ai * v.desiredPosition;
this.A2 += wi * ai * ai;
};
PositionStats.prototype.getPosn = function () {
return (this.AD - this.AB) / this.A2;
};
return PositionStats;
})();
vpsc.PositionStats = PositionStats;
var Constraint = (function () {
function Constraint(left, right, gap, equality) {
if (equality === void 0) { equality = false; }
this.left = left;
this.right = right;
this.gap = gap;
this.equality = equality;
this.active = false;
this.unsatisfiable = false;
this.left = left;
this.right = right;
this.gap = gap;
this.equality = equality;
}
Constraint.prototype.slack = function () {
return this.unsatisfiable ? Number.MAX_VALUE
: this.right.scale * this.right.position() - this.gap
- this.left.scale * this.left.position();
};
return Constraint;
})();
vpsc.Constraint = Constraint;
var Variable = (function () {
function Variable(desiredPosition, weight, scale) {
if (weight === void 0) { weight = 1; }
if (scale === void 0) { scale = 1; }
this.desiredPosition = desiredPosition;
this.weight = weight;
this.scale = scale;
this.offset = 0;
}
Variable.prototype.dfdv = function () {
return 2.0 * this.weight * (this.position() - this.desiredPosition);
};
Variable.prototype.position = function () {
return (this.block.ps.scale * this.block.posn + this.offset) / this.scale;
};
// visit neighbours by active constraints within the same block
Variable.prototype.visitNeighbours = function (prev, f) {
var ff = function (c, next) { return c.active && prev !== next && f(c, next); };
this.cOut.forEach(function (c) { return ff(c, c.right); });
this.cIn.forEach(function (c) { return ff(c, c.left); });
};
return Variable;
})();
vpsc.Variable = Variable;
var Block = (function () {
function Block(v) {
this.vars = [];
v.offset = 0;
this.ps = new PositionStats(v.scale);
this.addVariable(v);
}
Block.prototype.addVariable = function (v) {
v.block = this;
this.vars.push(v);
this.ps.addVariable(v);
this.posn = this.ps.getPosn();
};
// move the block where it needs to be to minimize cost
Block.prototype.updateWeightedPosition = function () {
this.ps.AB = this.ps.AD = this.ps.A2 = 0;
for (var i = 0, n = this.vars.length; i < n; ++i)
this.ps.addVariable(this.vars[i]);
this.posn = this.ps.getPosn();
};
Block.prototype.compute_lm = function (v, u, postAction) {
var _this = this;
var dfdv = v.dfdv();
v.visitNeighbours(u, function (c, next) {
var _dfdv = _this.compute_lm(next, v, postAction);
if (next === c.right) {
dfdv += _dfdv * c.left.scale;
c.lm = _dfdv;
}
else {
dfdv += _dfdv * c.right.scale;
c.lm = -_dfdv;
}
postAction(c);
});
return dfdv / v.scale;
};
Block.prototype.populateSplitBlock = function (v, prev) {
var _this = this;
v.visitNeighbours(prev, function (c, next) {
next.offset = v.offset + (next === c.right ? c.gap : -c.gap);
_this.addVariable(next);
_this.populateSplitBlock(next, v);
});
};
// traverse the active constraint tree applying visit to each active constraint
Block.prototype.traverse = function (visit, acc, v, prev) {
var _this = this;
if (v === void 0) { v = this.vars[0]; }
if (prev === void 0) { prev = null; }
v.visitNeighbours(prev, function (c, next) {
acc.push(visit(c));
_this.traverse(visit, acc, next, v);
});
};
// calculate lagrangian multipliers on constraints and
// find the active constraint in this block with the smallest lagrangian.
// if the lagrangian is negative, then the constraint is a split candidate.
Block.prototype.findMinLM = function () {
var m = null;
this.compute_lm(this.vars[0], null, function (c) {
if (!c.equality && (m === null || c.lm < m.lm))
m = c;
});
return m;
};
Block.prototype.findMinLMBetween = function (lv, rv) {
this.compute_lm(lv, null, function () { });
var m = null;
this.findPath(lv, null, rv, function (c, next) {
if (!c.equality && c.right === next && (m === null || c.lm < m.lm))
m = c;
});
return m;
};
Block.prototype.findPath = function (v, prev, to, visit) {
var _this = this;
var endFound = false;
v.visitNeighbours(prev, function (c, next) {
if (!endFound && (next === to || _this.findPath(next, v, to, visit))) {
endFound = true;
visit(c, next);
}
});
return endFound;
};
// Search active constraint tree from u to see if there is a directed path to v.
// Returns true if path is found.
Block.prototype.isActiveDirectedPathBetween = function (u, v) {
if (u === v)
return true;
var i = u.cOut.length;
while (i--) {
var c = u.cOut[i];
if (c.active && this.isActiveDirectedPathBetween(c.right, v))
return true;
}
return false;
};
// split the block into two by deactivating the specified constraint
Block.split = function (c) {
/* DEBUG
console.log("split on " + c);
console.assert(c.active, "attempt to split on inactive constraint");
DEBUG */
c.active = false;
return [Block.createSplitBlock(c.left), Block.createSplitBlock(c.right)];
};
Block.createSplitBlock = function (startVar) {
var b = new Block(startVar);
b.populateSplitBlock(startVar, null);
return b;
};
// find a split point somewhere between the specified variables
Block.prototype.splitBetween = function (vl, vr) {
/* DEBUG
console.assert(vl.block === this);
console.assert(vr.block === this);
DEBUG */
var c = this.findMinLMBetween(vl, vr);
if (c !== null) {
var bs = Block.split(c);
return { constraint: c, lb: bs[0], rb: bs[1] };
}
// couldn't find a split point - for example the active path is all equality constraints
return null;
};
Block.prototype.mergeAcross = function (b, c, dist) {
c.active = true;
for (var i = 0, n = b.vars.length; i < n; ++i) {
var v = b.vars[i];
v.offset += dist;
this.addVariable(v);
}
this.posn = this.ps.getPosn();
};
Block.prototype.cost = function () {
var sum = 0, i = this.vars.length;
while (i--) {
var v = this.vars[i], d = v.position() - v.desiredPosition;
sum += d * d * v.weight;
}
return sum;
};
return Block;
})();
vpsc.Block = Block;
var Blocks = (function () {
function Blocks(vs) {
this.vs = vs;
var n = vs.length;
this.list = new Array(n);
while (n--) {
var b = new Block(vs[n]);
this.list[n] = b;
b.blockInd = n;
}
}
Blocks.prototype.cost = function () {
var sum = 0, i = this.list.length;
while (i--)
sum += this.list[i].cost();
return sum;
};
Blocks.prototype.insert = function (b) {
/* DEBUG
console.assert(!this.contains(b), "blocks error: tried to reinsert block " + b.blockInd)
DEBUG */
b.blockInd = this.list.length;
this.list.push(b);
/* DEBUG
console.log("insert block: " + b.blockInd);
this.contains(b);
DEBUG */
};
Blocks.prototype.remove = function (b) {
/* DEBUG
console.log("remove block: " + b.blockInd);
console.assert(this.contains(b));
DEBUG */
var last = this.list.length - 1;
var swapBlock = this.list[last];
this.list.length = last;
if (b !== swapBlock) {
this.list[b.blockInd] = swapBlock;
swapBlock.blockInd = b.blockInd;
}
};
// merge the blocks on either side of the specified constraint, by copying the smaller block into the larger
// and deleting the smaller.
Blocks.prototype.merge = function (c) {
var l = c.left.block, r = c.right.block;
/* DEBUG
console.assert(l!==r, "attempt to merge within the same block");
DEBUG */
var dist = c.right.offset - c.left.offset - c.gap;
if (l.vars.length < r.vars.length) {
r.mergeAcross(l, c, dist);
this.remove(l);
}
else {
l.mergeAcross(r, c, -dist);
this.remove(r);
}
/* DEBUG
console.assert(Math.abs(c.slack()) < 1e-6, "Error: Constraint should be at equality after merge!");
console.log("merged on " + c);
DEBUG */
};
Blocks.prototype.forEach = function (f) {
this.list.forEach(f);
};
// useful, for example, after variable desired positions change.
Blocks.prototype.updateBlockPositions = function () {
this.list.forEach(function (b) { return b.updateWeightedPosition(); });
};
// split each block across its constraint with the minimum lagrangian
Blocks.prototype.split = function (inactive) {
var _this = this;
this.updateBlockPositions();
this.list.forEach(function (b) {
var v = b.findMinLM();
if (v !== null && v.lm < Solver.LAGRANGIAN_TOLERANCE) {
b = v.left.block;
Block.split(v).forEach(function (nb) { return _this.insert(nb); });
_this.remove(b);
inactive.push(v);
}
});
};
return Blocks;
})();
vpsc.Blocks = Blocks;
var Solver = (function () {
function Solver(vs, cs) {
this.vs = vs;
this.cs = cs;
this.vs = vs;
vs.forEach(function (v) {
v.cIn = [], v.cOut = [];
/* DEBUG
v.toString = () => "v" + vs.indexOf(v);
DEBUG */
});
this.cs = cs;
cs.forEach(function (c) {
c.left.cOut.push(c);
c.right.cIn.push(c);
/* DEBUG
c.toString = () => c.left + "+" + c.gap + "<=" + c.right + " slack=" + c.slack() + " active=" + c.active;
DEBUG */
});
this.inactive = cs.map(function (c) { c.active = false; return c; });
this.bs = null;
}
Solver.prototype.cost = function () {
return this.bs.cost();
};
// set starting positions without changing desired positions.
// Note: it throws away any previous block structure.
Solver.prototype.setStartingPositions = function (ps) {
this.inactive = this.cs.map(function (c) { c.active = false; return c; });
this.bs = new Blocks(this.vs);
this.bs.forEach(function (b, i) { return b.posn = ps[i]; });
};
Solver.prototype.setDesiredPositions = function (ps) {
this.vs.forEach(function (v, i) { return v.desiredPosition = ps[i]; });
};
/* DEBUG
private getId(v: Variable): number {
return this.vs.indexOf(v);
}
// sanity check of the index integrity of the inactive list
checkInactive(): void {
var inactiveCount = 0;
this.cs.forEach(c=> {
var i = this.inactive.indexOf(c);
console.assert(!c.active && i >= 0 || c.active && i < 0, "constraint should be in the inactive list if it is not active: " + c);
if (i >= 0) {
inactiveCount++;
} else {
console.assert(c.active, "inactive constraint not found in inactive list: " + c);
}
});
console.assert(inactiveCount === this.inactive.length, inactiveCount + " inactive constraints found, " + this.inactive.length + "in inactive list");
}
// after every call to satisfy the following should check should pass
checkSatisfied(): void {
this.cs.forEach(c=>console.assert(c.slack() >= vpsc.Solver.ZERO_UPPERBOUND, "Error: Unsatisfied constraint! "+c));
}
DEBUG */
Solver.prototype.mostViolated = function () {
var minSlack = Number.MAX_VALUE, v = null, l = this.inactive, n = l.length, deletePoint = n;
for (var i = 0; i < n; ++i) {
var c = l[i];
if (c.unsatisfiable)
continue;
var slack = c.slack();
if (c.equality || slack < minSlack) {
minSlack = slack;
v = c;
deletePoint = i;
if (c.equality)
break;
}
}
if (deletePoint !== n &&
(minSlack < Solver.ZERO_UPPERBOUND && !v.active || v.equality)) {
l[deletePoint] = l[n - 1];
l.length = n - 1;
}
return v;
};
// satisfy constraints by building block structure over violated constraints
// and moving the blocks to their desired positions
Solver.prototype.satisfy = function () {
if (this.bs == null) {
this.bs = new Blocks(this.vs);
}
/* DEBUG
console.log("satisfy: " + this.bs);
DEBUG */
this.bs.split(this.inactive);
var v = null;
while ((v = this.mostViolated()) && (v.equality || v.slack() < Solver.ZERO_UPPERBOUND && !v.active)) {
var lb = v.left.block, rb = v.right.block;
/* DEBUG
console.log("most violated is: " + v);
this.bs.contains(lb);
this.bs.contains(rb);
DEBUG */
if (lb !== rb) {
this.bs.merge(v);
}
else {
if (lb.isActiveDirectedPathBetween(v.right, v.left)) {
// cycle found!
v.unsatisfiable = true;
continue;
}
// constraint is within block, need to split first
var split = lb.splitBetween(v.left, v.right);
if (split !== null) {
this.bs.insert(split.lb);
this.bs.insert(split.rb);
this.bs.remove(lb);
this.inactive.push(split.constraint);
}
else {
/* DEBUG
console.log("unsatisfiable constraint found");
DEBUG */
v.unsatisfiable = true;
continue;
}
if (v.slack() >= 0) {
/* DEBUG
console.log("violated constraint indirectly satisfied: " + v);
DEBUG */
// v was satisfied by the above split!
this.inactive.push(v);
}
else {
/* DEBUG
console.log("merge after split:");
DEBUG */
this.bs.merge(v);
}
}
}
/* DEBUG
this.checkSatisfied();
DEBUG */
};
// repeatedly build and split block structure until we converge to an optimal solution
Solver.prototype.solve = function () {
this.satisfy();
var lastcost = Number.MAX_VALUE, cost = this.bs.cost();
while (Math.abs(lastcost - cost) > 0.0001) {
this.satisfy();
lastcost = cost;
cost = this.bs.cost();
}
return cost;
};
Solver.LAGRANGIAN_TOLERANCE = -1e-4;
Solver.ZERO_UPPERBOUND = -1e-10;
return Solver;
})();
vpsc.Solver = Solver;
})(vpsc = cola.vpsc || (cola.vpsc = {}));
})(cola || (cola = {}));
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var cola;
(function (cola) {
var vpsc;
(function (vpsc) {
//Based on js_es:
//
//https://github.com/vadimg/js_bintrees
//
//Copyright (C) 2011 by Vadim Graboys
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
var TreeBase = (function () {
function TreeBase() {
// returns iterator to node if found, null otherwise
this.findIter = function (data) {
var res = this._root;
var iter = this.iterator();
while (res !== null) {
var c = this._comparator(data, res.data);
if (c === 0) {
iter._cursor = res;
return iter;
}
else {
iter._ancestors.push(res);
res = res.get_child(c > 0);
}
}
return null;
};
}
// removes all nodes from the tree
TreeBase.prototype.clear = function () {
this._root = null;
this.size = 0;
};
;
// returns node data if found, null otherwise
TreeBase.prototype.find = function (data) {
var res = this._root;
while (res !== null) {
var c = this._comparator(data, res.data);
if (c === 0) {
return res.data;
}
else {
res = res.get_child(c > 0);
}
}
return null;
};
;
// Returns an interator to the tree node immediately before (or at) the element
TreeBase.prototype.lowerBound = function (data) {
return this._bound(data, this._comparator);
};
;
// Returns an interator to the tree node immediately after (or at) the element
TreeBase.prototype.upperBound = function (data) {
var cmp = this._comparator;
function reverse_cmp(a, b) {
return cmp(b, a);
}
return this._bound(data, reverse_cmp);
};
;
// returns null if tree is empty
TreeBase.prototype.min = function () {
var res = this._root;
if (res === null) {
return null;
}
while (res.left !== null) {
res = res.left;
}
return res.data;
};
;
// returns null if tree is empty
TreeBase.prototype.max = function () {
var res = this._root;
if (res === null) {
return null;
}
while (res.right !== null) {
res = res.right;
}
return res.data;
};
;
// returns a null iterator
// call next() or prev() to point to an element
TreeBase.prototype.iterator = function () {
return new Iterator(this);
};
;
// calls cb on each node's data, in order
TreeBase.prototype.each = function (cb) {
var it = this.iterator(), data;
while ((data = it.next()) !== null) {
cb(data);
}
};
;
// calls cb on each node's data, in reverse order
TreeBase.prototype.reach = function (cb) {
var it = this.iterator(), data;
while ((data = it.prev()) !== null) {
cb(data);
}
};
;
// used for lowerBound and upperBound
TreeBase.prototype._bound = function (data, cmp) {
var cur = this._root;
var iter = this.iterator();
while (cur !== null) {
var c = this._comparator(data, cur.data);
if (c === 0) {
iter._cursor = cur;
return iter;
}
iter._ancestors.push(cur);
cur = cur.get_child(c > 0);
}
for (var i = iter._ancestors.length - 1; i >= 0; --i) {
cur = iter._ancestors[i];
if (cmp(data, cur.data) > 0) {
iter._cursor = cur;
iter._ancestors.length = i;
return iter;
}
}
iter._ancestors.length = 0;
return iter;
};
;
return TreeBase;
})();
vpsc.TreeBase = TreeBase;
var Iterator = (function () {
function Iterator(tree) {
this._tree = tree;
this._ancestors = [];
this._cursor = null;
}
Iterator.prototype.data = function () {
return this._cursor !== null ? this._cursor.data : null;
};
;
// if null-iterator, returns first node
// otherwise, returns next node
Iterator.prototype.next = function () {
if (this._cursor === null) {
var root = this._tree._root;
if (root !== null) {
this._minNode(root);
}
}
else {
if (this._cursor.right === null) {
// no greater node in subtree, go up to parent
// if coming from a right child, continue up the stack
var save;
do {
save = this._cursor;
if (this._ancestors.length) {
this._cursor = this._ancestors.pop();
}
else {
this._cursor = null;
break;
}
} while (this._cursor.right === save);
}
else {
// get the next node from the subtree
this._ancestors.push(this._cursor);
this._minNode(this._cursor.right);
}
}
return this._cursor !== null ? this._cursor.data : null;
};
;
// if null-iterator, returns last node
// otherwise, returns previous node
Iterator.prototype.prev = function () {
if (this._cursor === null) {
var root = this._tree._root;
if (root !== null) {
this._maxNode(root);
}
}
else {
if (this._cursor.left === null) {
var save;
do {
save = this._cursor;
if (this._ancestors.length) {
this._cursor = this._ancestors.pop();
}
else {
this._cursor = null;
break;
}
} while (this._cursor.left === save);
}
else {
this._ancestors.push(this._cursor);
this._maxNode(this._cursor.left);
}
}
return this._cursor !== null ? this._cursor.data : null;
};
;
Iterator.prototype._minNode = function (start) {
while (start.left !== null) {
this._ancestors.push(start);
start = start.left;
}
this._cursor = start;
};
;
Iterator.prototype._maxNode = function (start) {
while (start.right !== null) {
this._ancestors.push(start);
start = start.right;
}
this._cursor = start;
};
;
return Iterator;
})();
vpsc.Iterator = Iterator;
var Node = (function () {
function Node(data) {
this.data = data;
this.left = null;
this.right = null;
this.red = true;
}
Node.prototype.get_child = function (dir) {
return dir ? this.right : this.left;
};
;
Node.prototype.set_child = function (dir, val) {
if (dir) {
this.right = val;
}
else {
this.left = val;
}
};
;
return Node;
})();
var RBTree = (function (_super) {
__extends(RBTree, _super);
function RBTree(comparator) {
_super.call(this);
this._root = null;
this._comparator = comparator;
this.size = 0;
}
// returns true if inserted, false if duplicate
RBTree.prototype.insert = function (data) {
var ret = false;
if (this._root === null) {
// empty tree
this._root = new Node(data);
ret = true;
this.size++;
}
else {
var head = new Node(undefined); // fake tree root
var dir = false;
var last = false;
// setup
var gp = null; // grandparent
var ggp = head; // grand-grand-parent
var p = null; // parent
var node = this._root;
ggp.right = this._root;
// search down
while (true) {
if (node === null) {
// insert new node at the bottom
node = new Node(data);
p.set_child(dir, node);
ret = true;
this.size++;
}
else if (RBTree.is_red(node.left) && RBTree.is_red(node.right)) {
// color flip
node.red = true;
node.left.red = false;
node.right.red = false;
}
// fix red violation
if (RBTree.is_red(node) && RBTree.is_red(p)) {
var dir2 = ggp.right === gp;
if (node === p.get_child(last)) {
ggp.set_child(dir2, RBTree.single_rotate(gp, !last));
}
else {
ggp.set_child(dir2, RBTree.double_rotate(gp, !last));
}
}
var cmp = this._comparator(node.data, data);
// stop if found
if (cmp === 0) {
break;
}
last = dir;
dir = cmp < 0;
// update helpers
if (gp !== null) {
ggp = gp;
}
gp = p;
p = node;
node = node.get_child(dir);
}
// update root
this._root = head.right;
}
// make root black
this._root.red = false;
return ret;
};
;
// returns true if removed, false if not found
RBTree.prototype.remove = function (data) {
if (this._root === null) {
return false;
}
var head = new Node(undefined); // fake tree root
var node = head;
node.right = this._root;
var p = null; // parent
var gp = null; // grand parent
var found = null; // found item
var dir = true;
while (node.get_child(dir) !== null) {
var last = dir;
// update helpers
gp = p;
p = node;
node = node.get_child(dir);
var cmp = this._comparator(data, node.data);
dir = cmp > 0;
// save found node
if (cmp === 0) {
found = node;
}
// push the red node down
if (!RBTree.is_red(node) && !RBTree.is_red(node.get_child(dir))) {
if (RBTree.is_red(node.get_child(!dir))) {
var sr = RBTree.single_rotate(node, dir);
p.set_child(last, sr);
p = sr;
}
else if (!RBTree.is_red(node.get_child(!dir))) {
var sibling = p.get_child(!last);
if (sibling !== null) {
if (!RBTree.is_red(sibling.get_child(!last)) && !RBTree.is_red(sibling.get_child(last))) {
// color flip
p.red = false;
sibling.red = true;
node.red = true;
}
else {
var dir2 = gp.right === p;
if (RBTree.is_red(sibling.get_child(last))) {
gp.set_child(dir2, RBTree.double_rotate(p, last));
}
else if (RBTree.is_red(sibling.get_child(!last))) {
gp.set_child(dir2, RBTree.single_rotate(p, last));
}
// ensure correct coloring
var gpc = gp.get_child(dir2);
gpc.red = true;
node.red = true;
gpc.left.red = false;
gpc.right.red = false;
}
}
}
}
}
// replace and remove if found
if (found !== null) {
found.data = node.data;
p.set_child(p.right === node, node.get_child(node.left === null));
this.size--;
}
// update root and make it black
this._root = head.right;
if (this._root !== null) {
this._root.red = false;
}
return found !== null;
};
;
RBTree.is_red = function (node) {
return node !== null && node.red;
};
RBTree.single_rotate = function (root, dir) {
var save = root.get_child(!dir);
root.set_child(!dir, save.get_child(dir));
save.set_child(dir, root);
root.red = true;
save.red = false;
return save;
};
RBTree.double_rotate = function (root, dir) {
root.set_child(!dir, RBTree.