All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.graphalgo.impl.shortestpath.Dijkstra Maven / Gradle / Ivy

There is a newer version: 5.26.1
Show newest version
/*
 * Copyright (c) 2002-2016 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.graphalgo.impl.shortestpath;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.neo4j.graphalgo.CostAccumulator;
import org.neo4j.graphalgo.CostEvaluator;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;

/**
 * Dijkstra class. This class can be used to perform shortest path computations
 * between two nodes. The search is made simultaneously from both the start node
 * and the end node. Note that per default, only one shortest path will be
 * searched for. This will be done when the path or the cost is asked for. If at
 * some later time getPaths is called to get all the paths, the calculation is
 * redone. In order to avoid this double computation when all paths are desired,
 * be sure to call getPaths (or calculateMultiple) before any call to getPath or
 * getCost (or calculate) is made.
 *
 * @complexity The {@link CostEvaluator}, the {@link CostAccumulator} and the
 *             cost comparator will all be called once for every relationship
 *             traversed. Assuming they run in constant time, the time
 *             complexity for this algorithm is O(m + n * log(n)).
 * @author Patrik Larsson
 * @param  The datatype the edge weights will be represented by.
 */
public class Dijkstra implements
        SingleSourceSingleSinkShortestPath
{
    protected CostType startCost; // starting cost for both the start node and
    // the end node
    protected Node startNode, endNode;
    protected RelationshipType[] costRelationTypes;
    protected Direction relationDirection;
    protected CostEvaluator costEvaluator = null;
    protected CostAccumulator costAccumulator = null;
    protected Comparator costComparator = null;
    protected boolean calculateAllShortestPaths = false;
    // Limits
    protected long maxRelationShipsToTraverse = -1;
    protected long numberOfTraversedRelationShips = 0;
    protected long maxNodesToTraverse = -1;
    protected long numberOfNodesTraversed = 0;
    protected CostType maxCost = null;

    /**
     * @return True if the set limits for the calculation has been reached (but
     *         not exceeded)
     */
    protected boolean limitReached()
    {
        if ( maxRelationShipsToTraverse >= 0
             && numberOfTraversedRelationShips >= maxRelationShipsToTraverse )
        {
            return true;
        }
        if ( maxNodesToTraverse >= 0
             && numberOfNodesTraversed >= maxNodesToTraverse )
        {
            return true;
        }
        return false;
    }

    protected boolean limitReached( CostType cost1, CostType cost2 )
    {
        if ( maxCost != null )
        {
            CostType totalCost = costAccumulator.addCosts( cost1, cost2 );
            if ( costComparator.compare( totalCost, maxCost ) > 0 )
            {
                foundPathsMiddleNodes = null;
                foundPathsCost = null;
                return true;
            }
        }

        return false;
    }

    // Result data
    protected boolean doneCalculation = false;
    protected Set foundPathsMiddleNodes = null;
    protected CostType foundPathsCost;
    protected HashMap> predecessors1 = new HashMap>();
    protected HashMap> predecessors2 = new HashMap>();

    /**
     * Resets the result data to force the computation to be run again when some
     * result is asked for.
     */
    public void reset()
    {
        doneCalculation = false;
        foundPathsMiddleNodes = null;
        predecessors1 = new HashMap>();
        predecessors2 = new HashMap>();
        // Limits
        numberOfTraversedRelationShips = 0;
        numberOfNodesTraversed = 0;
    }

    /**
     * @param startCost Starting cost for both the start node and the end node
     * @param startNode the start node
     * @param endNode the end node
     * @param costRelationTypes the relationship that should be included in the
     *            path
     * @param relationDirection relationship direction to follow
     * @param costEvaluator the cost function per relationship
     * @param costAccumulator adding up the path cost
     * @param costComparator comparing to path costs
     */
    public Dijkstra( CostType startCost, Node startNode, Node endNode,
            CostEvaluator costEvaluator,
            CostAccumulator costAccumulator,
            Comparator costComparator, Direction relationDirection,
            RelationshipType... costRelationTypes )
    {
        super();
        this.startCost = startCost;
        this.startNode = startNode;
        this.endNode = endNode;
        this.costRelationTypes = costRelationTypes;
        this.relationDirection = relationDirection;
        this.costEvaluator = costEvaluator;
        this.costAccumulator = costAccumulator;
        this.costComparator = costComparator;
    }

    /**
     * A DijkstraIterator computes the distances to nodes from a specified
     * starting node, one at a time, following the dijkstra algorithm.
     *
     * @author Patrik Larsson
     */
    protected class DijstraIterator implements Iterator
    {
        protected Node startNode;
        // where do we come from
        protected HashMap> predecessors;
        // observed distances not yet final
        protected HashMap mySeen;
        protected HashMap otherSeen;
        // the final distances
        protected HashMap myDistances;
        protected HashMap otherDistances;
        // Flag that indicates if we should follow egdes in the opposite
        // direction instead
        protected boolean backwards = false;
        // The priority queue
        protected DijkstraPriorityQueue queue;
        // "Done" flags. The first is set to true when a node is found that is
        // contained in both myDistances and otherDistances. This means the
        // calculation has found one of the shortest paths.
        protected boolean oneShortestPathHasBeenFound = false;
        protected boolean allShortestPathsHasBeenFound = false;

        public DijstraIterator( Node startNode,
                HashMap> predecessors,
                HashMap mySeen,
                HashMap otherSeen,
                HashMap myDistances,
                HashMap otherDistances, boolean backwards )
        {
            super();
            this.startNode = startNode;
            this.predecessors = predecessors;
            this.mySeen = mySeen;
            this.otherSeen = otherSeen;
            this.myDistances = myDistances;
            this.otherDistances = otherDistances;
            this.backwards = backwards;
            InitQueue();
        }

        /**
         * @return The direction to use when searching for relations/edges
         */
        protected Direction getDirection()
        {
            if ( backwards )
            {
                if ( relationDirection.equals( Direction.INCOMING ) )
                {
                    return Direction.OUTGOING;
                }
                if ( relationDirection.equals( Direction.OUTGOING ) )
                {
                    return Direction.INCOMING;
                }
            }
            return relationDirection;
        }

        // This puts the start node into the queue
        protected void InitQueue()
        {
            queue = new DijkstraPriorityQueueFibonacciImpl(
                    costComparator );
            queue.insertValue( startNode, startCost );
            mySeen.put( startNode, startCost );
        }

        public boolean hasNext()
        {
            return !queue.isEmpty() && !limitReached();
        }

        public void remove()
        {
            // Not used
            // Could be used to generate more sollutions, by removing an edge
            // from the sollution and run again?
        }

        /**
         * This checks if a node has been seen by the other iterator/traverser
         * as well. In that case a path has been found. In that case, the total
         * cost for the path is calculated and compared to previously found
         * paths.
         *
         * @param currentNode The node to be examined.
         * @param currentCost The cost from the start node to this node.
         * @param otherSideDistances Map over distances from other side. A path
         *            is found and examined if this contains currentNode.
         */
        protected void checkForPath( Node currentNode, CostType currentCost,
                HashMap otherSideDistances )
        {
            // Found a path?
            if ( otherSideDistances.containsKey( currentNode ) )
            {
                // Is it better than previously found paths?
                CostType otherCost = otherSideDistances.get( currentNode );
                CostType newTotalCost = costAccumulator.addCosts( currentCost,
                        otherCost );
                if ( foundPathsMiddleNodes == null )
                {
                    foundPathsMiddleNodes = new HashSet();
                }
                // No previous path found, or equally good one found?
                if ( foundPathsMiddleNodes.size() == 0
                     || costComparator.compare( foundPathsCost, newTotalCost ) == 0 )
                {
                    foundPathsCost = newTotalCost; // in case we had no
                    // previous path
                    foundPathsMiddleNodes.add( currentNode );
                }
                // New better path found?
                else if ( costComparator.compare( foundPathsCost, newTotalCost ) > 0 )
                {
                    foundPathsMiddleNodes.clear();
                    foundPathsCost = newTotalCost;
                    foundPathsMiddleNodes.add( currentNode );
                }
            }
        }

        public Node next()
        {
            Node currentNode = queue.extractMin();
            CostType currentCost = mySeen.get( currentNode );
            // Already done with this node?
            if ( myDistances.containsKey( currentNode ) )
            {
                return null;
            }
            if ( limitReached() )
            {
                return null;
            }
            ++numberOfNodesTraversed;
            myDistances.put( currentNode, currentCost );
            // TODO: remove from seen or not? probably not... because of path
            // detection
            // Check if we have found a better path
            checkForPath( currentNode, currentCost, otherSeen );
            // Found a path? (abort traversing from this node)
            if ( otherDistances.containsKey( currentNode ) )
            {
                oneShortestPathHasBeenFound = true;
            }
            else
            {
                // Otherwise, follow all edges from this node
                for ( RelationshipType costRelationType : costRelationTypes )
                {
                    for ( Relationship relationship : currentNode.getRelationships(
                            costRelationType, getDirection() ) )
                    {
                        if ( limitReached() )
                        {
                            break;
                        }
                        ++numberOfTraversedRelationShips;
                        // Target node
                        Node target = relationship.getOtherNode( currentNode );
                        // Find out if an eventual path would go in the opposite
                        // direction of the edge
                        boolean backwardsEdge = relationship.getEndNode().equals(
                                currentNode )
                                                ^ backwards;
                        CostType newCost = costAccumulator.addCosts(
                                currentCost, costEvaluator.getCost(
                                        relationship,
                                        backwardsEdge ? Direction.INCOMING
                                                : Direction.OUTGOING ) );
                        // Already done with target node?
                        if ( myDistances.containsKey( target ) )
                        {
                            // Have we found a better cost for a node which is
                            // already
                            // calculated?
                            if ( costComparator.compare(
                                    myDistances.get( target ), newCost ) > 0 )
                            {
                                throw new RuntimeException(
                                        "Cycle with negative costs found." );
                            }
                            // Equally good path found?
                            else if ( calculateAllShortestPaths
                                      && costComparator.compare(
                                              myDistances.get( target ),
                                              newCost ) == 0 )
                            {
                                // Put it in predecessors
                                List myPredecessors = predecessors.get( currentNode );
                                // Dont do it if this relation is already in
                                // predecessors (other direction)
                                if ( myPredecessors == null
                                     || !myPredecessors.contains( relationship ) )
                                {
                                    List predList = predecessors.get( target );
                                    if ( predList == null )
                                    {
                                        // This only happens if we get back to
                                        // the
                                        // start node, which is just bogus
                                    }
                                    else
                                    {
                                        predList.add( relationship );
                                    }
                                }
                            }
                            continue;
                        }
                        // Have we found a better cost for this node?
                        if ( !mySeen.containsKey( target )
                             || costComparator.compare( mySeen.get( target ),
                                     newCost ) > 0 )
                        {
                            // Put it in the queue
                            if ( !mySeen.containsKey( target ) )
                            {
                                queue.insertValue( target, newCost );
                            }
                            // or update the entry. (It is important to keep
                            // these
                            // cases apart to limit the size of the queue)
                            else
                            {
                                queue.decreaseValue( target, newCost );
                            }
                            // Update it
                            mySeen.put( target, newCost );
                            // Put it in predecessors
                            List predList = new LinkedList();
                            predList.add( relationship );
                            predecessors.put( target, predList );
                        }
                        // Have we found an equal cost for (additonal path to)
                        // this
                        // node?
                        else if ( calculateAllShortestPaths
                                  && costComparator.compare(
                                          mySeen.get( target ), newCost ) == 0 )
                        {
                            // Put it in predecessors
                            List predList = predecessors.get( target );
                            predList.add( relationship );
                        }
                    }
                }
            }
            // Check how far we need to continue when searching for all shortest
            // paths
            if ( calculateAllShortestPaths && oneShortestPathHasBeenFound )
            {
                // If we cannot continue or continuation would only find more
                // expensive paths: conclude that all shortest paths have been
                // found.
                allShortestPathsHasBeenFound = queue.isEmpty()
                                               || costComparator.compare(
                                                       mySeen.get( queue.peek() ),
                                                       currentCost ) > 0;
            }
            return currentNode;
        }

        public boolean isDone()
        {
            if ( !calculateAllShortestPaths )
            {
                return oneShortestPathHasBeenFound;
            }
            return allShortestPathsHasBeenFound;
        }
    }

    /**
     * Same as calculate(), but will set the flag to calculate all shortest
     * paths. It sets the flag and then calls calculate, so inheriting classes
     * only need to override calculate().
     *
     * @return
     */
    public boolean calculateMultiple()
    {
        if ( !calculateAllShortestPaths )
        {
            reset();
            calculateAllShortestPaths = true;
        }
        return calculate();
    }

    /**
     * Makes the main calculation If some limit is set, the shortest path(s)
     * that could be found within those limits will be calculated.
     *
     * @return True if a path was found.
     */
    public boolean calculate()
    {
        // Do this first as a general error check since this is supposed to be
        // called whenever a result is asked for.
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        // Don't do it more than once
        if ( doneCalculation )
        {
            return true;
        }
        doneCalculation = true;
        // Special case when path length is zero
        if ( startNode.equals( endNode ) )
        {
            foundPathsMiddleNodes = new HashSet();
            foundPathsMiddleNodes.add( startNode );
            foundPathsCost = costAccumulator.addCosts( startCost, startCost );
            return true;
        }
        HashMap seen1 = new HashMap();
        HashMap seen2 = new HashMap();
        HashMap dists1 = new HashMap();
        HashMap dists2 = new HashMap();
        DijstraIterator iter1 = new DijstraIterator( startNode, predecessors1,
                seen1, seen2, dists1, dists2, false );
        DijstraIterator iter2 = new DijstraIterator( endNode, predecessors2,
                seen2, seen1, dists2, dists1, true );
        Node node1 = null;
        Node node2 = null;
        while ( iter1.hasNext() && iter2.hasNext() )
        {
            if ( limitReached() )
            {
                break;
            }
            if ( iter1.hasNext() )
            {
                node1 = iter1.next();
                if ( node1 == null )
                {
                    break;
                }
            }
            if ( limitReached() )
            {
                break;
            }
            if ( !iter1.isDone() && iter2.hasNext() )
            {
                node2 = iter2.next();
                if ( node2 == null )
                {
                    break;
                }
            }
            if ( limitReached( seen1.get( node1 ), seen2.get( node2 ) ) )
            {
                break;
            }
            if ( iter1.isDone() || iter2.isDone() ) // A path was found
            {
                return true;
            }
        }

        return false;
    }

    /**
     * @return The cost for the found path(s).
     */
    public CostType getCost()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculate();
        return foundPathsCost;
    }

    /**
     * @return All the found paths or null.
     */
    public List> getPaths()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculateMultiple();
        if ( foundPathsMiddleNodes == null || foundPathsMiddleNodes.size() == 0 )
        {
            return Collections.emptyList();
        }
        // Currently we use a set to avoid duplicate paths
        // TODO: can this be done smarter?
        Set> paths = new HashSet>();
        for ( Node middleNode : foundPathsMiddleNodes )
        {
            List> paths1 = Util.constructAllPathsToNode(
                    middleNode, predecessors1, true, false );
            List> paths2 = Util.constructAllPathsToNode(
                    middleNode, predecessors2, false, true );
            // For all combinations...
            for ( List part1 : paths1 )
            {
                for ( List part2 : paths2 )
                {
                    // Combine them
                    LinkedList path = new LinkedList();
                    path.addAll( part1 );
                    path.addAll( part2 );
                    // Add to collection
                    paths.add( path );
                }
            }
        }
        return new LinkedList>( paths );
    }

    /**
     * @return All the found paths or null.
     */
    public List> getPathsAsNodes()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculateMultiple();
        if ( foundPathsMiddleNodes == null || foundPathsMiddleNodes.size() == 0 )
        {
            return null;
        }
        // Currently we use a set to avoid duplicate paths
        // TODO: can this be done smarter?
        Set> paths = new HashSet>();
        for ( Node middleNode : foundPathsMiddleNodes )
        {
            List> paths1 = Util.constructAllPathsToNodeAsNodes(
                    middleNode, predecessors1, true, false );
            List> paths2 = Util.constructAllPathsToNodeAsNodes(
                    middleNode, predecessors2, false, true );
            // For all combinations...
            for ( List part1 : paths1 )
            {
                for ( List part2 : paths2 )
                {
                    // Combine them
                    LinkedList path = new LinkedList();
                    path.addAll( part1 );
                    path.addAll( part2 );
                    // Add to collection
                    paths.add( path );
                }
            }
        }
        return new LinkedList>( paths );
    }

    /**
     * @return All the found paths or null.
     */
    public List> getPathsAsRelationships()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculateMultiple();
        if ( foundPathsMiddleNodes == null || foundPathsMiddleNodes.size() == 0 )
        {
            return null;
        }
        // Currently we use a set to avoid duplicate paths
        // TODO: can this be done smarter?
        Set> paths = new HashSet>();
        for ( Node middleNode : foundPathsMiddleNodes )
        {
            List> paths1 = Util.constructAllPathsToNodeAsRelationships(
                    middleNode, predecessors1, false );
            List> paths2 = Util.constructAllPathsToNodeAsRelationships(
                    middleNode, predecessors2, true );
            // For all combinations...
            for ( List part1 : paths1 )
            {
                for ( List part2 : paths2 )
                {
                    // Combine them
                    LinkedList path = new LinkedList();
                    path.addAll( part1 );
                    path.addAll( part2 );
                    // Add to collection
                    paths.add( path );
                }
            }
        }
        return new LinkedList>( paths );
    }

    /**
     * @return One of the shortest paths found or null.
     */
    public List getPath()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculate();
        if ( foundPathsMiddleNodes == null || foundPathsMiddleNodes.size() == 0 )
        {
            return null;
        }
        Node middleNode = foundPathsMiddleNodes.iterator().next();
        LinkedList path = new LinkedList();
        path.addAll( Util.constructSinglePathToNode( middleNode, predecessors1,
                true, false ) );
        path.addAll( Util.constructSinglePathToNode( middleNode, predecessors2,
                false, true ) );
        return path;
    }

    /**
     * @return One of the shortest paths found or null.
     */
    public List getPathAsNodes()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculate();
        if ( foundPathsMiddleNodes == null || foundPathsMiddleNodes.size() == 0 )
        {
            return null;
        }
        Node middleNode = foundPathsMiddleNodes.iterator().next();
        LinkedList pathNodes = new LinkedList();
        pathNodes.addAll( Util.constructSinglePathToNodeAsNodes( middleNode,
                predecessors1, true, false ) );
        pathNodes.addAll( Util.constructSinglePathToNodeAsNodes( middleNode,
                predecessors2, false, true ) );
        return pathNodes;
    }

    /**
     * @return One of the shortest paths found or null.
     */
    public List getPathAsRelationships()
    {
        if ( startNode == null || endNode == null )
        {
            throw new RuntimeException( "Start or end node undefined." );
        }
        calculate();
        if ( foundPathsMiddleNodes == null || foundPathsMiddleNodes.size() == 0 )
        {
            return null;
        }
        Node middleNode = foundPathsMiddleNodes.iterator().next();
        List path = new LinkedList();
        path.addAll( Util.constructSinglePathToNodeAsRelationships( middleNode,
                predecessors1, false ) );
        path.addAll( Util.constructSinglePathToNodeAsRelationships( middleNode,
                predecessors2, true ) );
        return path;
    }

    /**
     * This sets the maximum depth in the form of a maximum number of
     * relationships to follow.
     *
     * @param maxRelationShipsToTraverse
     */
    public void limitMaxRelationShipsToTraverse( long maxRelationShipsToTraverse )
    {
        this.maxRelationShipsToTraverse = maxRelationShipsToTraverse;
    }

    /**
     * This sets the maximum depth in the form of a maximum number of nodes to
     * scan.
     *
     * @param maxNodesToTraverse
     */
    public void limitMaxNodesToTraverse( long maxNodesToTraverse )
    {
        this.maxNodesToTraverse = maxNodesToTraverse;
    }

    /**
     * Set the end node. Will reset the calculation.
     *
     * @param endNode the endNode to set
     */
    public void setEndNode( Node endNode )
    {
        reset();
        this.endNode = endNode;
    }

    /**
     * Set the start node. Will reset the calculation.
     *
     * @param startNode the startNode to set
     */
    public void setStartNode( Node startNode )
    {
        this.startNode = startNode;
        reset();
    }

    /**
     * @return the relationDirection
     */
    public Direction getDirection()
    {
        return relationDirection;
    }

    /**
     * @return the costRelationType
     */
    public RelationshipType[] getRelationshipTypes()
    {
        return costRelationTypes;
    }

    /**
     * Set the evaluator for pruning the paths when the maximum cost is
     * exceeded.
     *
     * @param maxCost
     */
    public void limitMaxCostToTraverse( CostType maxCost )
    {
        this.maxCost = maxCost;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy