Faster path finding in C#

Until recently at work, we’ve been using the excellent QuickGraph library for finding optimal haul routes across pits. This week, I replaced it with my own creation, SpryGraph, written from the ground up for performance. (SpryGraph is not a replacement for QuickGraph, due to only having a tiny subset of the features)

Path finding is a much-researched topic in computer science. For a good overview see this paper [pdf].

Some brief performance comparisons:

Vertex Count Degree Algorithm Query Time – SpryGraph Query Time – QuickGraph
200,000 2 Dijkstra 145.9ms 903.3ms
1,000,000 4 A* 513.5ms 9625.7ms

How to use it:

First define your graph, vertex and edge classes:

using System;
using System.Collections.Generic;
using Alastri.SpryGraph;

namespace MyApp
{
    public class MyGraph : IMinimalPathFindingGraph<MyVertex,MyEdge>
    {
        public IReadOnlyList<MyEdge> GetOutEdges(MyVertex vertex)
        {
            return vertex.OutEdges;
        }
    }

    public  class MyEdge : ICostedEdge<MyVertex>
    {
        public MyEdge(MyVertex source, MyVertex target)
        {
            Source = source;
            Target = target;
        }

        public MyVertex Source { get; private set; }
        public MyVertex Target { get; private set; }
        public double GetCost()
        {
            return MyVertex.Distance(Source, Target);
        }
    }

    public  class MyVertex : IHeuristicVertex<MyVertex>
    {
        public MyVertex(double x, double y, double z, IReadOnlyList<MyEdge> outEdges)
        {
            X = x;
            Y = y;
            OutEdges = outEdges;
            Z = z;
        }

        public double X { get; private set; }
        public double Y { get; private set; }
        public double Z { get; private set; }

        public IReadOnlyList<MyEdge> OutEdges { get; private set; }

        public static double Distance(MyVertex a, MyVertex b)
        {
            double dx = a.X - b.X;
            double dy = a.Y - b.Y;
            double dz = a.Z - b.Z;
            return Math.Sqrt(dx * dx + dy * dy + dz * dz);
        }

        public double Heuristic(MyVertex destination)
        {
            return Distance(this, destination);
        }
    }
}

Second, create some code to do path finding:

using System;
using Alastri.SpryGraph;

namespace MyApp
{
    class Program
    {
        static void Main()
        {
            //create our graph
            MyGraph myGraph = new MyGraph();

            //create some vertices
            MyVertex v1 = new MyVertex(1,2,3);
            MyVertex v2 = new MyVertex(4,5,6);

            //add an edge from v1 -> v2
            v1.OutEdges = new[] {new MyEdge(v1, v2)};

            //read the graph into an efficient data structure
            GraphReader<MyVertex, MyEdge> graphReader = new GraphReader<MyVertex, MyEdge>(myGraph);

            //get a re-usable path finder, starting at v1
            IPathFinder<MyVertex,MyEdge> pathFinder = graphReader.GetAStarPathFinder(v1);

            //find path to v2
            MyEdge[] path;
            if (pathFinder.TryGetPath(v2, out path))
            {
                foreach (var item in path)
                {
                    Console.WriteLine("{0},{1},{2} -> {3},{4},{5}", v1.X, v1.Y, v1.Z, v2.X, v2.Y, v2.Z);
                }
            }


        }
    }
}

If successful, it should print:

1,2,3 -> 4,5,6

The full source is available here.

Leave a comment

Create a website or blog at WordPress.com