ngraph.path/dist/ngraph.path.js
2019-02-25 07:50:27 -08:00

1043 lines
30 KiB
JavaScript

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ngraphPath = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
/**
* Based on https://github.com/mourner/tinyqueue
* Copyright (c) 2017, Vladimir Agafonkin https://github.com/mourner/tinyqueue/blob/master/LICENSE
*
* Adapted for PathFinding needs by @anvaka
* Copyright (c) 2017, Andrei Kashcha
*/
module.exports = NodeHeap;
function NodeHeap(data, options) {
if (!(this instanceof NodeHeap)) return new NodeHeap(data, options);
if (!Array.isArray(data)) {
// assume first argument is our config object;
options = data;
data = [];
}
options = options || {};
this.data = data || [];
this.length = this.data.length;
this.compare = options.compare || defaultCompare;
this.setNodeId = options.setNodeId || noop;
if (this.length > 0) {
for (var i = (this.length >> 1); i >= 0; i--) this._down(i);
}
if (options.setNodeId) {
for (var i = 0; i < this.length; ++i) {
this.setNodeId(this.data[i], i);
}
}
}
function noop() {}
function defaultCompare(a, b) {
return a - b;
}
NodeHeap.prototype = {
push: function (item) {
this.data.push(item);
this.setNodeId(item, this.length);
this.length++;
this._up(this.length - 1);
},
pop: function () {
if (this.length === 0) return undefined;
var top = this.data[0];
this.length--;
if (this.length > 0) {
this.data[0] = this.data[this.length];
this.setNodeId(this.data[0], 0);
this._down(0);
}
this.data.pop();
return top;
},
peek: function () {
return this.data[0];
},
updateItem: function (pos) {
this._down(pos);
this._up(pos);
},
_up: function (pos) {
var data = this.data;
var compare = this.compare;
var setNodeId = this.setNodeId;
var item = data[pos];
while (pos > 0) {
var parent = (pos - 1) >> 1;
var current = data[parent];
if (compare(item, current) >= 0) break;
data[pos] = current;
setNodeId(current, pos);
pos = parent;
}
data[pos] = item;
setNodeId(item, pos);
},
_down: function (pos) {
var data = this.data;
var compare = this.compare;
var halfLength = this.length >> 1;
var item = data[pos];
var setNodeId = this.setNodeId;
while (pos < halfLength) {
var left = (pos << 1) + 1;
var right = left + 1;
var best = data[left];
if (right < this.length && compare(data[right], best) < 0) {
left = right;
best = data[right];
}
if (compare(best, item) >= 0) break;
data[pos] = best;
setNodeId(best, pos);
pos = left;
}
data[pos] = item;
setNodeId(item, pos);
}
};
},{}],2:[function(require,module,exports){
/**
* Performs suboptimal, greed A Star path finding.
* This finder does not necessary finds the shortest path. The path
* that it finds is very close to the shortest one. It is very fast though.
*/
module.exports = aStarBi;
var NodeHeap = require('./NodeHeap');
var makeSearchStatePool = require('./makeSearchStatePool');
var heuristics = require('./heuristics');
var defaultSettings = require('./defaultSettings');
var BY_FROM = 1;
var BY_TO = 2;
var NO_PATH = defaultSettings.NO_PATH;
module.exports.l2 = heuristics.l2;
module.exports.l1 = heuristics.l1;
/**
* Creates a new instance of pathfinder. A pathfinder has just one method:
* `find(fromId, toId)`, it may be extended in future.
*
* NOTE: Algorithm implemented in this code DOES NOT find optimal path.
* Yet the path that it finds is always near optimal, and it finds it very fast.
*
* @param {ngraph.graph} graph instance. See https://github.com/anvaka/ngraph.graph
*
* @param {Object} options that configures search
* @param {Function(a, b)} options.heuristic - a function that returns estimated distance between
* nodes `a` and `b`. Defaults function returns 0, which makes this search equivalent to Dijkstra search.
* @param {Function(a, b)} options.distance - a function that returns actual distance between two
* nodes `a` and `b`. By default this is set to return graph-theoretical distance (always 1);
* @param {Boolean} options.oriented - whether graph should be considered oriented or not.
*
* @returns {Object} A pathfinder with single method `find()`.
*/
function aStarBi(graph, options) {
options = options || {};
// whether traversal should be considered over oriented graph.
var oriented = options.oriented;
var heuristic = options.heuristic;
if (!heuristic) heuristic = defaultSettings.heuristic;
var distance = options.distance;
if (!distance) distance = defaultSettings.distance;
var pool = makeSearchStatePool();
return {
find: find
};
function find(fromId, toId) {
// Not sure if we should return NO_PATH or throw. Throw seem to be more
// helpful to debug errors. So, throwing.
var from = graph.getNode(fromId);
if (!from) throw new Error('fromId is not defined in this graph: ' + fromId);
var to = graph.getNode(toId);
if (!to) throw new Error('toId is not defined in this graph: ' + toId);
if (from === to) return [from]; // trivial case.
pool.reset();
var callVisitor = oriented ? orientedVisitor : nonOrientedVisitor;
// Maps nodeId to NodeSearchState.
var nodeState = new Map();
var openSetFrom = new NodeHeap({
compare: defaultSettings.compareFScore,
setNodeId: defaultSettings.setHeapIndex
});
var openSetTo = new NodeHeap({
compare: defaultSettings.compareFScore,
setNodeId: defaultSettings.setHeapIndex
});
var startNode = pool.createNewState(from);
nodeState.set(fromId, startNode);
// For the first node, fScore is completely heuristic.
startNode.fScore = heuristic(from, to);
// The cost of going from start to start is zero.
startNode.distanceToSource = 0;
openSetFrom.push(startNode);
startNode.open = BY_FROM;
var endNode = pool.createNewState(to);
endNode.fScore = heuristic(to, from);
endNode.distanceToSource = 0;
openSetTo.push(endNode);
endNode.open = BY_TO;
// Cost of the best solution found so far. Used for accurate termination
var lMin = Number.POSITIVE_INFINITY;
var minFrom;
var minTo;
var currentSet = openSetFrom;
var currentOpener = BY_FROM;
while (openSetFrom.length > 0 && openSetTo.length > 0) {
if (openSetFrom.length < openSetTo.length) {
// we pick a set with less elements
currentOpener = BY_FROM;
currentSet = openSetFrom;
} else {
currentOpener = BY_TO;
currentSet = openSetTo;
}
var current = currentSet.pop();
// no need to visit this node anymore
current.closed = true;
if (current.distanceToSource > lMin) continue;
graph.forEachLinkedNode(current.node.id, callVisitor);
if (minFrom && minTo) {
// This is not necessary the best path, but we are so greedy that we
// can't resist:
return reconstructBiDirectionalPath(minFrom, minTo);
}
}
return NO_PATH; // No path.
function nonOrientedVisitor(otherNode, link) {
return visitNode(otherNode, link, current);
}
function orientedVisitor(otherNode, link) {
// For oritned graphs we need to reverse graph, when traveling
// backwards. So, we use non-oriented ngraph's traversal, and
// filter link orientation here.
if (currentOpener === BY_FROM) {
if (link.fromId === current.node.id) return visitNode(otherNode, link, current)
} else if (currentOpener === BY_TO) {
if (link.toId === current.node.id) return visitNode(otherNode, link, current);
}
}
function canExit(currentNode) {
var opener = currentNode.open
if (opener && opener !== currentOpener) {
return true;
}
return false;
}
function reconstructBiDirectionalPath(a, b) {
var pathOfNodes = [];
var aParent = a;
while(aParent) {
pathOfNodes.push(aParent.node);
aParent = aParent.parent;
}
var bParent = b;
while (bParent) {
pathOfNodes.unshift(bParent.node);
bParent = bParent.parent
}
return pathOfNodes;
}
function visitNode(otherNode, link, cameFrom) {
var otherSearchState = nodeState.get(otherNode.id);
if (!otherSearchState) {
otherSearchState = pool.createNewState(otherNode);
nodeState.set(otherNode.id, otherSearchState);
}
if (otherSearchState.closed) {
// Already processed this node.
return;
}
if (canExit(otherSearchState, cameFrom)) {
// this node was opened by alternative opener. The sets intersect now,
// we found an optimal path, that goes through *this* node. However, there
// is no guarantee that this is the global optimal solution path.
var potentialLMin = otherSearchState.distanceToSource + cameFrom.distanceToSource;
if (potentialLMin < lMin) {
minFrom = otherSearchState;
minTo = cameFrom
lMin = potentialLMin;
}
// we are done with this node.
return;
}
var tentativeDistance = cameFrom.distanceToSource + distance(otherSearchState.node, cameFrom.node, link);
if (tentativeDistance >= otherSearchState.distanceToSource) {
// This would only make our path longer. Ignore this route.
return;
}
// Choose target based on current working set:
var target = (currentOpener === BY_FROM) ? to : from;
var newFScore = tentativeDistance + heuristic(otherSearchState.node, target);
if (newFScore >= lMin) {
// this can't be optimal path, as we have already found a shorter path.
return;
}
otherSearchState.fScore = newFScore;
if (otherSearchState.open === 0) {
// Remember this node in the current set
currentSet.push(otherSearchState);
currentSet.updateItem(otherSearchState.heapIndex);
otherSearchState.open = currentOpener;
}
// bingo! we found shorter path:
otherSearchState.parent = cameFrom;
otherSearchState.distanceToSource = tentativeDistance;
}
}
}
},{"./NodeHeap":1,"./defaultSettings":4,"./heuristics":5,"./makeSearchStatePool":6}],3:[function(require,module,exports){
/**
* Performs a uni-directional A Star search on graph.
*
* We will try to minimize f(n) = g(n) + h(n), where
* g(n) is actual distance from source node to `n`, and
* h(n) is heuristic distance from `n` to target node.
*/
module.exports = aStarPathSearch;
var NodeHeap = require('./NodeHeap');
var makeSearchStatePool = require('./makeSearchStatePool');
var heuristics = require('./heuristics');
var defaultSettings = require('./defaultSettings.js');
var NO_PATH = defaultSettings.NO_PATH;
module.exports.l2 = heuristics.l2;
module.exports.l1 = heuristics.l1;
/**
* Creates a new instance of pathfinder. A pathfinder has just one method:
* `find(fromId, toId)`, it may be extended in future.
*
* @param {ngraph.graph} graph instance. See https://github.com/anvaka/ngraph.graph
* @param {Object} options that configures search
* @param {Function(a, b)} options.heuristic - a function that returns estimated distance between
* nodes `a` and `b`. This function should never overestimate actual distance between two
* nodes (otherwise the found path will not be the shortest). Defaults function returns 0,
* which makes this search equivalent to Dijkstra search.
* @param {Function(a, b)} options.distance - a function that returns actual distance between two
* nodes `a` and `b`. By default this is set to return graph-theoretical distance (always 1);
* @param {Boolean} options.oriented - whether graph should be considered oriented or not.
*
* @returns {Object} A pathfinder with single method `find()`.
*/
function aStarPathSearch(graph, options) {
options = options || {};
// whether traversal should be considered over oriented graph.
var oriented = options.oriented;
var heuristic = options.heuristic;
if (!heuristic) heuristic = defaultSettings.heuristic;
var distance = options.distance;
if (!distance) distance = defaultSettings.distance;
var pool = makeSearchStatePool();
return {
/**
* Finds a path between node `fromId` and `toId`.
* @returns {Array} of nodes between `toId` and `fromId`. Empty array is returned
* if no path is found.
*/
find: find
};
function find(fromId, toId) {
var from = graph.getNode(fromId);
if (!from) throw new Error('fromId is not defined in this graph: ' + fromId);
var to = graph.getNode(toId);
if (!to) throw new Error('toId is not defined in this graph: ' + toId);
pool.reset();
// Maps nodeId to NodeSearchState.
var nodeState = new Map();
// the nodes that we still need to evaluate
var openSet = new NodeHeap({
compare: defaultSettings.compareFScore,
setNodeId: defaultSettings.setHeapIndex
});
var startNode = pool.createNewState(from);
nodeState.set(fromId, startNode);
// For the first node, fScore is completely heuristic.
startNode.fScore = heuristic(from, to);
// The cost of going from start to start is zero.
startNode.distanceToSource = 0;
openSet.push(startNode);
startNode.open = 1;
var cameFrom;
while (openSet.length > 0) {
cameFrom = openSet.pop();
if (goalReached(cameFrom, to)) return reconstructPath(cameFrom);
// no need to visit this node anymore
cameFrom.closed = true;
graph.forEachLinkedNode(cameFrom.node.id, visitNeighbour, oriented);
}
// If we got here, then there is no path.
return NO_PATH;
function visitNeighbour(otherNode, link) {
var otherSearchState = nodeState.get(otherNode.id);
if (!otherSearchState) {
otherSearchState = pool.createNewState(otherNode);
nodeState.set(otherNode.id, otherSearchState);
}
if (otherSearchState.closed) {
// Already processed this node.
return;
}
if (otherSearchState.open === 0) {
// Remember this node.
openSet.push(otherSearchState);
otherSearchState.open = 1;
}
var tentativeDistance = cameFrom.distanceToSource + distance(otherNode, cameFrom.node, link);
if (tentativeDistance >= otherSearchState.distanceToSource) {
// This would only make our path longer. Ignore this route.
return;
}
// bingo! we found shorter path:
otherSearchState.parent = cameFrom;
otherSearchState.distanceToSource = tentativeDistance;
otherSearchState.fScore = tentativeDistance + heuristic(otherSearchState.node, to);
openSet.updateItem(otherSearchState.heapIndex);
}
}
}
function goalReached(searchState, targetNode) {
return searchState.node === targetNode;
}
function reconstructPath(searchState) {
var path = [searchState.node];
var parent = searchState.parent;
while (parent) {
path.push(parent.node);
parent = parent.parent;
}
return path;
}
},{"./NodeHeap":1,"./defaultSettings.js":4,"./heuristics":5,"./makeSearchStatePool":6}],4:[function(require,module,exports){
// We reuse instance of array, but we trie to freeze it as well,
// so that consumers don't modify it. Maybe it's a bad idea.
var NO_PATH = [];
if (typeof Object.freeze === 'function') Object.freeze(NO_PATH);
module.exports = {
// Path search settings
heuristic: blindHeuristic,
distance: constantDistance,
compareFScore: compareFScore,
NO_PATH: NO_PATH,
// heap settings
setHeapIndex: setHeapIndex,
// nba:
setH1: setH1,
setH2: setH2,
compareF1Score: compareF1Score,
compareF2Score: compareF2Score,
}
function blindHeuristic(/* a, b */) {
// blind heuristic makes this search equal to plain Dijkstra path search.
return 0;
}
function constantDistance(/* a, b */) {
return 1;
}
function compareFScore(a, b) {
var result = a.fScore - b.fScore;
// TODO: Can I improve speed with smarter ties-breaking?
// I tried distanceToSource, but it didn't seem to have much effect
return result;
}
function setHeapIndex(nodeSearchState, heapIndex) {
nodeSearchState.heapIndex = heapIndex;
}
function compareF1Score(a, b) {
return a.f1 - b.f1;
}
function compareF2Score(a, b) {
return a.f2 - b.f2;
}
function setH1(node, heapIndex) {
node.h1 = heapIndex;
}
function setH2(node, heapIndex) {
node.h2 = heapIndex;
}
},{}],5:[function(require,module,exports){
module.exports = {
l2: l2,
l1: l1
};
/**
* Euclid distance (l2 norm);
*
* @param {*} a
* @param {*} b
*/
function l2(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Manhattan distance (l1 norm);
* @param {*} a
* @param {*} b
*/
function l1(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.abs(dx) + Math.abs(dy);
}
},{}],6:[function(require,module,exports){
/**
* This class represents a single search node in the exploration tree for
* A* algorithm.
*
* @param {Object} node original node in the graph
*/
function NodeSearchState(node) {
this.node = node;
// How we came to this node?
this.parent = null;
this.closed = false;
this.open = 0;
this.distanceToSource = Number.POSITIVE_INFINITY;
// the f(n) = g(n) + h(n) value
this.fScore = Number.POSITIVE_INFINITY;
// used to reconstruct heap when fScore is updated.
this.heapIndex = -1;
};
function makeSearchStatePool() {
var currentInCache = 0;
var nodeCache = [];
return {
createNewState: createNewState,
reset: reset
};
function reset() {
currentInCache = 0;
}
function createNewState(node) {
var cached = nodeCache[currentInCache];
if (cached) {
// TODO: This almost duplicates constructor code. Not sure if
// it would impact performance if I move this code into a function
cached.node = node;
// How we came to this node?
cached.parent = null;
cached.closed = false;
cached.open = 0;
cached.distanceToSource = Number.POSITIVE_INFINITY;
// the f(n) = g(n) + h(n) value
cached.fScore = Number.POSITIVE_INFINITY;
// used to reconstruct heap when fScore is updated.
cached.heapIndex = -1;
} else {
cached = new NodeSearchState(node);
nodeCache[currentInCache] = cached;
}
currentInCache++;
return cached;
}
}
module.exports = makeSearchStatePool;
},{}],7:[function(require,module,exports){
module.exports = nba;
var NodeHeap = require('../NodeHeap');
var heuristics = require('../heuristics');
var defaultSettings = require('../defaultSettings.js');
var makeNBASearchStatePool = require('./makeNBASearchStatePool.js');
var NO_PATH = defaultSettings.NO_PATH;
module.exports.l2 = heuristics.l2;
module.exports.l1 = heuristics.l1;
/**
* Creates a new instance of pathfinder. A pathfinder has just one method:
* `find(fromId, toId)`.
*
* This is implementation of the NBA* algorithm described in
*
* "Yet another bidirectional algorithm for shortest paths" paper by Wim Pijls and Henk Post
*
* The paper is available here: https://repub.eur.nl/pub/16100/ei2009-10.pdf
*
* @param {ngraph.graph} graph instance. See https://github.com/anvaka/ngraph.graph
* @param {Object} options that configures search
* @param {Function(a, b)} options.heuristic - a function that returns estimated distance between
* nodes `a` and `b`. This function should never overestimate actual distance between two
* nodes (otherwise the found path will not be the shortest). Defaults function returns 0,
* which makes this search equivalent to Dijkstra search.
* @param {Function(a, b)} options.distance - a function that returns actual distance between two
* nodes `a` and `b`. By default this is set to return graph-theoretical distance (always 1);
*
* @returns {Object} A pathfinder with single method `find()`.
*/
function nba(graph, options) {
options = options || {};
// whether traversal should be considered over oriented graph.
var oriented = options.oriented;
var quitFast = options.quitFast;
var heuristic = options.heuristic;
if (!heuristic) heuristic = defaultSettings.heuristic;
var distance = options.distance;
if (!distance) distance = defaultSettings.distance;
// During stress tests I noticed that garbage collection was one of the heaviest
// contributors to the algorithm's speed. So I'm using an object pool to recycle nodes.
var pool = makeNBASearchStatePool();
return {
/**
* Finds a path between node `fromId` and `toId`.
* @returns {Array} of nodes between `toId` and `fromId`. Empty array is returned
* if no path is found.
*/
find: find
};
function find(fromId, toId) {
// I must apologize for the code duplication. This was the easiest way for me to
// implement the algorithm fast.
var from = graph.getNode(fromId);
if (!from) throw new Error('fromId is not defined in this graph: ' + fromId);
var to = graph.getNode(toId);
if (!to) throw new Error('toId is not defined in this graph: ' + toId);
pool.reset();
// I must also apologize for somewhat cryptic names. The NBA* is bi-directional
// search algorithm, which means it runs two searches in parallel. One is called
// forward search and it runs from source node to target, while the other one
// (backward search) runs from target to source.
// Everywhere where you see `1` it means it's for the forward search. `2` is for
// backward search.
// For oriented graph path finding, we need to reverse the graph, so that
// backward search visits correct link. Obviously we don't want to duplicate
// the graph, instead we always traverse the graph as non-oriented, and filter
// edges in `visitN1Oriented/visitN2Oritented`
var forwardVisitor = oriented ? visitN1Oriented : visitN1;
var reverseVisitor = oriented ? visitN2Oriented : visitN2;
// Maps nodeId to NBASearchState.
var nodeState = new Map();
// These two heaps store nodes by their underestimated values.
var open1Set = new NodeHeap({
compare: defaultSettings.compareF1Score,
setNodeId: defaultSettings.setH1
});
var open2Set = new NodeHeap({
compare: defaultSettings.compareF2Score,
setNodeId: defaultSettings.setH2
});
// This is where both searches will meet.
var minNode;
// The smallest path length seen so far is stored here:
var lMin = Number.POSITIVE_INFINITY;
// We start by putting start/end nodes to the corresponding heaps
// If variable names like `f1`, `g1` are too confusing, please refer
// to makeNBASearchStatePool.js file, which has detailed description.
var startNode = pool.createNewState(from);
nodeState.set(fromId, startNode);
startNode.g1 = 0;
var f1 = heuristic(from, to);
startNode.f1 = f1;
open1Set.push(startNode);
var endNode = pool.createNewState(to);
nodeState.set(toId, endNode);
endNode.g2 = 0;
var f2 = f1; // they should agree originally
endNode.f2 = f2;
open2Set.push(endNode)
// the `cameFrom` variable is accessed by both searches, so that we can store parents.
var cameFrom;
// this is the main algorithm loop:
while (open2Set.length && open1Set.length) {
if (open1Set.length < open2Set.length) {
forwardSearch();
} else {
reverseSearch();
}
if (quitFast && minNode) break;
}
var path = reconstructPath(minNode);
return path; // the public API is over
function forwardSearch() {
cameFrom = open1Set.pop();
if (cameFrom.closed) {
return;
}
cameFrom.closed = true;
if (cameFrom.f1 < lMin && (cameFrom.g1 + f2 - heuristic(from, cameFrom.node)) < lMin) {
graph.forEachLinkedNode(cameFrom.node.id, forwardVisitor);
}
if (open1Set.length > 0) {
// this will be used in reverse search
f1 = open1Set.peek().f1;
}
}
function reverseSearch() {
cameFrom = open2Set.pop();
if (cameFrom.closed) {
return;
}
cameFrom.closed = true;
if (cameFrom.f2 < lMin && (cameFrom.g2 + f1 - heuristic(cameFrom.node, to)) < lMin) {
graph.forEachLinkedNode(cameFrom.node.id, reverseVisitor);
}
if (open2Set.length > 0) {
// this will be used in forward search
f2 = open2Set.peek().f2;
}
}
function visitN1(otherNode, link) {
var otherSearchState = nodeState.get(otherNode.id);
if (!otherSearchState) {
otherSearchState = pool.createNewState(otherNode);
nodeState.set(otherNode.id, otherSearchState);
}
if (otherSearchState.closed) return;
var tentativeDistance = cameFrom.g1 + distance(cameFrom.node, otherNode, link);
if (tentativeDistance < otherSearchState.g1) {
otherSearchState.g1 = tentativeDistance;
otherSearchState.f1 = tentativeDistance + heuristic(otherSearchState.node, to);
otherSearchState.p1 = cameFrom;
if (otherSearchState.h1 < 0) {
open1Set.push(otherSearchState);
} else {
open1Set.updateItem(otherSearchState.h1);
}
}
var potentialMin = otherSearchState.g1 + otherSearchState.g2;
if (potentialMin < lMin) {
lMin = potentialMin;
minNode = otherSearchState;
}
}
function visitN2(otherNode, link) {
var otherSearchState = nodeState.get(otherNode.id);
if (!otherSearchState) {
otherSearchState = pool.createNewState(otherNode);
nodeState.set(otherNode.id, otherSearchState);
}
if (otherSearchState.closed) return;
var tentativeDistance = cameFrom.g2 + distance(cameFrom.node, otherNode, link);
if (tentativeDistance < otherSearchState.g2) {
otherSearchState.g2 = tentativeDistance;
otherSearchState.f2 = tentativeDistance + heuristic(from, otherSearchState.node);
otherSearchState.p2 = cameFrom;
if (otherSearchState.h2 < 0) {
open2Set.push(otherSearchState);
} else {
open2Set.updateItem(otherSearchState.h2);
}
}
var potentialMin = otherSearchState.g1 + otherSearchState.g2;
if (potentialMin < lMin) {
lMin = potentialMin;
minNode = otherSearchState;
}
}
function visitN2Oriented(otherNode, link) {
// we are going backwards, graph needs to be reversed.
if (link.toId === cameFrom.node.id) return visitN2(otherNode, link);
}
function visitN1Oriented(otherNode, link) {
// this is forward direction, so we should be coming FROM:
if (link.fromId === cameFrom.node.id) return visitN1(otherNode, link);
}
}
}
function reconstructPath(searchState) {
if (!searchState) return NO_PATH;
var path = [searchState.node];
var parent = searchState.p1;
while (parent) {
path.push(parent.node);
parent = parent.p1;
}
var child = searchState.p2;
while (child) {
path.unshift(child.node);
child = child.p2;
}
return path;
}
},{"../NodeHeap":1,"../defaultSettings.js":4,"../heuristics":5,"./makeNBASearchStatePool.js":8}],8:[function(require,module,exports){
module.exports = makeNBASearchStatePool;
/**
* Creates new instance of NBASearchState. The instance stores information
* about search state, and is used by NBA* algorithm.
*
* @param {Object} node - original graph node
*/
function NBASearchState(node) {
/**
* Original graph node.
*/
this.node = node;
/**
* Parent of this node in forward search
*/
this.p1 = null;
/**
* Parent of this node in reverse search
*/
this.p2 = null;
/**
* If this is set to true, then the node was already processed
* and we should not touch it anymore.
*/
this.closed = false;
/**
* Actual distance from this node to its parent in forward search
*/
this.g1 = Number.POSITIVE_INFINITY;
/**
* Actual distance from this node to its parent in reverse search
*/
this.g2 = Number.POSITIVE_INFINITY;
/**
* Underestimated distance from this node to the path-finding source.
*/
this.f1 = Number.POSITIVE_INFINITY;
/**
* Underestimated distance from this node to the path-finding target.
*/
this.f2 = Number.POSITIVE_INFINITY;
// used to reconstruct heap when fScore is updated. TODO: do I need them both?
/**
* Index of this node in the forward heap.
*/
this.h1 = -1;
/**
* Index of this node in the reverse heap.
*/
this.h2 = -1;
}
/**
* As path-finding is memory-intensive process, we want to reduce pressure on
* garbage collector. This class helps us to recycle path-finding nodes and significantly
* reduces the search time (~20% faster than without it).
*/
function makeNBASearchStatePool() {
var currentInCache = 0;
var nodeCache = [];
return {
/**
* Creates a new NBASearchState instance
*/
createNewState: createNewState,
/**
* Marks all created instances available for recycling.
*/
reset: reset
};
function reset() {
currentInCache = 0;
}
function createNewState(node) {
var cached = nodeCache[currentInCache];
if (cached) {
// TODO: This almost duplicates constructor code. Not sure if
// it would impact performance if I move this code into a function
cached.node = node;
// How we came to this node?
cached.p1 = null;
cached.p2 = null;
cached.closed = false;
cached.g1 = Number.POSITIVE_INFINITY;
cached.g2 = Number.POSITIVE_INFINITY;
cached.f1 = Number.POSITIVE_INFINITY;
cached.f2 = Number.POSITIVE_INFINITY;
// used to reconstruct heap when fScore is updated.
cached.h1 = -1;
cached.h2 = -1;
} else {
cached = new NBASearchState(node);
nodeCache[currentInCache] = cached;
}
currentInCache++;
return cached;
}
}
},{}],9:[function(require,module,exports){
module.exports = {
aStar: require('./a-star/a-star.js'),
aGreedy: require('./a-star/a-greedy-star'),
nba: require('./a-star/nba/index.js'),
}
},{"./a-star/a-greedy-star":2,"./a-star/a-star.js":3,"./a-star/nba/index.js":7}]},{},[9])(9)
});