/// <summary> /// Initializes this force-directed layout. Assumes that graph has some /// reasonable initial node positions. /// </summary> /// <param name="graph">The graph to layout.</param> /// <param name="start_node">The node to start layout from.</param> public ForceDirectedLayout(IReadOnlyGraph<FDLNode, FDLEdge> graph, FDLNode start_node) { if (graph == null) throw new ArgumentNullException("graph"); if (start_node == null) throw new ArgumentNullException("start_node"); if (!graph.ContainsNode(start_node)) throw new ArgumentException("start_node must be in this graph"); //initialize nodes array to only the reachable nodes ArrayList n = new ArrayList(graph.NodeCount); Algorithms.Algorithms.BreadthFirstSearch(graph, start_node, null, delegate(FDLNode node){ n.Add(node); }); nodes = new FDLNode[n.Count]; n.CopyTo(nodes); new_positions = new MutablePoint[nodes.Length]; accels = new double[nodes.Length]; //summarize constraints HashSet<FDLEdge> h = new HashSet<FDLEdge>(); for (int i = 0; i < nodes.Length; i++) { foreach (FDLEdge edge in nodes[i].OutEdges) { DefaultEdge reverse = edge.Target.GetEdgeTo(edge.Source); if(h.Contains(edge) || (reverse != null && h.Contains(reverse))) continue; h.Add(edge); } } constraints = new FDLEdge[h.Count]; h.CopyTo(constraints); }
public static List <T> DijkstraSolve <T>(this IReadOnlyGraph <T> graph, T start, T end) where T : IEquatable <T> { // not strictly necessary (DijkstraSolveWithInfo does the same), bu oh well, better safe than sorry :) graph.ThrowOnInvalidInput(start, end); return(graph.DijkstraSolveWithInfo(start, end).Path); }
/// <summary> /// Throw if one of the input parameters is not a valid start node. /// </summary> /// <typeparam name="T"></typeparam> /// <exception cref="Graph{T}.NodeNotFoundException"></exception> /// <exception cref="InvalidOperationException"></exception> private static void ThrowOnInvalidInput <T>(this IReadOnlyGraph <T> g, T start, T end) where T : IEquatable <T> { if (!g.Contains(start)) { Logger.Error("Start node doesn't exist in graph!"); throw new Graph <T> .NodeNotFoundException(start); } if (!g.Contains(end)) { Logger.Error("End node doesn't exist in graph!"); throw new Graph <T> .NodeNotFoundException(end); } if (!g.NodeIsPassable(start)) { Logger.Error("Can't calculate path starting from impassable node."); throw new InvalidOperationException(); } if (!g.NodeIsPassable(end)) { Logger.Error("Can't calculate path ending in impassable node."); throw new InvalidOperationException(); } }
/// <summary> /// Get a path between two points in the graph using the A* algorithm. /// If no path can be found, an empty list is returned. /// </summary> public static List <T> AStarSolve <T>(this IReadOnlyGraph <T> graph, T start, T end, ICalculator <T> calculator = null) where T : IEquatable <T> { // validate input graph.ThrowOnInvalidInput(start, end); return(graph.AStarSolveWithInfo(start, end, calculator).Path); }
/// <summary> /// Get a path between two points in the graph using the A* algorithm. /// A list of the visited nodes is also returned. /// If no path can be found, the <see cref="PathFindingResult{T}"/> will be empty, but no members will be null. /// <br/><see cref="PathFindingResult{T}"/> will contain the found path, the nodes that were queued to be evaluated /// (in <see cref="PathFindingResult{T}.OpenNodes"/>) and the nodes that were finally evaluated /// (in <see cref="PathFindingResult{T}.ClosedNodes"/>) /// </summary> public static PathFindingResult <T> AStarSolveWithInfo <T>(this IReadOnlyGraph <T> g, T start, T end, ICalculator <T> calculator = null) where T : IEquatable <T> { g.ThrowOnInvalidInput(start, end); if (start.Equals(end)) { return(PathFindingResult <T> .Empty); } // check if another calculator has been provided, if not choose the one from the graph calculator = calculator ?? g.Calculator; // initialize the open and closed list // the closed list contains all nodes that we don't want to evaluate ever again, they are done var closed = new HashSet <AStarNode <T> >(); // the open list contains the nodes that are queued up for evaluation at some point var open = new HashSet <AStarNode <T> > // first we enqueue the provided start node, with an estimated distance of start to end // it should be possible to make this float.maxValue instead { new AStarNode <T>(start, distanceToFinish: g.Calculator.Distance(start, end)) }; // we have nodes to evaluate while (open.Count > 0) { // get most promising node var recordHolder = AStarGetMostPromisingNode(open); if (recordHolder == null) { throw new InvalidOperationException("The most promising node was null. This should never ever happen!"); } // check if the record node is the finish and return the corresponding data if it is if (recordHolder.Data.Equals(end)) { return(new PathFindingResult <T>(BuildPath(recordHolder, closed), new HashSet <T>(open.Select(x => x.Data)), new HashSet <T>(closed.Select(x => x.Data)))); } // if we chose a node, we are kind of done with evaluating it again // (there are scenarios where that might be a good idea) open.Remove(recordHolder); closed.Add(recordHolder); // expand the record holder. AStarExpandNode(recordHolder, end, g.GetPassableConnections(recordHolder.Data), open, closed, calculator); } // if we leave the loop (we have no nodes to evaluate), we have no path return(PathFindingResult <T> .Empty); }
/// <summary> /// Kind of the worst kind of pathfinding you can choose, ever. /// <br/>It will (eventually) return a path between <see cref="start"/> and <see cref="end"/> using a /// recursive algorithm. /// </summary> public static List <T> RecursiveSolve <T>(this IReadOnlyGraph <T> graph, T start, T end) where T : IEquatable <T> { graph.ThrowOnInvalidInput(start, end); Logger.Warn("This can take a while!"); var path = RecursiveSolve(graph, start, end, new List <T>()); path.Reverse(); return(path); }
/// <summary> /// The actual recursive method that is used to calculate the path. /// <br/>This method has an "artificial" recursion anchor at <see cref="MaxDepth"/> to /// prevent a <see cref="StackOverflowException"/>. /// It will simply stop recursing deeper after a depth higher than <see cref="MaxDepth"/>. /// <br/>Use cautiously :) it can take a while. /// </summary> /// <param name="graph">The graph to perform the operation on.</param> /// <param name="start">The start node of the problem</param> /// <param name="end">The end node.</param> /// <param name="pathTo">The current path to the start node.</param> /// <param name="depth">The current depth we are at.</param> /// <typeparam name="T"></typeparam> /// <returns>Returns the reverse path from <see cref="start"/> to <see cref="end"/></returns> private static List <T> RecursiveSolve <T>(IReadOnlyGraph <T> graph, T start, T end, List <T> pathTo, int depth = 0) where T : IEquatable <T> { if (depth == MaxDepth) { return(new List <T>()); } List <T> path = null; // In this case LINQ made it way more readable :) so we keep it // we only want to ask neighbors for a path, if they aren't in the path towards us // otherwise there would be some nasty infinite loops making this bad algorithm even worse foreach (var nb in graph.GetPassableConnections(start).Where(nb => !pathTo.Contains(nb))) { // end and start have to be flipped because the path is built in reverse order if (nb.Equals(end)) { return new List <T> { end, start } } ; // hand the task of finding a path off to the neighbor var newPath = RecursiveSolve(graph, nb, end, new List <T>(pathTo) { start }, depth + 1); // the new path is only better if it exists, if there was no previous path, or // if it is shorter than the previous path if (newPath.Count > 0 && (path == null || newPath.Count < path.Count)) { path = newPath; } } // If there is a path we want to add start to it (living off our neighbors work) path?.Add(start); return(path ?? new List <T>()); } }
public static PathFindingResult <T> DijkstraSolveWithInfo <T>(this IReadOnlyGraph <T> g, T start, T end) where T : IEquatable <T> { g.ThrowOnInvalidInput(start, end); if (start.Equals(end)) { return(PathFindingResult <T> .Empty); } var startNode = new DijkstraNode <T>(start, currentPathLength: 0f); // we have visited the start node already and thus don't need to again var visited = new HashSet <DijkstraNode <T> > { startNode }; // we also want to add it to the open set though, to have a node to evaluate the neighbors from var open = new HashSet <DijkstraNode <T> > { startNode }; while (open.Count > 0) { // get the node that has the shortest saved path to the start node // TODO refactor into function (Finding best node) var curr = GetNearestNode(open); // if the best node is actually the finish: hooray lucky day, return it if (curr.Data.Equals(end)) { return(new PathFindingResult <T>(BuildPath(curr, visited), new HashSet <T>(open.Select(x => x.Data)), new HashSet <T>(visited.Select(x => x.Data)))); } // the just evaluated node isn't open for evaluation again, we were to pushy :( open.Remove(curr); // we have in fact just visited it visited.Add(curr); // expand this node DijkstraExpandNode(curr, g.GetPassableConnections(curr.Data), open, visited, g.Calculator); } // we haven't found a path return(PathFindingResult <T> .Empty); }
/// <summary> /// Use iterative BFS to find a path between <paramref name="start"/> and <paramref name="end"/>. /// This method will also return the visited nodes in the process. /// <br/>The returned <see cref="PathFindingResult{T}"/> will contain the path /// (in <see cref="PathFindingResult{T}.Path"/>), and the visited nodes /// (in <see cref="PathFindingResult{T}.OpenNodes"/>). /// <see cref="PathFindingResult{T}.ClosedNodes"/> will be empty as that is not applicable here. /// </summary> /// <param name="graph">The graph to operate on</param> /// <param name="start">The node to start searching from</param> /// <param name="end">The node that is the finish</param> public static PathFindingResult <T> IterativeBfsSolveWithInfo <T>(this IReadOnlyGraph <T> graph, T start, T end) where T : IEquatable <T> { // in BFS nodes are evaluated in order of appearance, so a Queue makes sense var nodesToCheck = new Queue <Node <T> >(); nodesToCheck.Enqueue(new Node <T>(start)); // These will be the visited nodes so we don't visit stuff multiple times (no loops either) var visited = new HashSet <Node <T> > { new Node <T>(start) }; while (nodesToCheck.Count > 0) { var currentNode = nodesToCheck.Dequeue(); var neighbors = graph.GetPassableConnections(currentNode.Data); foreach (var nb in neighbors) { // create a dummy node to check if it is the end and if we need to add it. // This works (also Contains()), because we have overriden Equals() and GetHashCode() accordingly var node = new Node <T>(nb, currentNode.Data); if (nb.Equals(end)) { return(new PathFindingResult <T>(BuildPath(node, visited), new HashSet <T>(visited.Select(x => x.Data)), new HashSet <T>())); } // Add() returns true, if the element is successfully added (no duplicates because hashset) :) if (visited.Add(node)) { nodesToCheck.Enqueue(node); } } } // TODO should we warn? Logger.Warn("No path found!"); return(PathFindingResult <T> .Empty); }
/// <summary> /// Use the iterative bfs algorithm to find a path between <paramref name="start"/> and <paramref name="end"/>. /// </summary> /// <param name="graph">The graph to solve on</param> /// <param name="start">The start node</param> /// <param name="end">The end node</param> /// <exception cref="Graph{T}.NodeNotFoundException">This will be thrown if either /// start or end are in the graph.</exception> /// <exception cref="InvalidOperationException">This will be thrown when one of /// the nodes is not passable at the time of asking (politely of course)</exception> public static List <T> IterativeBfsSolve <T>(this IReadOnlyGraph <T> graph, T start, T end) where T : IEquatable <T> { graph.ThrowOnInvalidInput(start, end); return(graph.IterativeBfsSolveWithInfo(start, end).Path); }