From 889a5d28a53bd8fa170cd660b59ddbabc173e7ed Mon Sep 17 00:00:00 2001 From: Andriy Kashcha Date: Mon, 18 Sep 2017 01:35:16 -0700 Subject: [PATCH] Added proper oriented search --- a-star/a-greedy-star.js | 24 +++++++++++--- a-star/nba/index.js | 16 ++++++++-- test/a-star.js | 69 ++++++++++++++++++++++++++++++++++++++--- test/nba.js | 54 ++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 test/nba.js diff --git a/a-star/a-greedy-star.js b/a-star/a-greedy-star.js index c6b0044..3d86d7c 100644 --- a/a-star/a-greedy-star.js +++ b/a-star/a-greedy-star.js @@ -61,6 +61,8 @@ function aStarBi(graph, options) { pool.reset(); + var callVisitor = oriented ? orientedVisitor : nonOrientedVisitor; + // Maps nodeId to NodeSearchState. var nodeState = new Map(); @@ -116,19 +118,32 @@ function aStarBi(graph, options) { if (current.distanceToSource > lMin) continue; - graph.forEachLinkedNode(current.node.id, callVisitor, oriented); + 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 callVisitor(otherNode, link) { + 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) { @@ -142,12 +157,12 @@ function aStarBi(graph, options) { var pathOfNodes = []; var aParent = a; while(aParent) { - pathOfNodes.unshift(aParent.node); + pathOfNodes.push(aParent.node); aParent = aParent.parent; } var bParent = b; while (bParent) { - pathOfNodes.push(bParent.node); + pathOfNodes.unshift(bParent.node); bParent = bParent.parent } return pathOfNodes; @@ -180,7 +195,6 @@ function aStarBi(graph, options) { return; } - var tentativeDistance = cameFrom.distanceToSource + distance(otherSearchState.node, cameFrom.node, link); if (tentativeDistance >= otherSearchState.distanceToSource) { diff --git a/a-star/nba/index.js b/a-star/nba/index.js index 7c4fee8..4c7c5a5 100644 --- a/a-star/nba/index.js +++ b/a-star/nba/index.js @@ -38,6 +38,9 @@ function nba(graph, options) { pool.reset(); + var forwardVisitor = oriented ? visitN1Oriented : visitN1; + var reverseVisitor = oriented ? visitN2Oriented : visitN2; + // Maps nodeId to NBASearchState. var nodeState = new Map(); @@ -90,7 +93,7 @@ function nba(graph, options) { cameFrom.closed = true; if (cameFrom.f1 < lMin && (cameFrom.g1 + f2 - heuristic(from, cameFrom.node)) < lMin) { - graph.forEachLinkedNode(cameFrom.node.id, visitN1, options.oriented) // todo - needs to reverse correctly + graph.forEachLinkedNode(cameFrom.node.id, forwardVisitor); } if (open1Set.length > 0) { @@ -106,7 +109,7 @@ function nba(graph, options) { cameFrom.closed = true; if (cameFrom.f2 < lMin && (cameFrom.g2 + f1 - heuristic(cameFrom.node, to)) < lMin) { - graph.forEachLinkedNode(cameFrom.node.id, visitN2, options.oriented) // todo - needs to reverse correctly + graph.forEachLinkedNode(cameFrom.node.id, reverseVisitor); } if (open2Set.length > 0) { @@ -169,6 +172,15 @@ function nba(graph, options) { 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); + } } } diff --git a/test/a-star.js b/test/a-star.js index 7eab84b..6df89b5 100644 --- a/test/a-star.js +++ b/test/a-star.js @@ -6,7 +6,6 @@ var fromDot = require('ngraph.fromdot'); var asciiUtils = require('./utils/graphFromAscii'); test('it can find weighted', t => { - let createGraph = require('ngraph.graph'); let graph = createGraph(); graph.addLink('a', 'b', {weight: 10}); @@ -28,6 +27,64 @@ test('it can find weighted', t => { t.end(); }); +test('A* can find directed path', t => { + let graph = createGraph(); + + // We want to find a path from a to e. + // a -> b <- e + // \ / + // c -> d + // In undirected graph the `a, b, e` will be the solution. + // In directed graph it sohuld be `a c d e` + graph.addLink('a', 'b'); + graph.addLink('e', 'b'); + + graph.addLink('a', 'c'); + graph.addLink('c', 'd'); + graph.addLink('d', 'e'); + + + var pathFinder = aStar(graph, { + oriented: true + }); + let path = pathFinder.find('a', 'e'); + + t.equals(path[0].id, 'e', 'e is here'); + t.equals(path[1].id, 'd', 'd is here'); + t.equals(path[2].id, 'c', 'c is here'); + t.equals(path[3].id, 'a', 'a is here'); + t.end(); +}); + +test('A* greedy can find directed path', t => { + let graph = createGraph(); + + // We want to find a path from a to e. + // a -> b <- e + // \ / + // c -> d + // In undirected graph the `a, b, e` will be the solution. + // In directed graph it sohuld be `a c d e` + graph.addLink('a', 'b'); + graph.addLink('e', 'b'); + + graph.addLink('a', 'c'); + graph.addLink('c', 'd'); + graph.addLink('d', 'e'); + + var pathFinder = aGreedy(graph, { + oriented: true + }); + let path = pathFinder.find('a', 'e'); + + t.equals(path[0].id, 'e', 'e is here'); + t.equals(path[1].id, 'd', 'd is here'); + t.equals(path[2].id, 'c', 'c is here'); + t.equals(path[3].id, 'a', 'a is here'); + t.end(); +}); + + test('it can use heuristic', t => { let createGraph = require('ngraph.graph'); let graph = createGraph(); @@ -85,10 +142,12 @@ test('it can find path without any config', t => { t.equals(path[2].id, 'a', 'a is here'); var pathFinderBi = aGreedy(graph); - var pathBi = pathFinderBi.find('a', 'c'); - t.equals(pathBi[0].id, 'c', 'c is here'); - t.equals(pathBi[1].id, 'b', 'b is here'); - t.equals(pathBi[2].id, 'a', 'a is here'); + let foundNodes = new Set(); + pathFinderBi.find('a', 'c').forEach(n => foundNodes.add(n.id)); + + t.ok(foundNodes.has('c'), 'c is here'); + t.ok(foundNodes.has('b'), 'b is here'); + t.ok(foundNodes.has('a'), 'a is here'); t.end(); }) diff --git a/test/nba.js b/test/nba.js new file mode 100644 index 0000000..406a6c4 --- /dev/null +++ b/test/nba.js @@ -0,0 +1,54 @@ +var test = require('tap').test; +var nba = require('../').nba; +var createGraph = require('ngraph.graph'); + +test('it can find path', t => { + let graph = createGraph(); + + graph.addLink('a', 'b', {weight: 10}); + graph.addLink('a', 'c', {weight: 10}); + graph.addLink('c', 'd', {weight: 5}); + graph.addLink('b', 'd', {weight: 10}); + + + var pathFinder = nba(graph, { + distance(a, b, link) { + return link.data.weight; + } + }); + let path = pathFinder.find('a', 'd'); + + t.equals(path[0].id, 'd', 'd is here'); + t.equals(path[1].id, 'c', 'c is here'); + t.equals(path[2].id, 'a', 'a is here'); + t.end(); +}); + +test('it can find directed path', t => { + let graph = createGraph(); + + // We want to find a path from a to e. + // a -> b <- e + // \ / + // c -> d + // In undirected graph the `a, b, e` will be the solution. + // In directed graph it sohuld be `a c d e` + graph.addLink('a', 'b'); + graph.addLink('e', 'b'); + + graph.addLink('a', 'c'); + graph.addLink('c', 'd'); + graph.addLink('d', 'e'); + + + var pathFinder = nba(graph, { + oriented: true + }); + let path = pathFinder.find('a', 'e'); + + t.equals(path[0].id, 'e', 'e is here'); + t.equals(path[1].id, 'd', 'd is here'); + t.equals(path[2].id, 'c', 'c is here'); + t.equals(path[3].id, 'a', 'a is here'); + t.end(); +}); \ No newline at end of file