Skip to content

Commit

Permalink
Merge pull request #61 from fslaborg/digraphComponents
Browse files Browse the repository at this point in the history
added support for finding and returning components from digraph
  • Loading branch information
LibraChris authored Oct 16, 2023
2 parents eccad69 + 142a201 commit e34a9a4
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 25 deletions.
55 changes: 55 additions & 0 deletions src/Graphoscope/Algorithms/Components.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,59 @@ type Components() =
graph
|> Components.getGraphComponentsOfAdjGraph
|> Seq.maxBy (fun x -> AdjGraph.countNodes x)


/// DiGraph

/// Return true if all nodes in the graph are connect into one component.
/// </summary>
/// <param name="graph">The graph to analyse</param>
/// <returns>Returns true or false</returns>
static member hasGiantComponentOfDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
if (DFS.ofDiGraphUndirected (g.NodeKeys |> Seq.head) g
|> Seq.length) = g.NodeKeys .Count then true
else false

/// Finds seperate components of the graph and returns sets of nodes
/// </summary>
/// <param name="graph">The graph to analyse</param>
/// <returns>returns set of sets of nodes making up each component.</returns>
static member getComponentsDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
g.NodeKeys
|> Seq.map(fun k -> DFS.ofDiGraphUndirected k g |> Set.ofSeq)
|> Set.ofSeq


/// Finds the largest component and returns it's size. Whih may be the giant component.
/// </summary>
/// <param name="graph">The graph to analyse</param>
/// <returns>returns an int indicating numner of nodes</returns>
static member getLargestComponentSizeDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>)=
g
|> Components.getComponentsDiGraph
|> Set.map(fun s -> s.Count)
|> Set.toSeq
|> Seq.max


/// Finds the largest component and returns it as a new graph
/// </summary>
/// <param name="graph">The graph to analyse</param>
/// <returns>returns a new graph</returns>
static member getLargestComponentDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>)=
g
|> Components.getComponentsDiGraph
|> Seq.sortByDescending(fun c -> c |> Set.count)
|> Seq.head
|> fun c ->
DiGraph.empty
|> DiGraph.addNodes (c |> Set.toArray)
|> DiGraph.addEdges (
DiGraph.getAllEdges g
|> Array.filter(fun (f,t,_) ->
(c |> Set.map fst).Contains f && (c |> Set.map fst).Contains t)
)



97 changes: 73 additions & 24 deletions src/Graphoscope/Algorithms/DFS.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,85 @@ open Graphoscope
open System.Collections.Generic


/// <summary>
/// Depth-First Traversal (or Search)
/// </summary>
type DFS() =



/// <summary>
/// Traverses nodes reachable from given node in a Depth-First Traversal (or Search)
/// Depth-First Traversal (or Search)
/// </summary>
/// <param name="starting">Nodekey for starting the BFS traversal.</param>
/// <param name="graph">The graph to traverse.</param>
/// <returns>Sequence of node key and node data</returns>
static member ofFGraph (starting : 'NodeKey) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) =
let visited = HashSet<'NodeKey>()
let stack = Stack<'NodeKey>()
type DFS() =

static member private searchDiGraph
(starting : 'NodeKey)
(edgeFinder: 'NodeKey -> DiGraph<'NodeKey,'NodeData,'EdgeData> -> ('NodeKey * 'EdgeData) array)
(graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =

let visited = HashSet<'NodeKey>()
let stack = Stack<'NodeKey>()

stack.Push(starting)
visited.Add(starting) |> ignore
stack.Push(starting)
visited.Add(starting) |> ignore

seq {
while stack.Count > 0 do
let nodeKey = stack.Pop()
let (_, nd, s) = graph.[nodeKey]
yield (nodeKey, nd)
seq {
while stack.Count > 0 do
let nodeKey = stack.Pop()
let sucessors = edgeFinder nodeKey graph
let nodeData = DiGraph.Node.getNodeData nodeKey graph

yield (nodeKey, nodeData)

for kv in s do
if not(visited.Contains(kv.Key)) then
stack.Push(kv.Key)
visited.Add(kv.Key) |> ignore
}
for (key,_) in sucessors do
if not(visited.Contains(key)) then
stack.Push(key)
visited.Add(key) |> ignore
}

/// <summary>
/// Traverses nodes reachable from given node in a Depth-First Traversal (or Search)
/// </summary>
/// <param name="starting">Nodekey for starting the BFS traversal.</param>
/// <param name="graph">The graph to traverse.</param>
/// <returns>Sequence of node key and node data</returns>
static member ofFGraph (starting : 'NodeKey) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) =
let visited = HashSet<'NodeKey>()
let stack = Stack<'NodeKey>()

stack.Push(starting)
visited.Add(starting) |> ignore

seq {
while stack.Count > 0 do
let nodeKey = stack.Pop()
let (_, nd, s) = graph.[nodeKey]
yield (nodeKey, nd)

for kv in s do
if not(visited.Contains(kv.Key)) then
stack.Push(kv.Key)
visited.Add(kv.Key) |> ignore
}

/// Traverses nodes reachable from given node in a Depth-First Traversal (or Search)
/// </summary>
/// <param name="starting">Nodekey for starting the BFS traversal.</param>
/// <param name="graph">The graph to traverse.</param>
/// <returns>Sequence of node key and node data</returns>
static member ofDiGraph (starting : 'NodeKey) (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
DFS.searchDiGraph starting (DiGraph.getOutEdges) graph

/// Same as ofDiGraph except it traverses nodes along in and out edges.
/// This is useful for finding components and other operations where the direction of the edge shouldnt matter.
/// </summary>
/// <param name="starting">Nodekey for starting the BFS traversal.</param>
/// <param name="graph">The graph to traverse.</param>
/// <returns>Sequence of node key and node data</returns>
static member ofDiGraphUndirected (starting : 'NodeKey) (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =

let undirectedEdgeFinder (starting : 'NodeKey) (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
DiGraph.getOutEdges starting graph
|> Array.append(DiGraph.getInEdges starting graph)

DFS.searchDiGraph starting undirectedEdgeFinder graph




11 changes: 10 additions & 1 deletion src/Graphoscope/DiGraph.fs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,16 @@ module DiGraph =
/// <returns>Unit</returns>
static member removeNode (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) (node: 'NodeKey) =
DiGraph.removeNode node graph


/// <summary>
/// Returns Node Data for a given node from the graph
/// </summary>
/// <param name="node">The key of the node node to be returned</param>
/// <param name="graph">The graph the node will be returned from.</param>
/// <returns>Unit</returns>
static member getNodeData(node: 'NodeKey) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
graph.NodeData[graph.IdMap[node]]

type Edge() =

/// <summary>
Expand Down
68 changes: 68 additions & 0 deletions tests/Graphoscope.Tests/Components.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

module Components

open Xunit
open Graphoscope
open FSharpAux


let testGraph =
DiGraph.empty
|> DiGraph.addNode 1 1
|> DiGraph.addNode 2 2
|> DiGraph.addNode 3 3
|> DiGraph.addNode 4 4
|> DiGraph.addNode 5 5
|> DiGraph.addNode 6 6
|> DiGraph.addNode 7 7
|> DiGraph.addEdge (1, 2, 1.0)
|> DiGraph.addEdge (1, 3, 1.0)
|> DiGraph.addEdge (2, 4, 1.0)
|> DiGraph.addEdge (5, 6, 1.0)
|> DiGraph.addEdge (5, 7, 1.0)


let testGraphGiant =
DiGraph.empty
|> DiGraph.addNode 1 1
|> DiGraph.addNode 2 2
|> DiGraph.addNode 3 3
|> DiGraph.addNode 4 4
|> DiGraph.addNode 5 5
|> DiGraph.addNode 6 6
|> DiGraph.addNode 7 7
|> DiGraph.addEdge (1, 2, 1.0)
|> DiGraph.addEdge (1, 3, 1.0)
|> DiGraph.addEdge (2, 4, 1.0)
|> DiGraph.addEdge (4, 5, 1.0)
|> DiGraph.addEdge (5, 6, 1.0)
|> DiGraph.addEdge (5, 7, 1.0)

[<Fact>]
let ``Can detect no giant compenent`` () =
testGraph
|> Algorithms.Components.hasGiantComponentOfDiGraph
|> Assert.False

[<Fact>]
let ``Can detect giant compenent`` () =
testGraphGiant
|> Algorithms.Components.hasGiantComponentOfDiGraph
|> Assert.True

[<Fact>]
let ``Can get components`` () =
let components =
testGraph
|> Algorithms.Components.getComponentsDiGraph
|> Set.count

Assert.True (2 = components)

[<Fact>]
let ``Can get new graph of largest component`` () =
let newGraph =
testGraph
|> Algorithms.Components.getLargestComponentDiGraph

Assert.True (3 = (DiGraph.countEdges newGraph) )
27 changes: 27 additions & 0 deletions tests/Graphoscope.Tests/DFS.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,30 @@ let ``BFS simple example on FGraph works correctly`` () =
Assert.Equal<seq<int*string>>(expected, actual)


[<Fact>]
let ``DFS works on DiGraph`` () =

let actual =
DiGraph.empty
|> DiGraph.addNode 1 1
|> DiGraph.addNode 2 2
|> DiGraph.addNode 3 3
|> DiGraph.addNode 4 4
|> DiGraph.addNode 5 5
|> DiGraph.addNode 6 6
|> DiGraph.addNode 7 7
|> DiGraph.addEdge (1, 2, 1.0)
|> DiGraph.addEdge (1, 3, 1.0)
|> DiGraph.addEdge (2, 4, 1.0)
|> DiGraph.addEdge (2, 5, 1.0)
|> DiGraph.addEdge (3, 6, 1.0)
|> DiGraph.addEdge (5, 7, 1.0)
|> Algorithms.DFS.ofDiGraph 1


// DFS Traversal Result (starting node 1):
let expected = [( 1, 1); ( 3, 3); ( 6, 6); ( 2, 2);
( 5, 5); ( 7, 7); ( 4, 4);]

Assert.Equal<seq<int*int>>(expected, actual)

1 change: 1 addition & 0 deletions tests/Graphoscope.Tests/Graphoscope.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Compile Include="Loop.fs" />
<Compile Include="GraphGenerators.fs" />
<Compile Include="WedgeCount.fs" />
<Compile Include="Components.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
Expand Down

0 comments on commit e34a9a4

Please sign in to comment.