UNPKG

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
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.