Skip to content

Commit

Permalink
Merge pull request #63 from fslaborg/undirected-graph-revival
Browse files Browse the repository at this point in the history
Undirected graph revival
  • Loading branch information
DoganCK authored Oct 19, 2023
2 parents b61fc52 + 103c6a8 commit 306ad6f
Show file tree
Hide file tree
Showing 17 changed files with 448 additions and 189 deletions.
146 changes: 119 additions & 27 deletions src/Graphoscope/Algorithms/Dijkstra.fs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,19 @@ type Dijkstra() =
distance

/// Computes the shortest path
static member internal getAdjacencyArrayFor (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) (getEdgeWeight : 'EdgeData -> float) (nodeIx: int) =
let dist =
Array.init (graph.NodeKeys.Count) (fun x -> if x = nodeIx then 0. else infinity)
graph.OutEdges[nodeIx]
|> ResizeArray.iter(fun (target, w) -> dist[target] <- getEdgeWeight w)
dist
static member internal getAdjacencyArrayForUndirected (graph: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) (getEdgeWeight : 'EdgeData -> float) (nodeIx: int) =
let dist =
Array.init (graph.NodeKeys.Count) (fun x -> if x = nodeIx then 0. else infinity)
graph.Edges[nodeIx]
|> ResizeArray.iter(fun (target, w) -> dist[target] <- getEdgeWeight w)
dist

static member internal getAdjacencyArrayForDirected (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) (getEdgeWeight : 'EdgeData -> float) (nodeIx: int) =
let dist =
Array.init (graph.NodeKeys.Count) (fun x -> if x = nodeIx then 0. else infinity)
graph.OutEdges[nodeIx]
|> ResizeArray.iter(fun (target, w) -> dist[target] <- getEdgeWeight w)
dist

/// <summary>
/// Computes shortest paths from <paramref name="source"/> for <paramref name="graph"/> using Dijkstra's algorithm in parallel.
Expand All @@ -130,32 +137,121 @@ type Dijkstra() =
/// <param name="source"> Calculate the shortest paths from this node.</param>
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks>
/// <returns>Tuples of target node and distance.</returns>
static member ofDiGraph (source: 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) : ('NodeKey * float) [] =
let que= ResizeArray()
static member ofUndirected (source: 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) : ('NodeKey * float) [] =
let que = SortedSet<int * float>(Comparer<int * float>.Create(fun (n1, d1) (n2, d2) -> compare (d1,n1) (d2,n2)))
let sourceIx = graph.IdMap[source]
let dist = Dijkstra.getAdjacencyArrayFor graph getEdgeWeight sourceIx
let dist = Array.init (graph.NodeKeys.Count) (fun ix -> if ix = sourceIx then 0. else infinity)

for n in 0 .. graph.NodeKeys.Count - 1 do
que.Add(n)
que.Add((sourceIx, 0.)) |> ignore

while que.Count > 0 do
let minDistNode =
que
|> ResizeArray.minBy( fun n -> dist[n])
let (currentNodeIx, currentDistance) = que.Min
que.Remove(que.Min) |> ignore

let minDistNodeIx = que.IndexOf minDistNode
que.RemoveAt minDistNodeIx
let neighbors = graph.Edges[currentNodeIx]

let minDistAdjacency = Dijkstra.getAdjacencyArrayFor graph getEdgeWeight minDistNode
for (ix, ed) in neighbors do
let newCost = currentDistance + (getEdgeWeight ed)
if newCost < dist[ix] then
dist[ix] <- newCost
que.Add((ix, newCost)) |> ignore

for n in que do
let newCost = dist[minDistNode] + minDistAdjacency[n]
if newCost < dist[n] then
dist[n] <- newCost
dist
|> Array.mapi(fun i x -> graph.NodeKeys[i], x)


/// <summary>
/// Computes shortest paths from <paramref name="source"/> for <paramref name="graph"/> using Dijkstra's algorithm in parallel.
/// </summary>
/// <param name="graph"> The graph for which to compute the shortest path.</param>
/// <param name="source"> Calculate the shortest paths from this node.</param>
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks>
/// <returns>Tuples of target node and distance.</returns>
static member ofDiGraph (source: 'NodeKey) (getEdgeWeight : 'EdgeData -> float) (graph: DiGraph<'NodeKey, _, 'EdgeData>) : ('NodeKey * float) [] =
let que= SortedSet<int * float>(Comparer<int * float>.Create(fun (n1, d1) (n2, d2) -> compare (d1,n1) (d2,n2)))
let sourceIx = graph.IdMap[source]
let dist = Array.init (graph.NodeKeys.Count) (fun ix -> if ix = sourceIx then 0. else infinity)

que.Add((sourceIx, 0.)) |> ignore

while que.Count > 0 do
let (currentNodeIx, currentDistance) = que.Min
que.Remove(que.Min) |> ignore

let successors = graph.OutEdges[currentNodeIx]

for (ix, ed) in successors do
let newCost = currentDistance + (getEdgeWeight ed)
if newCost < dist[ix] then
que.Add((ix, newCost)) |> ignore
dist[ix] <- newCost
dist
|> Array.mapi(fun i x -> graph.NodeKeys[i], x)

/// <summary>
/// Computes all-pairs shortest paths for <paramref name="graph"/> using Dijkstra algorithm in parallel.
/// </summary>
/// <param name="graph">The graph for which to compute the shortest paths.</param>
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks>
/// <returns>
/// The ordered array of nodes and 2D Array of distances where each
/// row and column index corresponds to a node's index in the nodes array.
/// </returns>
static member ofUndirectedAllPairs (getEdgeWeight : 'EdgeData -> float) (graph: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>): 'NodeKey [] * float [][] =
let dijkstra (sourceIx: int) =
let que= SortedSet<int * float>(Comparer<int * float>.Create(fun (n1, d1) (n2, d2) -> compare (d1,n1) (d2,n2)))
let dist = Array.init (graph.NodeKeys.Count) (fun ix -> if ix = sourceIx then 0. else infinity)

que.Add((sourceIx, 0.)) |> ignore

while que.Count > 0 do
let (currentNodeIx, currentDistance) = que.Min
que.Remove(que.Min) |> ignore

let neighbors = graph.Edges[currentNodeIx]

for (ix, ed) in neighbors do
let newCost = currentDistance + (getEdgeWeight ed)
if newCost < dist[ix] then
que.Add((ix, newCost)) |> ignore
dist[ix] <- newCost
dist

graph.NodeKeys |> Array.ofSeq,
dijkstra |> Array.Parallel.init graph.NodeKeys.Count

/// <summary>
/// Computes all-pairs shortest paths for <paramref name="graph"/> using Dijkstra algorithm in parallel.
/// </summary>
/// <param name="graph">The graph for which to compute the shortest paths.</param>
/// <remarks>If there isn't a path between two edges, the distance is set to `infinity`.</remarks>
/// <returns>
/// The ordered array of nodes and 2D Array of distances where each
/// row and column index corresponds to a node's index in the nodes array.
/// </returns>
static member ofDiGraphAllPairs (getEdgeWeight : 'EdgeData -> float) (graph: DiGraph<'NodeKey, _, 'EdgeData>): 'NodeKey [] * float [][] =
let dijkstra (sourceIx: int) =
let que= SortedSet<int * float>(Comparer<int * float>.Create(fun (n1, d1) (n2, d2) -> compare (d1,n1) (d2,n2)))
let dist = Array.init (graph.NodeKeys.Count) (fun ix -> if ix = sourceIx then 0. else infinity)

que.Add((sourceIx, 0.)) |> ignore

while que.Count > 0 do
let (currentNodeIx, currentDistance) = que.Min
que.Remove(que.Min) |> ignore

let successors = graph.OutEdges[currentNodeIx]

for (ix, ed) in successors do
let newCost = currentDistance + (getEdgeWeight ed)
if newCost < dist[ix] then
que.Add((ix, newCost)) |> ignore
dist[ix] <- newCost
dist

graph.NodeKeys |> Array.ofSeq,
dijkstra |> Array.Parallel.init graph.NodeKeys.Count


/// <summary>
/// Returns the distance in numebr of directed edges between two nodes.
/// </summary>
Expand All @@ -172,16 +268,12 @@ type Dijkstra() =
| None -> None



static member compute (starting : 'NodeKey, getEdgeWeight: ('EdgeData -> float), graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) =
Dijkstra.ofFGraph starting getEdgeWeight graph

static member compute (starting : 'NodeKey, getEdgeWeight: ('EdgeData -> float), graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
static member compute (starting : 'NodeKey, getEdgeWeight: ('EdgeData -> float), graph : DiGraph<'NodeKey, _, 'EdgeData>) =
Dijkstra.ofDiGraph starting getEdgeWeight graph

static member compute (starting : 'NodeKey, getEdgeWeight: ('EdgeData -> float), graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) =
Dijkstra.ofAdjGraph starting getEdgeWeight graph

static member computeBetween (origin : 'NodeKey, destination :'NodeKey, graph : FGraph<'NodeKey, 'NodeData, float>) =
//TODO: Implement Dijkstra.ofFGraphBetween
System.NotImplementedException() |> raise
Expand Down
8 changes: 5 additions & 3 deletions src/Graphoscope/Algorithms/Louvain.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

open Graphoscope
open System.Collections.Generic

open FSharpAux

///Louvain method for community detection
//Blondel, Vincent D; Guillaume, Jean-Loup; Lambiotte, Renaud; Lefebvre, Etienne (9 October 2008). "Fast unfolding of communities in large networks". Journal of Statistical Mechanics: Theory and Experiment. 2008
Expand Down Expand Up @@ -436,5 +436,7 @@ type Louvain() =
/// <returns>A new FGraph whose NodeData has been transformed into tupels, where the second part is the community accorging to modularity-optimization.</returns>
static member louvainRandom (modularityIncreaseThreshold: float) (weightF:'Edge -> float) (graph:AdjGraph<'Node,'Label,'Edge>) : (AdjGraph<'Node,'Label*int,'Edge>)=
Louvain.louvainResolution true weightF modularityIncreaseThreshold 1. graph






91 changes: 49 additions & 42 deletions src/Graphoscope/DiGraph.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
namespace Graphoscope

//open FSharpx.Collections
open FSharpAux
open System.Collections.Generic
open FSharp.Data
open Graphoscope
open System

type DiGraph<'NodeKey, 'NodeData, 'EdgeData when 'NodeKey: equality and 'NodeKey: comparison>() =
let idMap = Dictionary<'NodeKey,int>()
Expand Down Expand Up @@ -40,15 +36,15 @@ type DiGraph() =
/// /// <param name="graph">The graph the node will be added to.</param>
/// /// <returns>Unit</returns>
static member addNode (nodeKey: 'NodeKey) (nodeData: 'NodeData) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
if not (DiGraph.nodeExists nodeKey graph) then
if DiGraph.nodeExists nodeKey graph then
failwith $"Node already exists, {nodeKey}."
else
graph.IdMap.Add(nodeKey, graph.NodeKeys.Count * 1)
graph.NodeKeys.Add nodeKey
graph.NodeData.Add nodeData
graph.OutEdges.Add (ResizeArray())
graph.InEdges.Add (ResizeArray())
graph
else
failwith $"Node already exists, {nodeKey}."

static member addNodes (nodes: ('NodeKey * 'NodeData) []) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
nodes
Expand All @@ -64,14 +60,18 @@ type DiGraph() =
static member removeNode (node: 'NodeKey) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) =
let nodeIx = graph.IdMap[node]

graph.OutEdges
|> ResizeArray.iteri(fun ri r ->
r
|> ResizeArray.mapi(fun ci (target, _) -> if target = nodeIx then Some ci else None)
|> ResizeArray.choose id
|> ResizeArray.rev
|> ResizeArray.iter(fun x -> graph.OutEdges[ri].RemoveAt x)
)
let removeEdges (edges: ResizeArray<ResizeArray<int * 'EdgeData>>) =
edges
|> ResizeArray.iteri(fun ri r ->
r
|> ResizeArray.mapi(fun ci (target, _) -> if target = nodeIx then Some ci else None)
|> ResizeArray.choose id
|> ResizeArray.rev
|> ResizeArray.iter(fun x -> edges[ri].RemoveAt x)
)

removeEdges graph.OutEdges
removeEdges graph.InEdges

// Update IdMap
graph.IdMap.Remove node |> ignore
Expand Down Expand Up @@ -132,7 +132,7 @@ type DiGraph() =
/// <returns>An array of target nodes and the corresponding 'EdgeData.</returns>
static member getOutEdges (origin: 'NodeKey) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) : ('NodeKey * 'EdgeData) []=
graph.OutEdges[graph.IdMap[origin]]
|> Seq.map(fun (t, w) -> graph.NodeKeys[t], w)
|> ResizeArray.map(fun (t, w) -> graph.NodeKeys[t], w)
|> Array.ofSeq

/// <summary>
Expand All @@ -142,12 +142,11 @@ type DiGraph() =
/// <returns>An array of origin, destination nodes and the corresponding 'EdgeData tuples.</returns>
static member getAllEdges (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>): ('NodeKey * 'NodeKey * 'EdgeData) [] =
DiGraph.getNodes graph
|> Array.map(fun n ->
|> Array.collect(fun n ->
n
|> (fun n -> DiGraph.getOutEdges n graph)
|> Array.map(fun (t, w) -> n, t, w)
)
|> Array.concat

/// <summary>
/// Returns the outbound edges for given node
Expand All @@ -157,7 +156,7 @@ type DiGraph() =
/// <returns>An array of target nodes and the corresponding 'EdgeData.</returns>
static member getInEdges (destination: 'NodeKey) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) : ('NodeKey * 'EdgeData) []=
graph.InEdges[graph.IdMap[destination]]
|> Seq.map(fun (t, w) -> graph.NodeKeys[t], w)
|> ResizeArray.map(fun (t, w) -> graph.NodeKeys[t], w)
|> Array.ofSeq

/// <summary>
Expand All @@ -173,6 +172,15 @@ type DiGraph() =
// let getInEdges (dest: 'NodeKey) (g: DiGraph<'NodeKey>) =
// g.InEdges[g.IdMap[dest]]

static member internal getAllPossibleEdges (graph: DiGraph<'NodeKey, _, 'EdgeData>) =
graph.NodeKeys
|> Seq.allPairs graph.NodeKeys

/// Returns all possible edges in a digraph, excluding self-loops.
static member internal getNonLoopingPossibleEdges (graph: DiGraph<'NodeKey, _, 'EdgeData>) =
DiGraph.getAllPossibleEdges graph
|> Seq.filter(fun (n1, n2) -> n1 <> n2)

/// <summary>
/// Tries to find an edge between the specified nodes. Raises KeyNotFoundException if no such edge exists in the graph.
/// </summary>
Expand Down Expand Up @@ -232,18 +240,17 @@ type DiGraph() =

/// <summary>
/// Builds a graph from a list of edges.
/// This is a shorthand graph generation method
/// where NodeData is assumed to be of the same type as NodeKey.
/// </summary>
/// <param name="edges">An array of edges. Each edge is a three part tuple of origin node, the destination node, and any edge label such as the weight</param>
/// <returns>A graph containing the nodes</returns>
static member createFromEdges (edges: ('NodeKey * 'NodeKey * 'EdgeData)[]) : DiGraph<'NodeKey, 'NodeKey, 'EdgeData> =
let g = DiGraph<'NodeKey, 'NodeKey, 'EdgeData>()
edges
|> Array.map(fun (n1, n2, _) -> n1, n2)
|> Array.unzip
|> fun (a1, a2) -> Array.append a1 a2
|> Array.collect(fun (n1,n2,_)-> [|n1, n1; n2, n2|])
|> Array.distinct
|> Array.sort
|> fun x -> DiGraph.addNodes (x|>Array.map(fun nk -> nk, nk )) g
|> fun x -> DiGraph.addNodes x g
|> DiGraph.addEdges edges

static member addElement (nk1 : 'NodeKey) (nd1 : 'NodeData) (nk2 : 'NodeKey) (nd2 : 'NodeData) (ed : 'EdgeData) (g : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) : DiGraph<'NodeKey, 'NodeData, 'EdgeData> =
Expand All @@ -264,24 +271,6 @@ type DiGraph() =
static member createFromNodes<'NodeKey, 'NodeData, 'EdgeData when 'NodeKey: equality and 'NodeKey: comparison> (nodes: ('NodeKey * 'NodeData) []) : DiGraph<'NodeKey, 'NodeData, 'EdgeData> =
DiGraph<'NodeKey, 'NodeData, 'EdgeData>()
|>DiGraph.addNodes nodes


/// <summary>
/// Converts the DiGraph to an Adjacency Matrix
/// The operation assumes edge data types of float in the graph.
/// </summary>
/// <param name="graph">The graph to be converted</param>
/// <returns>An adjacency matrix</returns>
static member toMatrix (graph: DiGraph<'NodeKey, 'NodeData, float>) =
let matrix = Array.init graph.NodeKeys.Count (fun _ -> Array.init graph.NodeKeys.Count (fun _ -> 0.))
graph.OutEdges
|> ResizeArray.iteri(fun ri r ->
r
|> ResizeArray.iter(fun c ->
matrix[ri][fst c] <- snd c
)
)
matrix

/// <summary>
/// Gets the total number of nodes of the graph
Expand Down Expand Up @@ -315,6 +304,24 @@ type DiGraph() =
)
|> Seq.concat

/// <summary>
/// Converts the Graph to an Adjacency Matrix
/// This is preliminary step in many graph algorithms such as Floyd-Warshall.
/// The operation assumes edge data types of float in the graph.
/// </summary>
/// <param name="graph">The graph to be converted</param>
/// <returns>An adjacency matrix</returns>
static member toAdjacencyMatrix (getEdgeWeight : 'EdgeData -> float) (graph: DiGraph<'NodeKey, _, 'EdgeData>) =
let matrix = Array.init graph.NodeKeys.Count (fun _ -> Array.init graph.NodeKeys.Count (fun _ -> 0.))
graph.OutEdges
|> ResizeArray.iteri(fun ri r ->
r
|> ResizeArray.iter(fun (ci, v) ->
matrix[ri][ci] <- getEdgeWeight v
)
)
matrix


module DiGraph =
/// <summary>
Expand Down
Loading

0 comments on commit 306ad6f

Please sign in to comment.