docs: 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);
      }
    }
  }