-
Notifications
You must be signed in to change notification settings - Fork 6
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
Add Longest path #79
Merged
Merged
Add Longest path #79
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
9fae168
Merge pull request #62 from fslaborg/developer
HarryMcCarney 74a2858
Merge pull request #64 from fslaborg/developer
DoganCK 8742f9d
Merge pull request #65 from fslaborg/developer
LibraChris 37ca140
Merge pull request #70 from fslaborg/developer
LibraChris 3eb9ea7
Add Measures.LongestPath
LibraChris 7687b53
Merge branch 'main' into longestPath
LibraChris a415f9b
Update LongestPath.fs
LibraChris c35201d
Update LongestPath.fs
LibraChris c14106f
Add tests for LongestPath
LibraChris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
namespace Graphoscope.Measures | ||
|
||
open Graphoscope | ||
open System.Collections.Generic | ||
|
||
type LongestPath() = | ||
|
||
/// <summary> | ||
/// Determines whether a cycle can be formed in the given FGraph starting from the specified node, | ||
/// using an algorithm based on priority queues and breadth-first traversal. | ||
/// </summary> | ||
/// <param name="starting">The node from which to check for the existence of a cycle.</param> | ||
/// <param name="graph">The FGraph in which to search for the cycle.</param> | ||
/// <returns>True if a cycle is found, otherwise false.</returns> | ||
static member hasCycleFromNodeInFGraph (starting : 'NodeKey) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = | ||
let visited = HashSet<'NodeKey>() | ||
//let stack = Stack<'NodeKey>() | ||
let priorityQueue: Queue<('NodeKey)> = System.Collections.Generic.Queue()//Priority_Queue.SimplePriorityQueue<('NodeKey*float),float>() | ||
|
||
//stack.Push(starting) | ||
priorityQueue.Enqueue(starting) | ||
visited.Add(starting) |> ignore | ||
|
||
|
||
let rec outerLoop counter = | ||
if priorityQueue.Count>0 then | ||
//if stack.Count > 0 then | ||
let nodeKey = priorityQueue.Dequeue() //et nodeKey = (stack.Pop()) | ||
let (_, nd, s) = graph.[nodeKey] | ||
|
||
printfn $"{nodeKey}" | ||
let rec innerLoops counter = | ||
if counter=s.Count then | ||
outerLoop (counter+1) | ||
else | ||
let node = s.Keys|>Seq.item counter | ||
if node=starting then | ||
true | ||
elif not(visited.Contains(node)) then | ||
//stack.Push(node) | ||
priorityQueue.Enqueue(node) | ||
visited.Add(node) |> ignore | ||
innerLoops (counter+1) | ||
else | ||
innerLoops (counter+1) | ||
innerLoops 0 | ||
else | ||
false | ||
outerLoop 0 | ||
|
||
/// <summary> | ||
/// Determines whether a cycle exists in the given FGraph. | ||
/// </summary> | ||
/// <param name="graph">The FGraph to check for the presence of a cycle</param> | ||
/// <returns>True if a cycle is found, otherwise false.</returns> | ||
static member hasCycleInFGraph (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = | ||
|
||
let nodes = graph|>Seq.map(fun kvp -> kvp.Key) | ||
|
||
let rec isCyclic (counter:int) = | ||
if counter = (nodes|>Seq.length) then | ||
false | ||
else | ||
let node = nodes |>Seq.item counter | ||
if LongestPath.hasCycleFromNodeInFGraph node graph then | ||
true | ||
else | ||
isCyclic (counter+1) | ||
isCyclic 0 | ||
|
||
/// <summary> | ||
/// Computes longest paths from <paramref name="starting"/> for <paramref name="graph"/> using an inverse Dijkstra's algorithm. | ||
/// </summary> | ||
/// <param name="starting"> Calculate the longest paths from this node.</param> | ||
/// <param name="getEdgeWeight"> Function to convert the EdgeData to a float.</param> | ||
/// <param name="graph"> The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph.</param> | ||
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks> | ||
/// <returns>Tuples of an ordered list containing the path and the distance of the longest path.</returns> | ||
static member getLongestPathOfFGraphUnchecked (starting : 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> ) = | ||
|
||
//Inverse Version of Djikstra, transforming the edgeWeights into negatives | ||
let getLongestPathDictOfFGraph (starting : 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> ) = | ||
let distance = Dictionary<'NodeKey, ('NodeKey*float)>() | ||
//let priorityQueue = SortedSet<'NodeKey * float>(Comparer<'NodeKey * float>.Create(fun (_, d1) (_, d2) -> compare d1 d2)) | ||
let priorityQueue: Queue<('NodeKey * float)> = System.Collections.Generic.Queue()//Priority_Queue.SimplePriorityQueue<('NodeKey*float),float>() | ||
let infinity = System.Double.MaxValue | ||
|
||
// Initialize distances to infinity for all nodes except the starting node | ||
// TODO: this can be improved by getOrDefault | ||
for nodeKey in graph.Keys do | ||
if nodeKey = starting then | ||
distance.[nodeKey] <- (starting,0.) | ||
else | ||
distance.[nodeKey] <- (starting,infinity) | ||
|
||
priorityQueue.Enqueue((starting, 0.)) |> ignore | ||
|
||
while priorityQueue.Count > 0 do | ||
let (currentNode, currentDistance) = priorityQueue.Dequeue() | ||
|
||
let (_, _, predecessors) = graph.[currentNode] | ||
|
||
for kv in predecessors do | ||
if kv.Key <> currentNode then | ||
let kvValue = kv.Value |> getEdgeWeight |> fun x -> -x | ||
//if kvValue < 0. then failwithf "Dijkstra does not handle neg. edge weight" | ||
let totalDistance = (currentDistance + kvValue) // Assuming edgeWeight is always 1 in this example | ||
let prevNode,prevDistance = distance.[kv.Key] | ||
|
||
// Improve getValue | ||
if totalDistance < prevDistance then | ||
distance.[kv.Key] <- (currentNode,totalDistance) | ||
priorityQueue.Enqueue(kv.Key,totalDistance) |> ignore | ||
Seq.sortBy snd priorityQueue |>ignore | ||
|
||
distance | ||
|
||
//Contains the dictionary create by getLongestPathDictOfFGraph of nodeKey,(pred,pathLenght) | ||
let longestPathDict = getLongestPathDictOfFGraph (starting : 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> ) | ||
|
||
//Contains the most distant nodeKey and the negative path lenght to it | ||
let mostDistantNode,longestPath = | ||
longestPathDict | ||
|>Seq.minBy(fun kvp -> | ||
kvp.Value | ||
|>snd | ||
) | ||
|>fun kvp -> | ||
kvp.Key,snd kvp.Value | ||
|
||
//Function to recreate the path to the node with the longest path based on the inverse Djikstra. Returns an ordered List of the path from the starting node to the most distant node | ||
let rec reconstructPath (path: 'NodeKey list) = | ||
if List.head path = starting then | ||
path | ||
else | ||
let currentHead = List.head path | ||
let pred: 'NodeKey = longestPathDict.[currentHead] |>fst | ||
reconstructPath (pred::path) | ||
|
||
reconstructPath [mostDistantNode] , (longestPath|>fun x -> x*(-1.)) | ||
|
||
/// <summary> | ||
/// Computes longest paths from <paramref name="starting"/> for <paramref name="graph"/> using an inverse Dijkstra's algorithm. | ||
/// </summary> | ||
/// <param name="starting"> Calculate the longest paths from this node.</param> | ||
/// <param name="getEdgeWeight"> Function to convert the EdgeData to a float.</param> | ||
/// <param name="graph"> The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph.</param> | ||
/// <remarks>Uses hasCycleInFGraph to check for cyclic character. If the given FGraph is not a Directed Acyclic Graph this will fail.</remarks> | ||
/// <returns>Tuples of an ordered list containing the path and the distance of the longest path.</returns> | ||
static member getLongestPathOfFGraph (starting : 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> ) = | ||
if LongestPath.hasCycleInFGraph graph then | ||
failwith "The given FGraph is not a Directed Acyclic Graph!" | ||
else | ||
LongestPath.getLongestPathOfFGraphUnchecked starting getEdgeWeight graph | ||
|
||
|
||
/// <summary> | ||
/// Computes longest paths from <paramref name="starting"/> for <paramref name="graph"/> using an inverse Dijkstra's algorithm. | ||
/// </summary> | ||
/// <param name="starting"> Calculate the longest paths from this node.</param> | ||
/// <param name="graph"> The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph.</param> | ||
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks> | ||
/// <remarks>Compute sets all EdgeWeights to 1. If you do not want this, consider computeByEdgeData of computeByEdgeDataWith.</remarks> | ||
/// <returns>Tuples of an ordered list containing the path and the distance of the longest path.</returns> | ||
static member compute((starting : 'NodeKey),(graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> )) = | ||
LongestPath.getLongestPathOfFGraph starting (fun x -> 1.) graph | ||
|
||
/// <summary> | ||
/// Computes longest paths from <paramref name="starting"/> for <paramref name="graph"/> using an inverse Dijkstra's algorithm. | ||
/// </summary> | ||
/// <param name="starting"> Calculate the longest paths from this node.</param> | ||
/// <param name="graph"> The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph.</param> | ||
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks> | ||
/// <remarks>computeByEdgeData uses the value in EdgeData as the distance. Only usable with float weights as EdgeData.</remarks> | ||
/// <returns>Tuples of an ordered list containing the path and the distance of the longest path.</returns> | ||
static member computeByEdgeData((starting : 'NodeKey),(graph : FGraph<'NodeKey, 'NodeData, float> )) = | ||
LongestPath.getLongestPathOfFGraph starting id graph | ||
|
||
/// <summary> | ||
/// Computes longest paths from <paramref name="starting"/> for <paramref name="graph"/> using an inverse Dijkstra's algorithm. | ||
/// </summary> | ||
/// <param name="starting"> Calculate the longest paths from this node.</param> | ||
/// <param name="getEdgeWeight"> Function to convert the EdgeData to a float.</param> | ||
/// <param name="graph"> The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph.</param> | ||
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks> | ||
/// <remarks>computeByEdgeDataWith uses <paramref name="getEdgeWeight"/> to convert the EdgeData into the needed float weights.</remarks> | ||
/// <returns>Tuples of an ordered list containing the path and the distance of the longest path.</returns> | ||
static member computeByEdgeDataWith((starting : 'NodeKey),(getEdgeWeight : 'EdgeData -> float),(graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> )) = | ||
LongestPath.getLongestPathOfFGraph starting getEdgeWeight graph | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
module LongestPath | ||
|
||
open System | ||
open Xunit | ||
open Graphoscope | ||
open System.IO | ||
|
||
open Xunit | ||
|
||
[<Fact>] | ||
let ``smallCyclicGraphReturnsExceptedResults``() = | ||
let cyclicGraphExample = | ||
seq{ | ||
"a","b",1. | ||
"a","c",1. | ||
"a","d",15. | ||
"a","e",1. | ||
"c","e",1. | ||
"b","d",13. | ||
"d","e",1. | ||
"e","b",1. | ||
"d","d",2. | ||
} | ||
|> Seq.map(fun (s, t, w) -> (s, s, t, t, w)) | ||
|> FGraph.ofSeq | ||
|
||
let ex = new System.Exception("The given FGraph is not a Directed Acyclic Graph!") | ||
|
||
// Assert | ||
Assert.Throws<Exception>(fun () -> Measures.LongestPath.compute("a", cyclicGraphExample) |> ignore) | ||
|> fun thrown -> Assert.Equal(ex.Message, thrown.Message) | ||
|
||
[<Fact>] | ||
let smallAcyclicGraphReturnsExceptedResults () = | ||
|
||
let acyclicGraphExample = | ||
seq{ | ||
"A","B",2. | ||
"A","D",1. | ||
"B","C",2. | ||
"D","C",17.2 | ||
"A","C",18. | ||
|
||
} | ||
|>Seq.map(fun (s,t,w) -> (s,s,t,t,w)) | ||
|>FGraph.ofSeq | ||
|
||
Assert.Equal((["A"; "D"; "C"], 18.2),Measures.LongestPath.computeByEdgeData("A",acyclicGraphExample)) | ||
Assert.Equal((["A"; "B"; "C"], 2.),Measures.LongestPath.compute("A",acyclicGraphExample)) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LibraChris nitpick: you probably didn't need the constructor on this type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's correct. But this is implemented because every type annotation in this library includes this constructor. It's simply designed to conform to the established schema.