Skip to content

Support dynamic path decisions within the YenShortestPathsAlgorithm #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 134 additions & 4 deletions src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,95 @@ namespace QuikGraph.Algorithms.ShortestPath
/// <typeparam name="TVertex">Vertex type.</typeparam>
public class YenShortestPathsAlgorithm<TVertex>
{
/// <summary>
/// Struct provides the parameters used for the path inspection.
/// </summary>
public struct InspectPathParameters
{
/// <summary>
/// Initializes a new instance of the <see cref="InspectPathParameters"/> struct.
/// </summary>
public InspectPathParameters(SortedPath path, IEnumerable<SortedPath> shortestPaths, IEnumerable<SortedPath> acceptedPaths)
{
Path = path;
ShortestPaths = shortestPaths;
AcceptedPaths = acceptedPaths;
}

/// <summary>
/// The current found shortest path.
/// </summary>
public SortedPath Path { get; }

/// <summary>
/// All shortest paths found so far. They might were accepted or not. Includes the current found path.
/// </summary>
public IEnumerable<SortedPath> ShortestPaths { get; }

/// <summary>
/// All shortest paths that were accepted so far.
/// </summary>
public IEnumerable<SortedPath> AcceptedPaths { get; }
}

/// <summary>
/// Struct provides the result of the path inspection.
/// </summary>
public struct InspectPathResult
{
/// <summary>
/// Initializes a new instance of the <see cref="InspectPathResult"/> struct.
/// </summary>
public InspectPathResult(PathAcceptance pathAcceptance, SearchContinuation searchContinuation)
{
PathAcceptance = pathAcceptance;
SearchContinuation = searchContinuation;
}

/// <summary>
/// Decides whether to add the found path to the result set.
/// </summary>
public PathAcceptance PathAcceptance { get; }

/// <summary>
/// Decide whether to stop searching for more paths.
/// </summary>
public SearchContinuation SearchContinuation { get; }
}

/// <summary>
/// After path inspection, decide whether to add the found path to the result set.
/// </summary>
public enum PathAcceptance
{
/// <summary>
/// The found shortest path will be added to the result list.
/// </summary>
Accept,

/// <summary>
/// The found shortest path will not be added to the result list.
/// </summary>
Reject
}

/// <summary>
/// After path inspection, decide whether to stop searching for more paths.
/// </summary>
[Flags]
public enum SearchContinuation
{
/// <summary>
/// Continue the search until the k shortest paths are found
/// </summary>
Continue,

/// <summary>
/// The the search and return the found paths even if not k iterations have been done.
/// </summary>
StopSearch
}

/// <summary>
/// Class representing a sorted path.
/// </summary>
Expand Down Expand Up @@ -109,6 +198,9 @@ IEnumerator IEnumerable.GetEnumerator()
[NotNull]
private readonly Func<IEnumerable<SortedPath>, IEnumerable<SortedPath>> _filter;

[NotNull]
private readonly Func<InspectPathParameters, InspectPathResult> _inspectPath;

// Limit for the amount of paths
private readonly int _k;

Expand All @@ -127,6 +219,7 @@ IEnumerator IEnumerable.GetEnumerator()
/// <param name="k">Maximum number of path to search.</param>
/// <param name="edgeWeights">Optional function that computes the weight for a given edge.</param>
/// <param name="filter">Optional filter of found paths.</param>
/// <param name="inspectPath">Optional function to inspect a found shortest path right after it was found.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="graph"/> is <see langword="null"/>.</exception>
/// <exception cref="T:System.ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="T:System.ArgumentNullException"><paramref name="target"/> is <see langword="null"/>.</exception>
Expand All @@ -139,7 +232,8 @@ public YenShortestPathsAlgorithm(
[NotNull] TVertex target,
int k,
[CanBeNull] Func<EquatableTaggedEdge<TVertex, double>, double> edgeWeights = null,
[CanBeNull] Func<IEnumerable<SortedPath>, IEnumerable<SortedPath>> filter = null)
[CanBeNull] Func<IEnumerable<SortedPath>, IEnumerable<SortedPath>> filter = null,
[CanBeNull] Func<InspectPathParameters, InspectPathResult> inspectPath = null)
{
if (graph is null)
throw new ArgumentNullException(nameof(graph));
Expand All @@ -160,6 +254,7 @@ public YenShortestPathsAlgorithm(
_graph = graph.Clone();
_weights = edgeWeights ?? DefaultGetWeights;
_filter = filter ?? DefaultFilter;
_inspectPath = inspectPath ?? DefaultInspectPath;
}

[Pure]
Expand All @@ -175,6 +270,12 @@ private static double DefaultGetWeights([NotNull] EquatableTaggedEdge<TVertex, d
return edge.Tag;
}

[Pure]
private static InspectPathResult DefaultInspectPath(InspectPathParameters inspectPathParameters)
{
return new InspectPathResult(PathAcceptance.Accept, SearchContinuation.Continue);
}

[Pure]
private double GetPathDistance([ItemNotNull] SortedPath edges)
{
Expand Down Expand Up @@ -327,19 +428,48 @@ public IEnumerable<SortedPath> Execute()
{
SortedPath initialPath = GetInitialShortestPath();
var shortestPaths = new List<SortedPath> { initialPath };
var acceptedPaths = new List<SortedPath>();

InspectPathResult inspectPathResult = _inspectPath(new InspectPathParameters(initialPath, shortestPaths, acceptedPaths));

if (inspectPathResult.PathAcceptance == PathAcceptance.Accept)
{
acceptedPaths.Add(initialPath);
}

if (inspectPathResult.SearchContinuation == SearchContinuation.StopSearch)
{
return _filter(acceptedPaths);
}

// Initialize the set to store the potential k-th shortest path
var shortestPathCandidates = new BinaryQueue<SortedPath, double>(GetPathDistance);
var previousPath = initialPath;

for (int k = 1; k < _k; ++k)
{
SortedPath previousPath = shortestPaths[k - 1];

if (!SearchAndAddKthShortestPath(previousPath, shortestPaths, shortestPathCandidates))
{
break;
}

SortedPath newPath = shortestPaths[k];
inspectPathResult = _inspectPath(new InspectPathParameters(newPath, shortestPaths, acceptedPaths));

if (inspectPathResult.PathAcceptance == PathAcceptance.Accept)
{
acceptedPaths.Add(newPath);
}

if (inspectPathResult.SearchContinuation == SearchContinuation.StopSearch)
{
break;
}

previousPath = newPath;
}

return _filter(shortestPaths);
return _filter(acceptedPaths);
}

[NotNull, ItemNotNull]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,142 @@ void RunYenAndCheck(YenShortestPathsAlgorithm<char> yen)
#endregion
}

[Test]
public void InspectPath_RejectSomePaths()
{
var graph = new AdjacencyGraph<string, EquatableTaggedEdge<string, double>>(false);
graph.AddVertexRange(new[] { "A", "B", "C", "D" });
var edges = new[]
{
new EquatableTaggedEdge<string, double>("A", "B", 5),
new EquatableTaggedEdge<string, double>("A", "C", 6),
new EquatableTaggedEdge<string, double>("B", "C", 7),
new EquatableTaggedEdge<string, double>("B", "D", 8),
new EquatableTaggedEdge<string, double>("C", "D", 9)
};
graph.AddEdgeRange(edges);

// ignore all paths where "B" comes in the 2nd position
var algorithm = new YenShortestPathsAlgorithm<string>(graph, "A", "D", 5, inspectPath: InspectPath);
YenShortestPathsAlgorithm<string>.SortedPath[] paths = algorithm.Execute().ToArray();

// Expecting to find 3 paths:
// * => A-B-D (gets ignored)
// 1 => A-C-D
// * => A-B-C-D (gets ignored)
// Consistently checking the result
Assert.AreEqual(1, paths.Length);
// 1
EquatableTaggedEdge<string, double>[] path0 = paths[0].ToArray();
Assert.AreEqual(path0[0], edges[1]);
Assert.AreEqual(path0[1], edges[4]);

#region Local functions

YenShortestPathsAlgorithm<string>.InspectPathResult InspectPath(YenShortestPathsAlgorithm<string>.InspectPathParameters inspectPathParameters)
{
var pathAcceptance = inspectPathParameters.Path.GetVertex(1) == "B"
? YenShortestPathsAlgorithm<string>.PathAcceptance.Reject
: YenShortestPathsAlgorithm<string>.PathAcceptance.Accept;

return new YenShortestPathsAlgorithm<string>.InspectPathResult(pathAcceptance, YenShortestPathsAlgorithm<string>.SearchContinuation.Continue);
}

#endregion
}

[Test]
public void InspectPath_StopsSearchWhenConditionIsMet_ForInitialShortestPath()
{
var graph = new AdjacencyGraph<string, EquatableTaggedEdge<string, double>>(false);
graph.AddVertexRange(new[] { "A", "B", "C", "D" });
var edges = new[]
{
new EquatableTaggedEdge<string, double>("A", "B", 5),
new EquatableTaggedEdge<string, double>("A", "C", 6),
new EquatableTaggedEdge<string, double>("B", "C", 7),
new EquatableTaggedEdge<string, double>("B", "D", 8),
new EquatableTaggedEdge<string, double>("C", "D", 9)
};
graph.AddEdgeRange(edges);

// stop when we have found a shortest path that exceeds some costs
var algorithm = new YenShortestPathsAlgorithm<string>(graph, "A", "D", 10, inspectPath: InspectPath);
YenShortestPathsAlgorithm<string>.SortedPath[] paths = algorithm.Execute().ToArray();

// Expecting to get 3 paths:
// 1 => A-B-D (cost = 13)
// * => A-C-D (path limit reached)
// * => A-B-C-D (path limit reached)
// Consistently checking the result
Assert.AreEqual(1, paths.Length);
// 1
EquatableTaggedEdge<string, double>[] path0 = paths[0].ToArray();
Assert.AreEqual(path0[0], edges[0]);
Assert.AreEqual(path0[1], edges[3]);

#region Local functions

YenShortestPathsAlgorithm<string>.InspectPathResult InspectPath(YenShortestPathsAlgorithm<string>.InspectPathParameters inspectPathParameters)
{
var searchContinuation = inspectPathParameters.Path.Sum(x => x.Tag) >= 10
? YenShortestPathsAlgorithm<string>.SearchContinuation.StopSearch
: YenShortestPathsAlgorithm<string>.SearchContinuation.Continue;

return new YenShortestPathsAlgorithm<string>.InspectPathResult(YenShortestPathsAlgorithm<string>.PathAcceptance.Accept, searchContinuation);
}

#endregion
}

[Test]
public void InspectPath_StopsSearchWhenConditionIsMet_ForNthShortestPath()
{
var graph = new AdjacencyGraph<string, EquatableTaggedEdge<string, double>>(false);
graph.AddVertexRange(new[] { "A", "B", "C", "D" });
var edges = new[]
{
new EquatableTaggedEdge<string, double>("A", "B", 5),
new EquatableTaggedEdge<string, double>("A", "C", 6),
new EquatableTaggedEdge<string, double>("B", "C", 7),
new EquatableTaggedEdge<string, double>("B", "D", 8),
new EquatableTaggedEdge<string, double>("C", "D", 9)
};
graph.AddEdgeRange(edges);

// stop when we have found a shortest path that exceeds some costs
var algorithm = new YenShortestPathsAlgorithm<string>(graph, "A", "D", 10, inspectPath: InspectPath);
YenShortestPathsAlgorithm<string>.SortedPath[] paths = algorithm.Execute().ToArray();

// Expecting to get 3 paths:
// 1 => A-B-D (cost = 13)
// 2 => A-C-D (cost = 15)
// * => A-B-C-D (path limit reached)
// Consistently checking the result
Assert.AreEqual(2, paths.Length);
// 1
EquatableTaggedEdge<string, double>[] path0 = paths[0].ToArray();
Assert.AreEqual(path0[0], edges[0]);
Assert.AreEqual(path0[1], edges[3]);
// 2
EquatableTaggedEdge<string, double>[] path1 = paths[1].ToArray();
Assert.AreEqual(path1[0], edges[1]);
Assert.AreEqual(path1[1], edges[4]);

#region Local functions

YenShortestPathsAlgorithm<string>.InspectPathResult InspectPath(YenShortestPathsAlgorithm<string>.InspectPathParameters inspectPathParameters)
{
var searchContinuation = inspectPathParameters.Path.Sum(x => x.Tag) >= 15
? YenShortestPathsAlgorithm<string>.SearchContinuation.StopSearch
: YenShortestPathsAlgorithm<string>.SearchContinuation.Continue;

return new YenShortestPathsAlgorithm<string>.InspectPathResult(YenShortestPathsAlgorithm<string>.PathAcceptance.Accept, searchContinuation);
}

#endregion
}

[Test]
public void SortedPathHashCode()
{
Expand Down